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