1169577Smav/*-
2169577Smav * Copyright (c) 2005 Nuno Antunes <nuno.antunes@gmail.com>
3169577Smav * Copyright (c) 2007 Alexander Motin <mav@freebsd.org>
4169577Smav * All rights reserved.
5169577Smav *
6169577Smav * Redistribution and use in source and binary forms, with or without
7169577Smav * modification, are permitted provided that the following conditions
8169577Smav * are met:
9169577Smav * 1. Redistributions of source code must retain the above copyright
10169577Smav *    notice, this list of conditions and the following disclaimer.
11169577Smav * 2. Redistributions in binary form must reproduce the above copyright
12169577Smav *    notice, this list of conditions and the following disclaimer in the
13169577Smav *    documentation and/or other materials provided with the distribution.
14169577Smav *
15169577Smav * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16169577Smav * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17169577Smav * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18169577Smav * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
19169577Smav * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20169577Smav * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21169577Smav * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22169577Smav * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23169577Smav * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24169577Smav * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25169577Smav * SUCH DAMAGE.
26169577Smav *
27169577Smav * $FreeBSD$
28169577Smav */
29169577Smav
30169577Smav/*
31169577Smav * ng_car - An implementation of commited access rate for netgraph
32169577Smav *
33169577Smav * TODO:
34169577Smav *	- Sanitize input config values (impose some limits)
35169577Smav *	- Implement internal packet painting (possibly using mbuf tags)
36169577Smav *	- Implement color-aware mode
37169577Smav *	- Implement DSCP marking for IPv4
38169577Smav */
39169577Smav
40169577Smav#include <sys/param.h>
41169602Smav#include <sys/errno.h>
42169577Smav#include <sys/kernel.h>
43169602Smav#include <sys/malloc.h>
44169577Smav#include <sys/mbuf.h>
45169577Smav
46169577Smav#include <netgraph/ng_message.h>
47169577Smav#include <netgraph/ng_parse.h>
48169577Smav#include <netgraph/netgraph.h>
49169577Smav#include <netgraph/ng_car.h>
50169577Smav
51169577Smav#define NG_CAR_QUEUE_SIZE	100	/* Maximum queue size for SHAPE mode */
52169577Smav#define NG_CAR_QUEUE_MIN_TH	8	/* Minimum RED threshhold for SHAPE mode */
53169577Smav
54169577Smav/* Hook private info */
55169577Smavstruct hookinfo {
56169577Smav	hook_p		hook;		/* this (source) hook */
57169577Smav	hook_p		dest;		/* destination hook */
58169577Smav
59169577Smav	int64_t 	tc;		/* commited token bucket counter */
60169602Smav	int64_t 	te;		/* exceeded/peak token bucket counter */
61177670Smav	struct bintime	lastRefill;	/* last token refill time */
62169577Smav
63169577Smav	struct ng_car_hookconf conf;	/* hook configuration */
64169577Smav	struct ng_car_hookstats stats;	/* hook stats */
65169577Smav
66169602Smav	struct mbuf	*q[NG_CAR_QUEUE_SIZE];	/* circular packet queue */
67177732Smav	u_int		q_first;	/* first queue element */
68177732Smav	u_int		q_last;		/* last queue element */
69169577Smav	struct callout	q_callout;	/* periodic queue processing routine */
70169577Smav	struct mtx	q_mtx;		/* queue mutex */
71169577Smav};
72169577Smav
73169577Smav/* Private information for each node instance */
74169577Smavstruct privdata {
75169577Smav	node_p node;				/* the node itself */
76169577Smav	struct hookinfo upper;			/* hook to upper layers */
77169577Smav	struct hookinfo lower;			/* hook to lower layers */
78169577Smav};
79169577Smavtypedef struct privdata *priv_p;
80169577Smav
81169577Smavstatic ng_constructor_t	ng_car_constructor;
82169577Smavstatic ng_rcvmsg_t	ng_car_rcvmsg;
83169577Smavstatic ng_shutdown_t	ng_car_shutdown;
84169577Smavstatic ng_newhook_t	ng_car_newhook;
85169577Smavstatic ng_rcvdata_t	ng_car_rcvdata;
86169577Smavstatic ng_disconnect_t	ng_car_disconnect;
87169577Smav
88169577Smavstatic void	ng_car_refillhook(struct hookinfo *h);
89169577Smavstatic void	ng_car_schedule(struct hookinfo *h);
90169577Smavvoid		ng_car_q_event(node_p node, hook_p hook, void *arg, int arg2);
91169577Smavstatic void	ng_car_enqueue(struct hookinfo *h, item_p item);
92169577Smav
93169577Smav/* Parse type for struct ng_car_hookstats */
94169577Smavstatic const struct ng_parse_struct_field ng_car_hookstats_type_fields[]
95169577Smav	= NG_CAR_HOOKSTATS;
96169577Smavstatic const struct ng_parse_type ng_car_hookstats_type = {
97169577Smav	&ng_parse_struct_type,
98169577Smav	&ng_car_hookstats_type_fields
99169577Smav};
100169577Smav
101169577Smav/* Parse type for struct ng_car_bulkstats */
102169577Smavstatic const struct ng_parse_struct_field ng_car_bulkstats_type_fields[]
103169577Smav	= NG_CAR_BULKSTATS(&ng_car_hookstats_type);
104169577Smavstatic const struct ng_parse_type ng_car_bulkstats_type = {
105169577Smav	&ng_parse_struct_type,
106169577Smav	&ng_car_bulkstats_type_fields
107169577Smav};
108169577Smav
109169577Smav/* Parse type for struct ng_car_hookconf */
110169577Smavstatic const struct ng_parse_struct_field ng_car_hookconf_type_fields[]
111169577Smav	= NG_CAR_HOOKCONF;
112169577Smavstatic const struct ng_parse_type ng_car_hookconf_type = {
113169577Smav	&ng_parse_struct_type,
114169577Smav	&ng_car_hookconf_type_fields
115169577Smav};
116169577Smav
117169577Smav/* Parse type for struct ng_car_bulkconf */
118169577Smavstatic const struct ng_parse_struct_field ng_car_bulkconf_type_fields[]
119169577Smav	= NG_CAR_BULKCONF(&ng_car_hookconf_type);
120169577Smavstatic const struct ng_parse_type ng_car_bulkconf_type = {
121169577Smav	&ng_parse_struct_type,
122169577Smav	&ng_car_bulkconf_type_fields
123169577Smav};
124169577Smav
125169577Smav/* Command list */
126169577Smavstatic struct ng_cmdlist ng_car_cmdlist[] = {
127169577Smav	{
128169577Smav	  NGM_CAR_COOKIE,
129169577Smav	  NGM_CAR_GET_STATS,
130169577Smav	  "getstats",
131169577Smav	  NULL,
132169577Smav	  &ng_car_bulkstats_type,
133169577Smav	},
134169577Smav	{
135169577Smav	  NGM_CAR_COOKIE,
136169577Smav	  NGM_CAR_CLR_STATS,
137169577Smav	  "clrstats",
138169577Smav	  NULL,
139169577Smav	  NULL,
140169577Smav	},
141169577Smav	{
142169577Smav	  NGM_CAR_COOKIE,
143169577Smav	  NGM_CAR_GETCLR_STATS,
144169577Smav	  "getclrstats",
145169577Smav	  NULL,
146169577Smav	  &ng_car_bulkstats_type,
147169577Smav	},
148169577Smav
149169577Smav	{
150169577Smav	  NGM_CAR_COOKIE,
151169577Smav	  NGM_CAR_GET_CONF,
152169577Smav	  "getconf",
153169577Smav	  NULL,
154169577Smav	  &ng_car_bulkconf_type,
155169577Smav	},
156169577Smav	{
157169577Smav	  NGM_CAR_COOKIE,
158169577Smav	  NGM_CAR_SET_CONF,
159169577Smav	  "setconf",
160169577Smav	  &ng_car_bulkconf_type,
161169577Smav	  NULL,
162169577Smav	},
163169577Smav	{ 0 }
164169577Smav};
165169577Smav
166169577Smav/* Netgraph node type descriptor */
167169577Smavstatic struct ng_type ng_car_typestruct = {
168169577Smav	.version =	NG_ABI_VERSION,
169169577Smav	.name =		NG_CAR_NODE_TYPE,
170169577Smav	.constructor =	ng_car_constructor,
171169577Smav	.rcvmsg =	ng_car_rcvmsg,
172169577Smav	.shutdown =	ng_car_shutdown,
173169577Smav	.newhook =	ng_car_newhook,
174169577Smav	.rcvdata =	ng_car_rcvdata,
175169577Smav	.disconnect =	ng_car_disconnect,
176169577Smav	.cmdlist =	ng_car_cmdlist,
177169577Smav};
178169577SmavNETGRAPH_INIT(car, &ng_car_typestruct);
179169577Smav
180169577Smav/*
181169577Smav * Node constructor
182169577Smav */
183169577Smavstatic int
184169577Smavng_car_constructor(node_p node)
185169577Smav{
186169577Smav	priv_p priv;
187169577Smav
188169602Smav	/* Initialize private descriptor. */
189220768Sglebius	priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO);
190169577Smav
191169577Smav	NG_NODE_SET_PRIVATE(node, priv);
192169577Smav	priv->node = node;
193169577Smav
194169577Smav	/*
195169577Smav	 * Arbitrary default values
196169577Smav	 */
197169577Smav
198169577Smav	priv->upper.hook = NULL;
199169577Smav	priv->upper.dest = NULL;
200169577Smav	priv->upper.tc = priv->upper.conf.cbs = NG_CAR_CBS_MIN;
201169577Smav	priv->upper.te = priv->upper.conf.ebs = NG_CAR_EBS_MIN;
202169577Smav	priv->upper.conf.cir = NG_CAR_CIR_DFLT;
203169577Smav	priv->upper.conf.green_action = NG_CAR_ACTION_FORWARD;
204169577Smav	priv->upper.conf.yellow_action = NG_CAR_ACTION_FORWARD;
205169577Smav	priv->upper.conf.red_action = NG_CAR_ACTION_DROP;
206169577Smav	priv->upper.conf.mode = 0;
207177670Smav	getbinuptime(&priv->upper.lastRefill);
208169577Smav	priv->upper.q_first = 0;
209169577Smav	priv->upper.q_last = 0;
210169577Smav	ng_callout_init(&priv->upper.q_callout);
211169577Smav	mtx_init(&priv->upper.q_mtx, "ng_car_u", NULL, MTX_DEF);
212169577Smav
213169577Smav	priv->lower.hook = NULL;
214169577Smav	priv->lower.dest = NULL;
215169577Smav	priv->lower.tc = priv->lower.conf.cbs = NG_CAR_CBS_MIN;
216169577Smav	priv->lower.te = priv->lower.conf.ebs = NG_CAR_EBS_MIN;
217169577Smav	priv->lower.conf.cir = NG_CAR_CIR_DFLT;
218169577Smav	priv->lower.conf.green_action = NG_CAR_ACTION_FORWARD;
219169577Smav	priv->lower.conf.yellow_action = NG_CAR_ACTION_FORWARD;
220169577Smav	priv->lower.conf.red_action = NG_CAR_ACTION_DROP;
221169577Smav	priv->lower.conf.mode = 0;
222169577Smav	priv->lower.lastRefill = priv->upper.lastRefill;
223169577Smav	priv->lower.q_first = 0;
224169577Smav	priv->lower.q_last = 0;
225169577Smav	ng_callout_init(&priv->lower.q_callout);
226169577Smav	mtx_init(&priv->lower.q_mtx, "ng_car_l", NULL, MTX_DEF);
227169577Smav
228169577Smav	return (0);
229169577Smav}
230169577Smav
231169577Smav/*
232169602Smav * Add a hook.
233169577Smav */
234169577Smavstatic int
235169577Smavng_car_newhook(node_p node, hook_p hook, const char *name)
236169577Smav{
237169577Smav	const priv_p priv = NG_NODE_PRIVATE(node);
238169577Smav
239169577Smav	if (strcmp(name, NG_CAR_HOOK_LOWER) == 0) {
240169577Smav		priv->lower.hook = hook;
241169577Smav		priv->upper.dest = hook;
242169577Smav		bzero(&priv->lower.stats, sizeof(priv->lower.stats));
243169577Smav		NG_HOOK_SET_PRIVATE(hook, &priv->lower);
244169577Smav	} else if (strcmp(name, NG_CAR_HOOK_UPPER) == 0) {
245169577Smav		priv->upper.hook = hook;
246169577Smav		priv->lower.dest = hook;
247169577Smav		bzero(&priv->upper.stats, sizeof(priv->upper.stats));
248169577Smav		NG_HOOK_SET_PRIVATE(hook, &priv->upper);
249169577Smav	} else
250169577Smav		return (EINVAL);
251169577Smav	return(0);
252169577Smav}
253169577Smav
254169577Smav/*
255169602Smav * Data has arrived.
256169577Smav */
257169577Smavstatic int
258169577Smavng_car_rcvdata(hook_p hook, item_p item )
259169577Smav{
260169577Smav	struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook);
261177732Smav	struct mbuf *m;
262169577Smav	int error = 0;
263177732Smav	u_int len;
264169577Smav
265169577Smav	/* If queue is not empty now then enqueue packet. */
266169577Smav	if (hinfo->q_first != hinfo->q_last) {
267169577Smav		ng_car_enqueue(hinfo, item);
268169577Smav		return (0);
269169577Smav	}
270169577Smav
271169577Smav	m = NGI_M(item);
272169577Smav
273169577Smav#define NG_CAR_PERFORM_MATCH_ACTION(a)			\
274169577Smav	do {						\
275169577Smav		switch (a) {				\
276169577Smav		case NG_CAR_ACTION_FORWARD:		\
277169602Smav			/* Do nothing. */		\
278169577Smav			break;				\
279169577Smav		case NG_CAR_ACTION_MARK:		\
280169577Smav			/* XXX find a way to mark packets (mbuf tag?) */ \
281169577Smav			++hinfo->stats.errors;		\
282169577Smav			break;				\
283169577Smav		case NG_CAR_ACTION_DROP:		\
284169577Smav		default:				\
285169602Smav			/* Drop packet and return. */	\
286169577Smav			NG_FREE_ITEM(item);		\
287169577Smav			++hinfo->stats.droped_pkts;	\
288169602Smav			return (0);			\
289169577Smav		}					\
290169602Smav	} while (0)
291169577Smav
292174795Smav	/* Packet is counted as 128 tokens for better resolution */
293174795Smav	if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) {
294174795Smav		len = 128;
295174795Smav	} else {
296174795Smav		len = m->m_pkthdr.len;
297174795Smav	}
298174795Smav
299169577Smav	/* Check commited token bucket. */
300174795Smav	if (hinfo->tc - len >= 0) {
301169602Smav		/* This packet is green. */
302169577Smav		++hinfo->stats.green_pkts;
303174795Smav		hinfo->tc -= len;
304169577Smav		NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.green_action);
305169602Smav	} else {
306169577Smav
307169602Smav		/* Refill only if not green without it. */
308169602Smav		ng_car_refillhook(hinfo);
309169577Smav
310169602Smav		 /* Check commited token bucket again after refill. */
311174795Smav		if (hinfo->tc - len >= 0) {
312169602Smav			/* This packet is green */
313169602Smav			++hinfo->stats.green_pkts;
314174795Smav			hinfo->tc -= len;
315169602Smav			NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.green_action);
316169577Smav
317169602Smav		/* If not green and mode is SHAPE, enqueue packet. */
318169602Smav		} else if (hinfo->conf.mode == NG_CAR_SHAPE) {
319169602Smav			ng_car_enqueue(hinfo, item);
320169602Smav			return (0);
321169577Smav
322169602Smav		/* If not green and mode is RED, calculate probability. */
323169602Smav		} else if (hinfo->conf.mode == NG_CAR_RED) {
324169602Smav			/* Is packet is bigger then extended burst? */
325174795Smav			if (len - (hinfo->tc - len) > hinfo->conf.ebs) {
326169602Smav				/* This packet is definitely red. */
327169602Smav				++hinfo->stats.red_pkts;
328169602Smav				hinfo->te = 0;
329169602Smav				NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action);
330169577Smav
331169602Smav			/* Use token bucket to simulate RED-like drop
332169602Smav			   probability. */
333174795Smav			} else if (hinfo->te + (len - hinfo->tc) <
334169602Smav			    hinfo->conf.ebs) {
335169602Smav				/* This packet is yellow */
336169602Smav				++hinfo->stats.yellow_pkts;
337174795Smav				hinfo->te += len - hinfo->tc;
338169602Smav				/* Go to negative tokens. */
339174795Smav				hinfo->tc -= len;
340169602Smav				NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.yellow_action);
341169602Smav			} else {
342169602Smav				/* This packet is probaly red. */
343169602Smav				++hinfo->stats.red_pkts;
344169602Smav				hinfo->te = 0;
345169602Smav				NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action);
346169602Smav			}
347169602Smav		/* If not green and mode is SINGLE/DOUBLE RATE. */
348169577Smav		} else {
349169602Smav			/* Check extended token bucket. */
350174795Smav			if (hinfo->te - len >= 0) {
351169602Smav				/* This packet is yellow */
352169602Smav				++hinfo->stats.yellow_pkts;
353174795Smav				hinfo->te -= len;
354169602Smav				NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.yellow_action);
355169602Smav			} else {
356169602Smav				/* This packet is red */
357169602Smav				++hinfo->stats.red_pkts;
358169602Smav				NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action);
359169602Smav			}
360169577Smav		}
361169577Smav	}
362169577Smav
363169577Smav#undef NG_CAR_PERFORM_MATCH_ACTION
364169577Smav
365177732Smav	NG_FWD_ITEM_HOOK(error, item, hinfo->dest);
366169577Smav	if (error != 0)
367169577Smav		++hinfo->stats.errors;
368169577Smav	++hinfo->stats.passed_pkts;
369169577Smav
370169602Smav	return (error);
371169577Smav}
372169577Smav
373169577Smav/*
374169602Smav * Receive a control message.
375169577Smav */
376169577Smavstatic int
377169577Smavng_car_rcvmsg(node_p node, item_p item, hook_p lasthook)
378169577Smav{
379169577Smav	const priv_p priv = NG_NODE_PRIVATE(node);
380169577Smav	struct ng_mesg *resp = NULL;
381169577Smav	int error = 0;
382169577Smav	struct ng_mesg *msg;
383169577Smav
384169577Smav	NGI_GET_MSG(item, msg);
385169577Smav	switch (msg->header.typecookie) {
386169577Smav	case NGM_CAR_COOKIE:
387169577Smav		switch (msg->header.cmd) {
388169577Smav		case NGM_CAR_GET_STATS:
389169577Smav		case NGM_CAR_GETCLR_STATS:
390169577Smav			{
391169577Smav				struct ng_car_bulkstats *bstats;
392169602Smav
393169577Smav				NG_MKRESPONSE(resp, msg,
394169577Smav					sizeof(*bstats), M_NOWAIT);
395169577Smav				if (resp == NULL) {
396169577Smav					error = ENOMEM;
397169577Smav					break;
398169577Smav				}
399169577Smav				bstats = (struct ng_car_bulkstats *)resp->data;
400169577Smav
401169577Smav				bcopy(&priv->upper.stats, &bstats->downstream,
402169577Smav				    sizeof(bstats->downstream));
403169577Smav				bcopy(&priv->lower.stats, &bstats->upstream,
404169577Smav				    sizeof(bstats->upstream));
405169577Smav			}
406169577Smav			if (msg->header.cmd == NGM_CAR_GET_STATS)
407169577Smav				break;
408169577Smav		case NGM_CAR_CLR_STATS:
409169577Smav			bzero(&priv->upper.stats,
410169577Smav				sizeof(priv->upper.stats));
411169577Smav			bzero(&priv->lower.stats,
412169577Smav				sizeof(priv->lower.stats));
413169577Smav			break;
414169577Smav		case NGM_CAR_GET_CONF:
415169577Smav			{
416169577Smav				struct ng_car_bulkconf *bconf;
417169602Smav
418169577Smav				NG_MKRESPONSE(resp, msg,
419169577Smav					sizeof(*bconf), M_NOWAIT);
420169577Smav				if (resp == NULL) {
421169577Smav					error = ENOMEM;
422169577Smav					break;
423169577Smav				}
424169577Smav				bconf = (struct ng_car_bulkconf *)resp->data;
425169577Smav
426169577Smav				bcopy(&priv->upper.conf, &bconf->downstream,
427169577Smav				    sizeof(bconf->downstream));
428169577Smav				bcopy(&priv->lower.conf, &bconf->upstream,
429169577Smav				    sizeof(bconf->upstream));
430174795Smav				/* Convert internal 1/(8*128) of pps into pps */
431174795Smav				if (bconf->downstream.opt & NG_CAR_COUNT_PACKETS) {
432174795Smav				    bconf->downstream.cir /= 1024;
433174795Smav				    bconf->downstream.pir /= 1024;
434174795Smav				    bconf->downstream.cbs /= 128;
435174795Smav				    bconf->downstream.ebs /= 128;
436174795Smav				}
437174795Smav				if (bconf->upstream.opt & NG_CAR_COUNT_PACKETS) {
438174795Smav				    bconf->upstream.cir /= 1024;
439174795Smav				    bconf->upstream.pir /= 1024;
440174795Smav				    bconf->upstream.cbs /= 128;
441174795Smav				    bconf->upstream.ebs /= 128;
442174795Smav				}
443169577Smav			}
444169577Smav			break;
445169577Smav		case NGM_CAR_SET_CONF:
446169577Smav			{
447169577Smav				struct ng_car_bulkconf *const bconf =
448169577Smav				(struct ng_car_bulkconf *)msg->data;
449169602Smav
450169577Smav				/* Check for invalid or illegal config. */
451174795Smav				if (msg->header.arglen != sizeof(*bconf)) {
452174795Smav					error = EINVAL;
453174795Smav					break;
454174795Smav				}
455174795Smav				/* Convert pps into internal 1/(8*128) of pps */
456174795Smav				if (bconf->downstream.opt & NG_CAR_COUNT_PACKETS) {
457174795Smav				    bconf->downstream.cir *= 1024;
458174795Smav				    bconf->downstream.pir *= 1024;
459174795Smav				    bconf->downstream.cbs *= 125;
460174795Smav				    bconf->downstream.ebs *= 125;
461174795Smav				}
462174795Smav				if (bconf->upstream.opt & NG_CAR_COUNT_PACKETS) {
463174795Smav				    bconf->upstream.cir *= 1024;
464174795Smav				    bconf->upstream.pir *= 1024;
465174795Smav				    bconf->upstream.cbs *= 125;
466174795Smav				    bconf->upstream.ebs *= 125;
467174795Smav				}
468174795Smav				if ((bconf->downstream.cir > 1000000000) ||
469174795Smav				    (bconf->downstream.pir > 1000000000) ||
470174795Smav				    (bconf->upstream.cir > 1000000000) ||
471174795Smav				    (bconf->upstream.pir > 1000000000) ||
472174795Smav				    (bconf->downstream.cbs == 0 &&
473174795Smav					bconf->downstream.ebs == 0) ||
474174795Smav				    (bconf->upstream.cbs == 0 &&
475174795Smav					bconf->upstream.ebs == 0))
476169577Smav				{
477169577Smav					error = EINVAL;
478169577Smav					break;
479169577Smav				}
480174795Smav				if ((bconf->upstream.mode == NG_CAR_SHAPE) &&
481174795Smav				    (bconf->upstream.cir == 0)) {
482174795Smav					error = EINVAL;
483174795Smav					break;
484174795Smav				}
485174795Smav				if ((bconf->downstream.mode == NG_CAR_SHAPE) &&
486174795Smav				    (bconf->downstream.cir == 0)) {
487174795Smav					error = EINVAL;
488174795Smav					break;
489174795Smav				}
490169577Smav
491169577Smav				/* Copy downstream config. */
492169577Smav				bcopy(&bconf->downstream, &priv->upper.conf,
493169577Smav				    sizeof(priv->upper.conf));
494169577Smav    				priv->upper.tc = priv->upper.conf.cbs;
495169577Smav				if (priv->upper.conf.mode == NG_CAR_RED ||
496169656Smav				    priv->upper.conf.mode == NG_CAR_SHAPE) {
497169577Smav					priv->upper.te = 0;
498169577Smav				} else {
499169577Smav					priv->upper.te = priv->upper.conf.ebs;
500169577Smav				}
501169577Smav
502169577Smav				/* Copy upstream config. */
503169577Smav				bcopy(&bconf->upstream, &priv->lower.conf,
504169577Smav				    sizeof(priv->lower.conf));
505169577Smav    				priv->lower.tc = priv->lower.conf.cbs;
506169577Smav				if (priv->lower.conf.mode == NG_CAR_RED ||
507169577Smav				    priv->lower.conf.mode == NG_CAR_SHAPE) {
508169577Smav					priv->lower.te = 0;
509169577Smav				} else {
510169577Smav					priv->lower.te = priv->lower.conf.ebs;
511169577Smav				}
512169577Smav			}
513169577Smav			break;
514169577Smav		default:
515169577Smav			error = EINVAL;
516169577Smav			break;
517169577Smav		}
518169577Smav		break;
519169577Smav	default:
520169577Smav		error = EINVAL;
521169577Smav		break;
522169577Smav	}
523169577Smav	NG_RESPOND_MSG(error, node, item, resp);
524169577Smav	NG_FREE_MSG(msg);
525169577Smav	return (error);
526169577Smav}
527169577Smav
528169577Smav/*
529169577Smav * Do local shutdown processing.
530169577Smav */
531169577Smavstatic int
532169577Smavng_car_shutdown(node_p node)
533169577Smav{
534169577Smav	const priv_p priv = NG_NODE_PRIVATE(node);
535169577Smav
536170661Smav	ng_uncallout(&priv->upper.q_callout, node);
537170661Smav	ng_uncallout(&priv->lower.q_callout, node);
538169577Smav	mtx_destroy(&priv->upper.q_mtx);
539169577Smav	mtx_destroy(&priv->lower.q_mtx);
540169577Smav	NG_NODE_UNREF(priv->node);
541169602Smav	free(priv, M_NETGRAPH);
542169577Smav	return (0);
543169577Smav}
544169577Smav
545169577Smav/*
546169577Smav * Hook disconnection.
547169577Smav *
548169577Smav * For this type, removal of the last link destroys the node.
549169577Smav */
550169577Smavstatic int
551169577Smavng_car_disconnect(hook_p hook)
552169577Smav{
553169577Smav	struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook);
554169577Smav	const node_p node = NG_HOOK_NODE(hook);
555169577Smav	const priv_p priv = NG_NODE_PRIVATE(node);
556169577Smav
557169577Smav	if (hinfo) {
558169577Smav		/* Purge queue if not empty. */
559169577Smav		while (hinfo->q_first != hinfo->q_last) {
560169577Smav			NG_FREE_M(hinfo->q[hinfo->q_first]);
561169577Smav			hinfo->q_first++;
562169577Smav			if (hinfo->q_first >= NG_CAR_QUEUE_SIZE)
563169577Smav		    		hinfo->q_first = 0;
564169577Smav		}
565169577Smav		/* Remove hook refs. */
566169602Smav		if (hinfo->hook == priv->upper.hook)
567169577Smav			priv->lower.dest = NULL;
568169602Smav		else
569169577Smav			priv->upper.dest = NULL;
570169577Smav		hinfo->hook = NULL;
571169577Smav	}
572169577Smav	/* Already shutting down? */
573169577Smav	if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0)
574169577Smav	    && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
575169577Smav		ng_rmnode_self(NG_HOOK_NODE(hook));
576169577Smav	return (0);
577169577Smav}
578169577Smav
579169577Smav/*
580169577Smav * Hook's token buckets refillment.
581169577Smav */
582169577Smavstatic void
583169577Smavng_car_refillhook(struct hookinfo *h)
584169577Smav{
585177670Smav	struct bintime newt, deltat;
586177670Smav	unsigned int deltat_us;
587169577Smav
588169577Smav	/* Get current time. */
589177670Smav	getbinuptime(&newt);
590169577Smav
591177670Smav	/* Get time delta since last refill. */
592177670Smav	deltat = newt;
593177670Smav	bintime_sub(&deltat, &h->lastRefill);
594177670Smav
595169577Smav	/* Time must go forward. */
596177670Smav	if (deltat.sec < 0) {
597169577Smav	    h->lastRefill = newt;
598169577Smav	    return;
599169577Smav	}
600169577Smav
601177670Smav	/* But not too far forward. */
602177670Smav	if (deltat.sec >= 1000) {
603177670Smav	    deltat_us = (1000 << 20);
604169577Smav	} else {
605177670Smav	    /* convert bintime to the 1/(2^20) of sec */
606177670Smav	    deltat_us = (deltat.sec << 20) + (deltat.frac >> 44);
607169577Smav	}
608169577Smav
609169577Smav	if (h->conf.mode == NG_CAR_SINGLE_RATE) {
610177670Smav		int64_t	delta;
611169577Smav		/* Refill commited token bucket. */
612177670Smav		h->tc += (h->conf.cir * deltat_us) >> 23;
613169577Smav		delta = h->tc - h->conf.cbs;
614169577Smav		if (delta > 0) {
615169577Smav			h->tc = h->conf.cbs;
616169577Smav
617169577Smav			/* Refill exceeded token bucket. */
618169577Smav			h->te += delta;
619177670Smav			if (h->te > ((int64_t)h->conf.ebs))
620169577Smav				h->te = h->conf.ebs;
621169577Smav		}
622169577Smav
623169577Smav	} else if (h->conf.mode == NG_CAR_DOUBLE_RATE) {
624169577Smav		/* Refill commited token bucket. */
625177670Smav		h->tc += (h->conf.cir * deltat_us) >> 23;
626177670Smav		if (h->tc > ((int64_t)h->conf.cbs))
627169577Smav			h->tc = h->conf.cbs;
628169577Smav
629169577Smav		/* Refill peak token bucket. */
630177670Smav		h->te += (h->conf.pir * deltat_us) >> 23;
631177670Smav		if (h->te > ((int64_t)h->conf.ebs))
632169577Smav			h->te = h->conf.ebs;
633169577Smav
634169577Smav	} else { /* RED or SHAPE mode. */
635169577Smav		/* Refill commited token bucket. */
636177670Smav		h->tc += (h->conf.cir * deltat_us) >> 23;
637169577Smav		if (h->tc > ((int64_t)h->conf.cbs))
638169577Smav			h->tc = h->conf.cbs;
639169577Smav	}
640169577Smav
641169577Smav	/* Remember this moment. */
642169577Smav	h->lastRefill = newt;
643169577Smav}
644169577Smav
645169577Smav/*
646169577Smav * Schedule callout when we will have required tokens.
647169577Smav */
648169577Smavstatic void
649169577Smavng_car_schedule(struct hookinfo *hinfo)
650169577Smav{
651169577Smav	int 	delay;
652169602Smav
653169577Smav	delay = (-(hinfo->tc)) * hz * 8 / hinfo->conf.cir + 1;
654169577Smav
655169602Smav	ng_callout(&hinfo->q_callout, NG_HOOK_NODE(hinfo->hook), hinfo->hook,
656169577Smav	    delay, &ng_car_q_event, NULL, 0);
657169577Smav}
658169577Smav
659169577Smav/*
660169577Smav * Queue processing callout handler.
661169577Smav */
662169577Smavvoid
663169577Smavng_car_q_event(node_p node, hook_p hook, void *arg, int arg2)
664169577Smav{
665169577Smav	struct hookinfo	*hinfo = NG_HOOK_PRIVATE(hook);
666169577Smav	struct mbuf 	*m;
667169577Smav	int		error;
668169577Smav
669169577Smav	/* Refill tokens for time we have slept. */
670169577Smav	ng_car_refillhook(hinfo);
671169577Smav
672177732Smav	/* If we have some tokens */
673177732Smav	while (hinfo->tc >= 0) {
674169577Smav
675177732Smav		/* Send packet. */
676177732Smav		m = hinfo->q[hinfo->q_first];
677177732Smav		NG_SEND_DATA_ONLY(error, hinfo->dest, m);
678177732Smav		if (error != 0)
679177732Smav			++hinfo->stats.errors;
680177732Smav		++hinfo->stats.passed_pkts;
681169577Smav
682177732Smav		/* Get next one. */
683177732Smav		hinfo->q_first++;
684177732Smav		if (hinfo->q_first >= NG_CAR_QUEUE_SIZE)
685177732Smav			hinfo->q_first = 0;
686169577Smav
687177732Smav		/* Stop if none left. */
688177732Smav		if (hinfo->q_first == hinfo->q_last)
689177732Smav			break;
690169577Smav
691177732Smav		/* If we have more packet, try it. */
692177732Smav		m = hinfo->q[hinfo->q_first];
693177732Smav		if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) {
694177732Smav			hinfo->tc -= 128;
695177732Smav		} else {
696177732Smav			hinfo->tc -= m->m_pkthdr.len;
697169577Smav		}
698169577Smav	}
699169577Smav
700169577Smav	/* If something left */
701169602Smav	if (hinfo->q_first != hinfo->q_last)
702169602Smav		/* Schedule queue processing. */
703169577Smav		ng_car_schedule(hinfo);
704169577Smav}
705169577Smav
706169577Smav/*
707169577Smav * Enqueue packet.
708169577Smav */
709169577Smavstatic void
710169577Smavng_car_enqueue(struct hookinfo *hinfo, item_p item)
711169577Smav{
712169577Smav	struct mbuf 	*m;
713169577Smav	int		len;
714169577Smav
715169577Smav	NGI_GET_M(item, m);
716169577Smav	NG_FREE_ITEM(item);
717169577Smav
718169602Smav	/* Lock queue mutex. */
719169577Smav	mtx_lock(&hinfo->q_mtx);
720169577Smav
721169602Smav	/* Calculate used queue length. */
722169577Smav	len = hinfo->q_last - hinfo->q_first;
723169577Smav	if (len < 0)
724169577Smav		len += NG_CAR_QUEUE_SIZE;
725169577Smav
726169602Smav	/* If queue is overflowed or we have no RED tokens. */
727169602Smav	if ((len >= (NG_CAR_QUEUE_SIZE - 1)) ||
728169577Smav	    (hinfo->te + len >= NG_CAR_QUEUE_SIZE)) {
729169602Smav		/* Drop packet. */
730169577Smav		++hinfo->stats.red_pkts;
731177732Smav		++hinfo->stats.droped_pkts;
732169577Smav		NG_FREE_M(m);
733169602Smav
734169577Smav		hinfo->te = 0;
735169577Smav	} else {
736169577Smav		/* This packet is yellow. */
737169577Smav		++hinfo->stats.yellow_pkts;
738169577Smav
739169577Smav		/* Enqueue packet. */
740169577Smav		hinfo->q[hinfo->q_last] = m;
741169577Smav		hinfo->q_last++;
742169577Smav		if (hinfo->q_last >= NG_CAR_QUEUE_SIZE)
743169577Smav			hinfo->q_last = 0;
744169577Smav
745169577Smav		/* Use RED tokens. */
746169577Smav		if (len > NG_CAR_QUEUE_MIN_TH)
747169577Smav			hinfo->te += len - NG_CAR_QUEUE_MIN_TH;
748169577Smav
749169602Smav		/* If this is a first packet in the queue. */
750169577Smav		if (len == 0) {
751174795Smav			if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) {
752174795Smav				hinfo->tc -= 128;
753174795Smav			} else {
754174795Smav				hinfo->tc -= m->m_pkthdr.len;
755174795Smav			}
756169577Smav
757169602Smav			/* Schedule queue processing. */
758169577Smav			ng_car_schedule(hinfo);
759169577Smav		}
760169577Smav	}
761169577Smav
762169602Smav	/* Unlock queue mutex. */
763169577Smav	mtx_unlock(&hinfo->q_mtx);
764169577Smav}
765