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