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 */
60struct 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 */
79struct 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};
84typedef struct privdata *priv_p;
85
86static ng_constructor_t	ng_car_constructor;
87static ng_rcvmsg_t	ng_car_rcvmsg;
88static ng_shutdown_t	ng_car_shutdown;
89static ng_newhook_t	ng_car_newhook;
90static ng_rcvdata_t	ng_car_rcvdata;
91static ng_disconnect_t	ng_car_disconnect;
92
93static void	ng_car_refillhook(struct hookinfo *h);
94static void	ng_car_schedule(struct hookinfo *h);
95void		ng_car_q_event(node_p node, hook_p hook, void *arg, int arg2);
96static void	ng_car_enqueue(struct hookinfo *h, item_p item);
97
98/* Parse type for struct ng_car_hookstats */
99static const struct ng_parse_struct_field ng_car_hookstats_type_fields[]
100	= NG_CAR_HOOKSTATS;
101static 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 */
107static const struct ng_parse_struct_field ng_car_bulkstats_type_fields[]
108	= NG_CAR_BULKSTATS(&ng_car_hookstats_type);
109static 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 */
115static const struct ng_parse_struct_field ng_car_hookconf_type_fields[]
116	= NG_CAR_HOOKCONF;
117static 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 */
123static const struct ng_parse_struct_field ng_car_bulkconf_type_fields[]
124	= NG_CAR_BULKCONF(&ng_car_hookconf_type);
125static 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 */
131static 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 */
172static 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};
183NETGRAPH_INIT(car, &ng_car_typestruct);
184
185/*
186 * Node constructor
187 */
188static int
189ng_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 */
239static int
240ng_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 */
262static int
263ng_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 */
411static int
412ng_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 */
566static int
567ng_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 */
585static int
586ng_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 */
617static void
618ng_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 */
683static void
684ng_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 */
697void
698ng_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 */
743static void
744ng_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