1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2005 Nuno Antunes <nuno.antunes@gmail.com> 5 * Copyright (c) 2007 Alexander Motin <mav@freebsd.org> 6 * Copyright (c) 2019 Lutz Donnerhacke <lutz@donnerhacke.de> 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 * 30 * $FreeBSD$ 31 */ 32 33 /* 34 * ng_car - An implementation of committed access rate for netgraph 35 * 36 * TODO: 37 * - Sanitize input config values (impose some limits) 38 * - Implement DSCP marking for IPv4 39 * - Decouple functionality into a simple classifier (g/y/r) 40 * and various action nodes (i.e. shape, dcsp, pcp) 41 */ 42 43 #include <sys/param.h> 44 #include <sys/errno.h> 45 #include <sys/kernel.h> 46 #include <sys/malloc.h> 47 #include <sys/mbuf.h> 48 49 #include <netgraph/ng_message.h> 50 #include <netgraph/ng_parse.h> 51 #include <netgraph/netgraph.h> 52 #include <netgraph/ng_car.h> 53 54 #include "qos.h" 55 56 #define NG_CAR_QUEUE_SIZE 100 /* Maximum queue size for SHAPE mode */ 57 #define NG_CAR_QUEUE_MIN_TH 8 /* Minimum RED threshold for SHAPE mode */ 58 59 /* Hook private info */ 60 struct hookinfo { 61 hook_p hook; /* this (source) hook */ 62 hook_p dest; /* destination hook */ 63 64 int64_t tc; /* committed token bucket counter */ 65 int64_t te; /* exceeded/peak token bucket counter */ 66 struct bintime lastRefill; /* last token refill time */ 67 68 struct ng_car_hookconf conf; /* hook configuration */ 69 struct ng_car_hookstats stats; /* hook stats */ 70 71 struct mbuf *q[NG_CAR_QUEUE_SIZE]; /* circular packet queue */ 72 u_int q_first; /* first queue element */ 73 u_int q_last; /* last queue element */ 74 struct callout q_callout; /* periodic queue processing routine */ 75 struct mtx q_mtx; /* queue mutex */ 76 }; 77 78 /* Private information for each node instance */ 79 struct privdata { 80 node_p node; /* the node itself */ 81 struct hookinfo upper; /* hook to upper layers */ 82 struct hookinfo lower; /* hook to lower layers */ 83 }; 84 typedef struct privdata *priv_p; 85 86 static ng_constructor_t ng_car_constructor; 87 static ng_rcvmsg_t ng_car_rcvmsg; 88 static ng_shutdown_t ng_car_shutdown; 89 static ng_newhook_t ng_car_newhook; 90 static ng_rcvdata_t ng_car_rcvdata; 91 static ng_disconnect_t ng_car_disconnect; 92 93 static void ng_car_refillhook(struct hookinfo *h); 94 static void ng_car_schedule(struct hookinfo *h); 95 void ng_car_q_event(node_p node, hook_p hook, void *arg, int arg2); 96 static void ng_car_enqueue(struct hookinfo *h, item_p item); 97 98 /* Parse type for struct ng_car_hookstats */ 99 static const struct ng_parse_struct_field ng_car_hookstats_type_fields[] 100 = NG_CAR_HOOKSTATS; 101 static const struct ng_parse_type ng_car_hookstats_type = { 102 &ng_parse_struct_type, 103 &ng_car_hookstats_type_fields 104 }; 105 106 /* Parse type for struct ng_car_bulkstats */ 107 static const struct ng_parse_struct_field ng_car_bulkstats_type_fields[] 108 = NG_CAR_BULKSTATS(&ng_car_hookstats_type); 109 static const struct ng_parse_type ng_car_bulkstats_type = { 110 &ng_parse_struct_type, 111 &ng_car_bulkstats_type_fields 112 }; 113 114 /* Parse type for struct ng_car_hookconf */ 115 static const struct ng_parse_struct_field ng_car_hookconf_type_fields[] 116 = NG_CAR_HOOKCONF; 117 static const struct ng_parse_type ng_car_hookconf_type = { 118 &ng_parse_struct_type, 119 &ng_car_hookconf_type_fields 120 }; 121 122 /* Parse type for struct ng_car_bulkconf */ 123 static const struct ng_parse_struct_field ng_car_bulkconf_type_fields[] 124 = NG_CAR_BULKCONF(&ng_car_hookconf_type); 125 static const struct ng_parse_type ng_car_bulkconf_type = { 126 &ng_parse_struct_type, 127 &ng_car_bulkconf_type_fields 128 }; 129 130 /* Command list */ 131 static struct ng_cmdlist ng_car_cmdlist[] = { 132 { 133 NGM_CAR_COOKIE, 134 NGM_CAR_GET_STATS, 135 "getstats", 136 NULL, 137 &ng_car_bulkstats_type, 138 }, 139 { 140 NGM_CAR_COOKIE, 141 NGM_CAR_CLR_STATS, 142 "clrstats", 143 NULL, 144 NULL, 145 }, 146 { 147 NGM_CAR_COOKIE, 148 NGM_CAR_GETCLR_STATS, 149 "getclrstats", 150 NULL, 151 &ng_car_bulkstats_type, 152 }, 153 154 { 155 NGM_CAR_COOKIE, 156 NGM_CAR_GET_CONF, 157 "getconf", 158 NULL, 159 &ng_car_bulkconf_type, 160 }, 161 { 162 NGM_CAR_COOKIE, 163 NGM_CAR_SET_CONF, 164 "setconf", 165 &ng_car_bulkconf_type, 166 NULL, 167 }, 168 { 0 } 169 }; 170 171 /* Netgraph node type descriptor */ 172 static struct ng_type ng_car_typestruct = { 173 .version = NG_ABI_VERSION, 174 .name = NG_CAR_NODE_TYPE, 175 .constructor = ng_car_constructor, 176 .rcvmsg = ng_car_rcvmsg, 177 .shutdown = ng_car_shutdown, 178 .newhook = ng_car_newhook, 179 .rcvdata = ng_car_rcvdata, 180 .disconnect = ng_car_disconnect, 181 .cmdlist = ng_car_cmdlist, 182 }; 183 NETGRAPH_INIT(car, &ng_car_typestruct); 184 185 /* 186 * Node constructor 187 */ 188 static int 189 ng_car_constructor(node_p node) 190 { 191 priv_p priv; 192 193 /* Initialize private descriptor. */ 194 priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); 195 196 NG_NODE_SET_PRIVATE(node, priv); 197 priv->node = node; 198 199 /* 200 * Arbitrary default values 201 */ 202 203 priv->upper.hook = NULL; 204 priv->upper.dest = NULL; 205 priv->upper.tc = priv->upper.conf.cbs = NG_CAR_CBS_MIN; 206 priv->upper.te = priv->upper.conf.ebs = NG_CAR_EBS_MIN; 207 priv->upper.conf.cir = NG_CAR_CIR_DFLT; 208 priv->upper.conf.green_action = NG_CAR_ACTION_FORWARD; 209 priv->upper.conf.yellow_action = NG_CAR_ACTION_FORWARD; 210 priv->upper.conf.red_action = NG_CAR_ACTION_DROP; 211 priv->upper.conf.mode = 0; 212 getbinuptime(&priv->upper.lastRefill); 213 priv->upper.q_first = 0; 214 priv->upper.q_last = 0; 215 ng_callout_init(&priv->upper.q_callout); 216 mtx_init(&priv->upper.q_mtx, "ng_car_u", NULL, MTX_DEF); 217 218 priv->lower.hook = NULL; 219 priv->lower.dest = NULL; 220 priv->lower.tc = priv->lower.conf.cbs = NG_CAR_CBS_MIN; 221 priv->lower.te = priv->lower.conf.ebs = NG_CAR_EBS_MIN; 222 priv->lower.conf.cir = NG_CAR_CIR_DFLT; 223 priv->lower.conf.green_action = NG_CAR_ACTION_FORWARD; 224 priv->lower.conf.yellow_action = NG_CAR_ACTION_FORWARD; 225 priv->lower.conf.red_action = NG_CAR_ACTION_DROP; 226 priv->lower.conf.mode = 0; 227 priv->lower.lastRefill = priv->upper.lastRefill; 228 priv->lower.q_first = 0; 229 priv->lower.q_last = 0; 230 ng_callout_init(&priv->lower.q_callout); 231 mtx_init(&priv->lower.q_mtx, "ng_car_l", NULL, MTX_DEF); 232 233 return (0); 234 } 235 236 /* 237 * Add a hook. 238 */ 239 static int 240 ng_car_newhook(node_p node, hook_p hook, const char *name) 241 { 242 const priv_p priv = NG_NODE_PRIVATE(node); 243 244 if (strcmp(name, NG_CAR_HOOK_LOWER) == 0) { 245 priv->lower.hook = hook; 246 priv->upper.dest = hook; 247 bzero(&priv->lower.stats, sizeof(priv->lower.stats)); 248 NG_HOOK_SET_PRIVATE(hook, &priv->lower); 249 } else if (strcmp(name, NG_CAR_HOOK_UPPER) == 0) { 250 priv->upper.hook = hook; 251 priv->lower.dest = hook; 252 bzero(&priv->upper.stats, sizeof(priv->upper.stats)); 253 NG_HOOK_SET_PRIVATE(hook, &priv->upper); 254 } else 255 return (EINVAL); 256 return(0); 257 } 258 259 /* 260 * Data has arrived. 261 */ 262 static int 263 ng_car_rcvdata(hook_p hook, item_p item ) 264 { 265 struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook); 266 struct mbuf *m; 267 struct m_qos_color *colp; 268 enum qos_color col; 269 int error = 0; 270 u_int len; 271 272 /* If queue is not empty now then enqueue packet. */ 273 if (hinfo->q_first != hinfo->q_last) { 274 ng_car_enqueue(hinfo, item); 275 return (0); 276 } 277 278 m = NGI_M(item); 279 280 #define NG_CAR_PERFORM_MATCH_ACTION(a,col) \ 281 do { \ 282 switch (a) { \ 283 case NG_CAR_ACTION_FORWARD: \ 284 /* Do nothing. */ \ 285 break; \ 286 case NG_CAR_ACTION_MARK: \ 287 if (colp == NULL) { \ 288 colp = (void *)m_tag_alloc( \ 289 M_QOS_COOKIE, M_QOS_COLOR, \ 290 MTAG_SIZE(m_qos_color), M_NOWAIT); \ 291 if (colp != NULL) \ 292 m_tag_prepend(m, &colp->tag); \ 293 } \ 294 if (colp != NULL) \ 295 colp->color = col; \ 296 break; \ 297 case NG_CAR_ACTION_DROP: \ 298 default: \ 299 /* Drop packet and return. */ \ 300 NG_FREE_ITEM(item); \ 301 ++hinfo->stats.dropped_pkts; \ 302 return (0); \ 303 } \ 304 } while (0) 305 306 /* Packet is counted as 128 tokens for better resolution */ 307 if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { 308 len = 128; 309 } else { 310 len = m->m_pkthdr.len; 311 } 312 313 /* Determine current color of the packet (default green) */ 314 colp = (void *)m_tag_locate(m, M_QOS_COOKIE, M_QOS_COLOR, NULL); 315 if ((hinfo->conf.opt & NG_CAR_COLOR_AWARE) && (colp != NULL)) 316 col = colp->color; 317 else 318 col = QOS_COLOR_GREEN; 319 320 /* Check committed token bucket. */ 321 if (hinfo->tc - len >= 0 && col <= QOS_COLOR_GREEN) { 322 /* This packet is green. */ 323 ++hinfo->stats.green_pkts; 324 hinfo->tc -= len; 325 NG_CAR_PERFORM_MATCH_ACTION( 326 hinfo->conf.green_action, 327 QOS_COLOR_GREEN); 328 } else { 329 /* Refill only if not green without it. */ 330 ng_car_refillhook(hinfo); 331 332 /* Check committed token bucket again after refill. */ 333 if (hinfo->tc - len >= 0 && col <= QOS_COLOR_GREEN) { 334 /* This packet is green */ 335 ++hinfo->stats.green_pkts; 336 hinfo->tc -= len; 337 NG_CAR_PERFORM_MATCH_ACTION( 338 hinfo->conf.green_action, 339 QOS_COLOR_GREEN); 340 341 /* If not green and mode is SHAPE, enqueue packet. */ 342 } else if (hinfo->conf.mode == NG_CAR_SHAPE) { 343 ng_car_enqueue(hinfo, item); 344 return (0); 345 346 /* If not green and mode is RED, calculate probability. */ 347 } else if (hinfo->conf.mode == NG_CAR_RED) { 348 /* Is packet is bigger then extended burst? */ 349 if (len - (hinfo->tc - len) > hinfo->conf.ebs || 350 col >= QOS_COLOR_RED) { 351 /* This packet is definitely red. */ 352 ++hinfo->stats.red_pkts; 353 hinfo->te = 0; 354 NG_CAR_PERFORM_MATCH_ACTION( 355 hinfo->conf.red_action, 356 QOS_COLOR_RED); 357 358 /* Use token bucket to simulate RED-like drop 359 probability. */ 360 } else if (hinfo->te + (len - hinfo->tc) < hinfo->conf.ebs && 361 col <= QOS_COLOR_YELLOW) { 362 /* This packet is yellow */ 363 ++hinfo->stats.yellow_pkts; 364 hinfo->te += len - hinfo->tc; 365 /* Go to negative tokens. */ 366 hinfo->tc -= len; 367 NG_CAR_PERFORM_MATCH_ACTION( 368 hinfo->conf.yellow_action, 369 QOS_COLOR_YELLOW); 370 } else { 371 /* This packet is probably red. */ 372 ++hinfo->stats.red_pkts; 373 hinfo->te = 0; 374 NG_CAR_PERFORM_MATCH_ACTION( 375 hinfo->conf.red_action, 376 QOS_COLOR_RED); 377 } 378 /* If not green and mode is SINGLE/DOUBLE RATE. */ 379 } else { 380 /* Check extended token bucket. */ 381 if (hinfo->te - len >= 0 && col <= QOS_COLOR_YELLOW) { 382 /* This packet is yellow */ 383 ++hinfo->stats.yellow_pkts; 384 hinfo->te -= len; 385 NG_CAR_PERFORM_MATCH_ACTION( 386 hinfo->conf.yellow_action, 387 QOS_COLOR_YELLOW); 388 } else { 389 /* This packet is red */ 390 ++hinfo->stats.red_pkts; 391 NG_CAR_PERFORM_MATCH_ACTION( 392 hinfo->conf.red_action, 393 QOS_COLOR_RED); 394 } 395 } 396 } 397 398 #undef NG_CAR_PERFORM_MATCH_ACTION 399 400 NG_FWD_ITEM_HOOK(error, item, hinfo->dest); 401 if (error != 0) 402 ++hinfo->stats.errors; 403 ++hinfo->stats.passed_pkts; 404 405 return (error); 406 } 407 408 /* 409 * Receive a control message. 410 */ 411 static int 412 ng_car_rcvmsg(node_p node, item_p item, hook_p lasthook) 413 { 414 const priv_p priv = NG_NODE_PRIVATE(node); 415 struct ng_mesg *resp = NULL; 416 int error = 0; 417 struct ng_mesg *msg; 418 419 NGI_GET_MSG(item, msg); 420 switch (msg->header.typecookie) { 421 case NGM_CAR_COOKIE: 422 switch (msg->header.cmd) { 423 case NGM_CAR_GET_STATS: 424 case NGM_CAR_GETCLR_STATS: 425 { 426 struct ng_car_bulkstats *bstats; 427 428 NG_MKRESPONSE(resp, msg, 429 sizeof(*bstats), M_NOWAIT); 430 if (resp == NULL) { 431 error = ENOMEM; 432 break; 433 } 434 bstats = (struct ng_car_bulkstats *)resp->data; 435 436 bcopy(&priv->upper.stats, &bstats->downstream, 437 sizeof(bstats->downstream)); 438 bcopy(&priv->lower.stats, &bstats->upstream, 439 sizeof(bstats->upstream)); 440 } 441 if (msg->header.cmd == NGM_CAR_GET_STATS) 442 break; 443 case NGM_CAR_CLR_STATS: 444 bzero(&priv->upper.stats, 445 sizeof(priv->upper.stats)); 446 bzero(&priv->lower.stats, 447 sizeof(priv->lower.stats)); 448 break; 449 case NGM_CAR_GET_CONF: 450 { 451 struct ng_car_bulkconf *bconf; 452 453 NG_MKRESPONSE(resp, msg, 454 sizeof(*bconf), M_NOWAIT); 455 if (resp == NULL) { 456 error = ENOMEM; 457 break; 458 } 459 bconf = (struct ng_car_bulkconf *)resp->data; 460 461 bcopy(&priv->upper.conf, &bconf->downstream, 462 sizeof(bconf->downstream)); 463 bcopy(&priv->lower.conf, &bconf->upstream, 464 sizeof(bconf->upstream)); 465 /* Convert internal 1/(8*128) of pps into pps */ 466 if (bconf->downstream.opt & NG_CAR_COUNT_PACKETS) { 467 bconf->downstream.cir /= 1024; 468 bconf->downstream.pir /= 1024; 469 bconf->downstream.cbs /= 128; 470 bconf->downstream.ebs /= 128; 471 } 472 if (bconf->upstream.opt & NG_CAR_COUNT_PACKETS) { 473 bconf->upstream.cir /= 1024; 474 bconf->upstream.pir /= 1024; 475 bconf->upstream.cbs /= 128; 476 bconf->upstream.ebs /= 128; 477 } 478 } 479 break; 480 case NGM_CAR_SET_CONF: 481 { 482 struct ng_car_bulkconf *const bconf = 483 (struct ng_car_bulkconf *)msg->data; 484 485 /* Check for invalid or illegal config. */ 486 if (msg->header.arglen != sizeof(*bconf)) { 487 error = EINVAL; 488 break; 489 } 490 /* Convert pps into internal 1/(8*128) of pps */ 491 if (bconf->downstream.opt & NG_CAR_COUNT_PACKETS) { 492 bconf->downstream.cir *= 1024; 493 bconf->downstream.pir *= 1024; 494 bconf->downstream.cbs *= 128; 495 bconf->downstream.ebs *= 128; 496 } 497 if (bconf->upstream.opt & NG_CAR_COUNT_PACKETS) { 498 bconf->upstream.cir *= 1024; 499 bconf->upstream.pir *= 1024; 500 bconf->upstream.cbs *= 128; 501 bconf->upstream.ebs *= 128; 502 } 503 if ((bconf->downstream.cir > 1000000000) || 504 (bconf->downstream.pir > 1000000000) || 505 (bconf->upstream.cir > 1000000000) || 506 (bconf->upstream.pir > 1000000000) || 507 (bconf->downstream.cbs == 0 && 508 bconf->downstream.ebs == 0) || 509 (bconf->upstream.cbs == 0 && 510 bconf->upstream.ebs == 0)) 511 { 512 error = EINVAL; 513 break; 514 } 515 if ((bconf->upstream.mode == NG_CAR_SHAPE) && 516 (bconf->upstream.cir == 0)) { 517 error = EINVAL; 518 break; 519 } 520 if ((bconf->downstream.mode == NG_CAR_SHAPE) && 521 (bconf->downstream.cir == 0)) { 522 error = EINVAL; 523 break; 524 } 525 526 /* Copy downstream config. */ 527 bcopy(&bconf->downstream, &priv->upper.conf, 528 sizeof(priv->upper.conf)); 529 priv->upper.tc = priv->upper.conf.cbs; 530 if (priv->upper.conf.mode == NG_CAR_RED || 531 priv->upper.conf.mode == NG_CAR_SHAPE) { 532 priv->upper.te = 0; 533 } else { 534 priv->upper.te = priv->upper.conf.ebs; 535 } 536 537 /* Copy upstream config. */ 538 bcopy(&bconf->upstream, &priv->lower.conf, 539 sizeof(priv->lower.conf)); 540 priv->lower.tc = priv->lower.conf.cbs; 541 if (priv->lower.conf.mode == NG_CAR_RED || 542 priv->lower.conf.mode == NG_CAR_SHAPE) { 543 priv->lower.te = 0; 544 } else { 545 priv->lower.te = priv->lower.conf.ebs; 546 } 547 } 548 break; 549 default: 550 error = EINVAL; 551 break; 552 } 553 break; 554 default: 555 error = EINVAL; 556 break; 557 } 558 NG_RESPOND_MSG(error, node, item, resp); 559 NG_FREE_MSG(msg); 560 return (error); 561 } 562 563 /* 564 * Do local shutdown processing. 565 */ 566 static int 567 ng_car_shutdown(node_p node) 568 { 569 const priv_p priv = NG_NODE_PRIVATE(node); 570 571 ng_uncallout(&priv->upper.q_callout, node); 572 ng_uncallout(&priv->lower.q_callout, node); 573 mtx_destroy(&priv->upper.q_mtx); 574 mtx_destroy(&priv->lower.q_mtx); 575 NG_NODE_UNREF(priv->node); 576 free(priv, M_NETGRAPH); 577 return (0); 578 } 579 580 /* 581 * Hook disconnection. 582 * 583 * For this type, removal of the last link destroys the node. 584 */ 585 static int 586 ng_car_disconnect(hook_p hook) 587 { 588 struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook); 589 const node_p node = NG_HOOK_NODE(hook); 590 const priv_p priv = NG_NODE_PRIVATE(node); 591 592 if (hinfo) { 593 /* Purge queue if not empty. */ 594 while (hinfo->q_first != hinfo->q_last) { 595 NG_FREE_M(hinfo->q[hinfo->q_first]); 596 hinfo->q_first++; 597 if (hinfo->q_first >= NG_CAR_QUEUE_SIZE) 598 hinfo->q_first = 0; 599 } 600 /* Remove hook refs. */ 601 if (hinfo->hook == priv->upper.hook) 602 priv->lower.dest = NULL; 603 else 604 priv->upper.dest = NULL; 605 hinfo->hook = NULL; 606 } 607 /* Already shutting down? */ 608 if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) 609 && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) 610 ng_rmnode_self(NG_HOOK_NODE(hook)); 611 return (0); 612 } 613 614 /* 615 * Hook's token buckets refillment. 616 */ 617 static void 618 ng_car_refillhook(struct hookinfo *h) 619 { 620 struct bintime newt, deltat; 621 unsigned int deltat_us; 622 623 /* Get current time. */ 624 getbinuptime(&newt); 625 626 /* Get time delta since last refill. */ 627 deltat = newt; 628 bintime_sub(&deltat, &h->lastRefill); 629 630 /* Time must go forward. */ 631 if (deltat.sec < 0) { 632 h->lastRefill = newt; 633 return; 634 } 635 636 /* But not too far forward. */ 637 if (deltat.sec >= 1000) { 638 deltat_us = (1000 << 20); 639 } else { 640 /* convert bintime to the 1/(2^20) of sec */ 641 deltat_us = (deltat.sec << 20) + (deltat.frac >> 44); 642 } 643 644 if (h->conf.mode == NG_CAR_SINGLE_RATE) { 645 int64_t delta; 646 /* Refill committed token bucket. */ 647 h->tc += (h->conf.cir * deltat_us) >> 23; 648 delta = h->tc - h->conf.cbs; 649 if (delta > 0) { 650 h->tc = h->conf.cbs; 651 652 /* Refill exceeded token bucket. */ 653 h->te += delta; 654 if (h->te > ((int64_t)h->conf.ebs)) 655 h->te = h->conf.ebs; 656 } 657 658 } else if (h->conf.mode == NG_CAR_DOUBLE_RATE) { 659 /* Refill committed token bucket. */ 660 h->tc += (h->conf.cir * deltat_us) >> 23; 661 if (h->tc > ((int64_t)h->conf.cbs)) 662 h->tc = h->conf.cbs; 663 664 /* Refill peak token bucket. */ 665 h->te += (h->conf.pir * deltat_us) >> 23; 666 if (h->te > ((int64_t)h->conf.ebs)) 667 h->te = h->conf.ebs; 668 669 } else { /* RED or SHAPE mode. */ 670 /* Refill committed token bucket. */ 671 h->tc += (h->conf.cir * deltat_us) >> 23; 672 if (h->tc > ((int64_t)h->conf.cbs)) 673 h->tc = h->conf.cbs; 674 } 675 676 /* Remember this moment. */ 677 h->lastRefill = newt; 678 } 679 680 /* 681 * Schedule callout when we will have required tokens. 682 */ 683 static void 684 ng_car_schedule(struct hookinfo *hinfo) 685 { 686 int delay; 687 688 delay = (-(hinfo->tc)) * hz * 8 / hinfo->conf.cir + 1; 689 690 ng_callout(&hinfo->q_callout, NG_HOOK_NODE(hinfo->hook), hinfo->hook, 691 delay, &ng_car_q_event, NULL, 0); 692 } 693 694 /* 695 * Queue processing callout handler. 696 */ 697 void 698 ng_car_q_event(node_p node, hook_p hook, void *arg, int arg2) 699 { 700 struct hookinfo *hinfo = NG_HOOK_PRIVATE(hook); 701 struct mbuf *m; 702 int error; 703 704 /* Refill tokens for time we have slept. */ 705 ng_car_refillhook(hinfo); 706 707 /* If we have some tokens */ 708 while (hinfo->tc >= 0) { 709 /* Send packet. */ 710 m = hinfo->q[hinfo->q_first]; 711 NG_SEND_DATA_ONLY(error, hinfo->dest, m); 712 if (error != 0) 713 ++hinfo->stats.errors; 714 ++hinfo->stats.passed_pkts; 715 716 /* Get next one. */ 717 hinfo->q_first++; 718 if (hinfo->q_first >= NG_CAR_QUEUE_SIZE) 719 hinfo->q_first = 0; 720 721 /* Stop if none left. */ 722 if (hinfo->q_first == hinfo->q_last) 723 break; 724 725 /* If we have more packet, try it. */ 726 m = hinfo->q[hinfo->q_first]; 727 if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { 728 hinfo->tc -= 128; 729 } else { 730 hinfo->tc -= m->m_pkthdr.len; 731 } 732 } 733 734 /* If something left */ 735 if (hinfo->q_first != hinfo->q_last) 736 /* Schedule queue processing. */ 737 ng_car_schedule(hinfo); 738 } 739 740 /* 741 * Enqueue packet. 742 */ 743 static void 744 ng_car_enqueue(struct hookinfo *hinfo, item_p item) 745 { 746 struct mbuf *m; 747 int len; 748 struct m_qos_color *colp; 749 enum qos_color col; 750 751 NGI_GET_M(item, m); 752 NG_FREE_ITEM(item); 753 754 /* Determine current color of the packet (default green) */ 755 colp = (void *)m_tag_locate(m, M_QOS_COOKIE, M_QOS_COLOR, NULL); 756 if ((hinfo->conf.opt & NG_CAR_COLOR_AWARE) && (colp != NULL)) 757 col = colp->color; 758 else 759 col = QOS_COLOR_GREEN; 760 761 /* Lock queue mutex. */ 762 mtx_lock(&hinfo->q_mtx); 763 764 /* Calculate used queue length. */ 765 len = hinfo->q_last - hinfo->q_first; 766 if (len < 0) 767 len += NG_CAR_QUEUE_SIZE; 768 769 /* If queue is overflowed or we have no RED tokens. */ 770 if ((len >= (NG_CAR_QUEUE_SIZE - 1)) || 771 (hinfo->te + len >= NG_CAR_QUEUE_SIZE) || 772 (col >= QOS_COLOR_RED)) { 773 /* Drop packet. */ 774 ++hinfo->stats.red_pkts; 775 ++hinfo->stats.dropped_pkts; 776 NG_FREE_M(m); 777 778 hinfo->te = 0; 779 } else { 780 /* This packet is yellow. */ 781 ++hinfo->stats.yellow_pkts; 782 783 /* Enqueue packet. */ 784 hinfo->q[hinfo->q_last] = m; 785 hinfo->q_last++; 786 if (hinfo->q_last >= NG_CAR_QUEUE_SIZE) 787 hinfo->q_last = 0; 788 789 /* Use RED tokens. */ 790 if (len > NG_CAR_QUEUE_MIN_TH) 791 hinfo->te += len - NG_CAR_QUEUE_MIN_TH; 792 793 /* If this is a first packet in the queue. */ 794 if (len == 0) { 795 if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { 796 hinfo->tc -= 128; 797 } else { 798 hinfo->tc -= m->m_pkthdr.len; 799 } 800 801 /* Schedule queue processing. */ 802 ng_car_schedule(hinfo); 803 } 804 } 805 806 /* Unlock queue mutex. */ 807 mtx_unlock(&hinfo->q_mtx); 808 } 809