diff --git a/share/man/man4/ng_car.4 b/share/man/man4/ng_car.4 index cb1459fe6987..c42f955a106a 100644 --- a/share/man/man4/ng_car.4 +++ b/share/man/man4/ng_car.4 @@ -1,215 +1,218 @@ .\" Copyright (c) 2005 Nuno Antunes .\" Copyright (c) 2007 Alexander Motin .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" -.Dd November 13, 2012 +.Dd January 27, 2021 .Dt NG_CAR 4 .Os .Sh NAME .Nm ng_car .Nd Committed Access Rate netgraph node type .Sh SYNOPSIS .In netgraph/ng_car.h .Sh DESCRIPTION The .Nm car node type limits traffic flowing through it using: .Pp .Bl -bullet -compact .It Single rate three color marker as described in RFC 2697, .It Two rate three color marker as described in RFC 2698, .It RED-like rate limit algorithm used by Cisco, .It Traffic shaping with RED. .El .Sh HOOKS This node type supports the following hooks: .Bl -tag -width ".Va upper" .It Va upper Hook leading to upper layer protocols. .It Va lower Hook leading to lower layer protocols. .El .Pp Traffic flowing from .Va upper to .Va lower is considered .Sy downstream traffic. Traffic flowing from .Va lower to .Va upper is considered .Sy upstream traffic. .Sh MODES OF OPERATION Each hook can operate in one of the following modes: .Bl -tag -width foo .It Dv NG_CAR_SINGLE_RATE Single rate three color marker as described in RFC 2697. Committed burst packets are counted as green, extended burst packets are counted as yellow and exceeding packets are counted as red. Committed burst getting refilled with CIR (Committed Information Rate) speed. When it is full, exceeded burst getting refilled. .It Dv NG_CAR_DOUBLE_RATE Two rate three color marker as described in RFC 2698. Committed burst packets are counted as green, peak burst packets are counted as yellow and exceeding packets are counted as red. Committed burst getting refilled with CIR speed. Peak burst getting refilled with PIR (Peak Information Rate) speed at the same time. .It Dv NG_CAR_RED Similar to .Dv NG_CAR_SINGLE_RATE , but with different understanding of extended burst. When normal burst exceeded and extended burst is used, packets are counted red with probability equal to part of extended burst consumed. Extended burst getting refilled first. When it is full, committed burst getting refilled. This behavior is similar to RED active queue management algorithm. .Pp This algorithm is more polite to the TCP traffic than NG_CAR_SINGLE_RATE. .It Dv NG_CAR_SHAPE Committed burst packets are counted as green, exceeding packets are delayed by queue with RED management and counted as yellow. Packets dropped by queue counted as red. Queue parameters are hardcoded: length 99 packets, min_th 8 packets, max_p 100%. .Pp Traffic shaping is much more polite to the TCP traffic than rate limit on links with bandwidth * delay product less than 6-8 TCP segments, but it consumes additional system resources for queue processing. .El +.Pp By default, all information rates are measured in bits per second and bursts are measured in bytes. But when NG_CAR_COUNT_PACKETS option is enabled, rates are measured in packets per second and bursts are in packets. .Sh CONTROL MESSAGES This node type supports the generic control messages and the following specific messages. .Bl -tag -width foo .It Dv NGM_CAR_SET_CONF Pq Ic setconf Set node configuration to the specified at .Vt "struct ng_car_bulkconf" .It Dv NGM_CAR_GET_CONF Pq Ic getconf Return current node configuration as .Vt "struct ng_car_bulkconf" .Bd -literal struct ng_car_hookconf { uint64_t cbs; /* Committed burst size (bytes) */ uint64_t ebs; /* Exceeded/Peak burst size (bytes) */ uint64_t cir; /* Committed information rate (bits/s) */ uint64_t pir; /* Peak information rate (bits/s) */ uint8_t green_action; /* Action for green packets */ uint8_t yellow_action; /* Action for yellow packets */ uint8_t red_action; /* Action for red packets */ uint8_t mode; /* single/double rate, ... */ uint8_t opt; /* color-aware or color-blind */ }; /* possible actions (..._action) */ enum { NG_CAR_ACTION_FORWARD = 1, - NG_CAR_ACTION_DROP + NG_CAR_ACTION_DROP, + NG_CAR_ACTION_MARK }; /* operation modes (mode) */ enum { NG_CAR_SINGLE_RATE = 0, NG_CAR_DOUBLE_RATE, NG_CAR_RED, NG_CAR_SHAPE }; -/* mode options (opt) */ +/* mode options (bits for opt) */ +#define NG_CAR_COLOR_AWARE 1 #define NG_CAR_COUNT_PACKETS 2 struct ng_car_bulkconf { struct ng_car_hookconf upstream; struct ng_car_hookconf downstream; }; .Ed .It Dv NGM_CAR_GET_STATS Pq Ic getstats Return node statistics as .Vt "struct ng_car_bulkstats" .Bd -literal struct ng_car_hookstats { uint64_t passed_pkts; /* Counter for passed packets */ uint64_t droped_pkts; /* Counter for dropped packets */ uint64_t green_pkts; /* Counter for green packets */ uint64_t yellow_pkts; /* Counter for yellow packets */ uint64_t red_pkts; /* Counter for red packets */ uint64_t errors; /* Counter for operation errors */ }; struct ng_car_bulkstats { struct ng_car_hookstats upstream; struct ng_car_hookstats downstream; }; .Ed .It Dv NGM_CAR_CLR_STATS Pq Ic clrstats Clear node statistics. .It Dv NGM_CAR_GETCLR_STATS Pq Ic getclrstats Atomically return and clear node statistics. .El .Sh SHUTDOWN This node shuts down upon receipt of a .Dv NGM_SHUTDOWN control message, or when all hooks have been disconnected. .Sh EXAMPLES Limit outgoing data rate over fxp0 Ethernet interface to 20Mbit/s and incoming packet rate to 5000pps. .Bd -literal -offset indent /usr/sbin/ngctl -f- <<-SEQ mkpeer fxp0: car lower lower name fxp0:lower fxp0_car connect fxp0: fxp0_car: upper upper msg fxp0_car: setconf { downstream={ cir=20000000 cbs=2500000 ebs=2500000 greenAction=1 yellowAction=1 redAction=2 mode=2 } upstream={ cir=5000 cbs=100 ebs=100 greenAction=1 yellowAction=1 redAction=2 mode=2 opt=2 } } SEQ .Ed .Sh SEE ALSO .Xr netgraph 4 , .Xr ngctl 8 .Rs .%A J. Heinanen .%T "A Single Rate Three Color Marker" .%O RFC 2697 .Re .Rs .%A J. Heinanen .%T "A Two Rate Three Color Marker" .%O RFC 2698 .Re .Sh AUTHORS .An Nuno Antunes Aq Mt nuno.antunes@gmail.com .An Alexander Motin Aq Mt mav@FreeBSD.org .Sh BUGS At this moment only DROP and FORWARD actions are implemented. diff --git a/sys/netgraph/ng_car.c b/sys/netgraph/ng_car.c index cdbaf5a3c920..3705186f26b0 100644 --- a/sys/netgraph/ng_car.c +++ b/sys/netgraph/ng_car.c @@ -1,764 +1,808 @@ /*- * Copyright (c) 2005 Nuno Antunes * Copyright (c) 2007 Alexander Motin + * Copyright (c) 2019 Lutz Donnerhacke * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * ng_car - An implementation of committed access rate for netgraph * * TODO: * - Sanitize input config values (impose some limits) - * - Implement internal packet painting (possibly using mbuf tags) - * - Implement color-aware mode * - Implement DSCP marking for IPv4 + * - Decouple functionality into a simple classifier (g/y/r) + * and various action nodes (i.e. shape, dcsp, pcp) */ #include #include #include #include #include #include #include #include #include +#include "qos.h" + #define NG_CAR_QUEUE_SIZE 100 /* Maximum queue size for SHAPE mode */ #define NG_CAR_QUEUE_MIN_TH 8 /* Minimum RED threshold for SHAPE mode */ /* Hook private info */ struct hookinfo { hook_p hook; /* this (source) hook */ hook_p dest; /* destination hook */ int64_t tc; /* committed token bucket counter */ int64_t te; /* exceeded/peak token bucket counter */ struct bintime lastRefill; /* last token refill time */ struct ng_car_hookconf conf; /* hook configuration */ struct ng_car_hookstats stats; /* hook stats */ struct mbuf *q[NG_CAR_QUEUE_SIZE]; /* circular packet queue */ u_int q_first; /* first queue element */ u_int q_last; /* last queue element */ struct callout q_callout; /* periodic queue processing routine */ struct mtx q_mtx; /* queue mutex */ }; /* Private information for each node instance */ struct privdata { node_p node; /* the node itself */ struct hookinfo upper; /* hook to upper layers */ struct hookinfo lower; /* hook to lower layers */ }; typedef struct privdata *priv_p; static ng_constructor_t ng_car_constructor; static ng_rcvmsg_t ng_car_rcvmsg; static ng_shutdown_t ng_car_shutdown; static ng_newhook_t ng_car_newhook; static ng_rcvdata_t ng_car_rcvdata; static ng_disconnect_t ng_car_disconnect; static void ng_car_refillhook(struct hookinfo *h); static void ng_car_schedule(struct hookinfo *h); void ng_car_q_event(node_p node, hook_p hook, void *arg, int arg2); static void ng_car_enqueue(struct hookinfo *h, item_p item); /* Parse type for struct ng_car_hookstats */ static const struct ng_parse_struct_field ng_car_hookstats_type_fields[] = NG_CAR_HOOKSTATS; static const struct ng_parse_type ng_car_hookstats_type = { &ng_parse_struct_type, &ng_car_hookstats_type_fields }; /* Parse type for struct ng_car_bulkstats */ static const struct ng_parse_struct_field ng_car_bulkstats_type_fields[] = NG_CAR_BULKSTATS(&ng_car_hookstats_type); static const struct ng_parse_type ng_car_bulkstats_type = { &ng_parse_struct_type, &ng_car_bulkstats_type_fields }; /* Parse type for struct ng_car_hookconf */ static const struct ng_parse_struct_field ng_car_hookconf_type_fields[] = NG_CAR_HOOKCONF; static const struct ng_parse_type ng_car_hookconf_type = { &ng_parse_struct_type, &ng_car_hookconf_type_fields }; /* Parse type for struct ng_car_bulkconf */ static const struct ng_parse_struct_field ng_car_bulkconf_type_fields[] = NG_CAR_BULKCONF(&ng_car_hookconf_type); static const struct ng_parse_type ng_car_bulkconf_type = { &ng_parse_struct_type, &ng_car_bulkconf_type_fields }; /* Command list */ static struct ng_cmdlist ng_car_cmdlist[] = { { NGM_CAR_COOKIE, NGM_CAR_GET_STATS, "getstats", NULL, &ng_car_bulkstats_type, }, { NGM_CAR_COOKIE, NGM_CAR_CLR_STATS, "clrstats", NULL, NULL, }, { NGM_CAR_COOKIE, NGM_CAR_GETCLR_STATS, "getclrstats", NULL, &ng_car_bulkstats_type, }, { NGM_CAR_COOKIE, NGM_CAR_GET_CONF, "getconf", NULL, &ng_car_bulkconf_type, }, { NGM_CAR_COOKIE, NGM_CAR_SET_CONF, "setconf", &ng_car_bulkconf_type, NULL, }, { 0 } }; /* Netgraph node type descriptor */ static struct ng_type ng_car_typestruct = { .version = NG_ABI_VERSION, .name = NG_CAR_NODE_TYPE, .constructor = ng_car_constructor, .rcvmsg = ng_car_rcvmsg, .shutdown = ng_car_shutdown, .newhook = ng_car_newhook, .rcvdata = ng_car_rcvdata, .disconnect = ng_car_disconnect, .cmdlist = ng_car_cmdlist, }; NETGRAPH_INIT(car, &ng_car_typestruct); /* * Node constructor */ static int ng_car_constructor(node_p node) { priv_p priv; /* Initialize private descriptor. */ priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* * Arbitrary default values */ priv->upper.hook = NULL; priv->upper.dest = NULL; priv->upper.tc = priv->upper.conf.cbs = NG_CAR_CBS_MIN; priv->upper.te = priv->upper.conf.ebs = NG_CAR_EBS_MIN; priv->upper.conf.cir = NG_CAR_CIR_DFLT; priv->upper.conf.green_action = NG_CAR_ACTION_FORWARD; priv->upper.conf.yellow_action = NG_CAR_ACTION_FORWARD; priv->upper.conf.red_action = NG_CAR_ACTION_DROP; priv->upper.conf.mode = 0; getbinuptime(&priv->upper.lastRefill); priv->upper.q_first = 0; priv->upper.q_last = 0; ng_callout_init(&priv->upper.q_callout); mtx_init(&priv->upper.q_mtx, "ng_car_u", NULL, MTX_DEF); priv->lower.hook = NULL; priv->lower.dest = NULL; priv->lower.tc = priv->lower.conf.cbs = NG_CAR_CBS_MIN; priv->lower.te = priv->lower.conf.ebs = NG_CAR_EBS_MIN; priv->lower.conf.cir = NG_CAR_CIR_DFLT; priv->lower.conf.green_action = NG_CAR_ACTION_FORWARD; priv->lower.conf.yellow_action = NG_CAR_ACTION_FORWARD; priv->lower.conf.red_action = NG_CAR_ACTION_DROP; priv->lower.conf.mode = 0; priv->lower.lastRefill = priv->upper.lastRefill; priv->lower.q_first = 0; priv->lower.q_last = 0; ng_callout_init(&priv->lower.q_callout); mtx_init(&priv->lower.q_mtx, "ng_car_l", NULL, MTX_DEF); return (0); } /* * Add a hook. */ static int ng_car_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); if (strcmp(name, NG_CAR_HOOK_LOWER) == 0) { priv->lower.hook = hook; priv->upper.dest = hook; bzero(&priv->lower.stats, sizeof(priv->lower.stats)); NG_HOOK_SET_PRIVATE(hook, &priv->lower); } else if (strcmp(name, NG_CAR_HOOK_UPPER) == 0) { priv->upper.hook = hook; priv->lower.dest = hook; bzero(&priv->upper.stats, sizeof(priv->upper.stats)); NG_HOOK_SET_PRIVATE(hook, &priv->upper); } else return (EINVAL); return(0); } /* * Data has arrived. */ static int ng_car_rcvdata(hook_p hook, item_p item ) { struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook); struct mbuf *m; + struct m_qos_color *colp; + enum qos_color col; int error = 0; u_int len; /* If queue is not empty now then enqueue packet. */ if (hinfo->q_first != hinfo->q_last) { ng_car_enqueue(hinfo, item); return (0); } m = NGI_M(item); -#define NG_CAR_PERFORM_MATCH_ACTION(a) \ +#define NG_CAR_PERFORM_MATCH_ACTION(a,col) \ do { \ switch (a) { \ case NG_CAR_ACTION_FORWARD: \ /* Do nothing. */ \ break; \ case NG_CAR_ACTION_MARK: \ - /* XXX find a way to mark packets (mbuf tag?) */ \ - ++hinfo->stats.errors; \ + if (colp == NULL) { \ + colp = (void *)m_tag_alloc( \ + M_QOS_COOKIE, M_QOS_COLOR, \ + MTAG_SIZE(m_qos_color), M_NOWAIT); \ + if (colp != NULL) \ + m_tag_prepend(m, &colp->tag); \ + } \ + if (colp != NULL) \ + colp->color = col; \ break; \ case NG_CAR_ACTION_DROP: \ default: \ /* Drop packet and return. */ \ NG_FREE_ITEM(item); \ ++hinfo->stats.droped_pkts; \ return (0); \ } \ } while (0) /* Packet is counted as 128 tokens for better resolution */ if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { len = 128; } else { len = m->m_pkthdr.len; } + /* Determine current color of the packet (default green) */ + colp = (void *)m_tag_locate(m, M_QOS_COOKIE, M_QOS_COLOR, NULL); + if ((hinfo->conf.opt & NG_CAR_COLOR_AWARE) && (colp != NULL)) + col = colp->color; + else + col = QOS_COLOR_GREEN; + /* Check committed token bucket. */ - if (hinfo->tc - len >= 0) { + if (hinfo->tc - len >= 0 && col <= QOS_COLOR_GREEN) { /* This packet is green. */ ++hinfo->stats.green_pkts; hinfo->tc -= len; - NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.green_action); + NG_CAR_PERFORM_MATCH_ACTION( + hinfo->conf.green_action, + QOS_COLOR_GREEN); } else { /* Refill only if not green without it. */ ng_car_refillhook(hinfo); /* Check committed token bucket again after refill. */ - if (hinfo->tc - len >= 0) { + if (hinfo->tc - len >= 0 && col <= QOS_COLOR_GREEN) { /* This packet is green */ ++hinfo->stats.green_pkts; hinfo->tc -= len; - NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.green_action); + NG_CAR_PERFORM_MATCH_ACTION( + hinfo->conf.green_action, + QOS_COLOR_GREEN); /* If not green and mode is SHAPE, enqueue packet. */ } else if (hinfo->conf.mode == NG_CAR_SHAPE) { ng_car_enqueue(hinfo, item); return (0); /* If not green and mode is RED, calculate probability. */ } else if (hinfo->conf.mode == NG_CAR_RED) { /* Is packet is bigger then extended burst? */ - if (len - (hinfo->tc - len) > hinfo->conf.ebs) { + if (len - (hinfo->tc - len) > hinfo->conf.ebs || + col >= QOS_COLOR_RED) { /* This packet is definitely red. */ ++hinfo->stats.red_pkts; hinfo->te = 0; - NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action); + NG_CAR_PERFORM_MATCH_ACTION( + hinfo->conf.red_action, + QOS_COLOR_RED); /* Use token bucket to simulate RED-like drop probability. */ - } else if (hinfo->te + (len - hinfo->tc) < - hinfo->conf.ebs) { + } else if (hinfo->te + (len - hinfo->tc) < hinfo->conf.ebs && + col <= QOS_COLOR_YELLOW) { /* This packet is yellow */ ++hinfo->stats.yellow_pkts; hinfo->te += len - hinfo->tc; /* Go to negative tokens. */ hinfo->tc -= len; - NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.yellow_action); + NG_CAR_PERFORM_MATCH_ACTION( + hinfo->conf.yellow_action, + QOS_COLOR_YELLOW); } else { /* This packet is probably red. */ ++hinfo->stats.red_pkts; hinfo->te = 0; - NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action); + NG_CAR_PERFORM_MATCH_ACTION( + hinfo->conf.red_action, + QOS_COLOR_RED); } /* If not green and mode is SINGLE/DOUBLE RATE. */ } else { /* Check extended token bucket. */ - if (hinfo->te - len >= 0) { + if (hinfo->te - len >= 0 && col <= QOS_COLOR_YELLOW) { /* This packet is yellow */ ++hinfo->stats.yellow_pkts; hinfo->te -= len; - NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.yellow_action); + NG_CAR_PERFORM_MATCH_ACTION( + hinfo->conf.yellow_action, + QOS_COLOR_YELLOW); } else { /* This packet is red */ ++hinfo->stats.red_pkts; - NG_CAR_PERFORM_MATCH_ACTION(hinfo->conf.red_action); + NG_CAR_PERFORM_MATCH_ACTION( + hinfo->conf.red_action, + QOS_COLOR_RED); } } } #undef NG_CAR_PERFORM_MATCH_ACTION NG_FWD_ITEM_HOOK(error, item, hinfo->dest); if (error != 0) ++hinfo->stats.errors; ++hinfo->stats.passed_pkts; return (error); } /* * Receive a control message. */ static int ng_car_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_CAR_COOKIE: switch (msg->header.cmd) { case NGM_CAR_GET_STATS: case NGM_CAR_GETCLR_STATS: { struct ng_car_bulkstats *bstats; NG_MKRESPONSE(resp, msg, sizeof(*bstats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bstats = (struct ng_car_bulkstats *)resp->data; bcopy(&priv->upper.stats, &bstats->downstream, sizeof(bstats->downstream)); bcopy(&priv->lower.stats, &bstats->upstream, sizeof(bstats->upstream)); } if (msg->header.cmd == NGM_CAR_GET_STATS) break; case NGM_CAR_CLR_STATS: bzero(&priv->upper.stats, sizeof(priv->upper.stats)); bzero(&priv->lower.stats, sizeof(priv->lower.stats)); break; case NGM_CAR_GET_CONF: { struct ng_car_bulkconf *bconf; NG_MKRESPONSE(resp, msg, sizeof(*bconf), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bconf = (struct ng_car_bulkconf *)resp->data; bcopy(&priv->upper.conf, &bconf->downstream, sizeof(bconf->downstream)); bcopy(&priv->lower.conf, &bconf->upstream, sizeof(bconf->upstream)); /* Convert internal 1/(8*128) of pps into pps */ if (bconf->downstream.opt & NG_CAR_COUNT_PACKETS) { bconf->downstream.cir /= 1024; bconf->downstream.pir /= 1024; bconf->downstream.cbs /= 128; bconf->downstream.ebs /= 128; } if (bconf->upstream.opt & NG_CAR_COUNT_PACKETS) { bconf->upstream.cir /= 1024; bconf->upstream.pir /= 1024; bconf->upstream.cbs /= 128; bconf->upstream.ebs /= 128; } } break; case NGM_CAR_SET_CONF: { struct ng_car_bulkconf *const bconf = (struct ng_car_bulkconf *)msg->data; /* Check for invalid or illegal config. */ if (msg->header.arglen != sizeof(*bconf)) { error = EINVAL; break; } /* Convert pps into internal 1/(8*128) of pps */ if (bconf->downstream.opt & NG_CAR_COUNT_PACKETS) { bconf->downstream.cir *= 1024; bconf->downstream.pir *= 1024; bconf->downstream.cbs *= 125; bconf->downstream.ebs *= 125; } if (bconf->upstream.opt & NG_CAR_COUNT_PACKETS) { bconf->upstream.cir *= 1024; bconf->upstream.pir *= 1024; bconf->upstream.cbs *= 125; bconf->upstream.ebs *= 125; } if ((bconf->downstream.cir > 1000000000) || (bconf->downstream.pir > 1000000000) || (bconf->upstream.cir > 1000000000) || (bconf->upstream.pir > 1000000000) || (bconf->downstream.cbs == 0 && bconf->downstream.ebs == 0) || (bconf->upstream.cbs == 0 && bconf->upstream.ebs == 0)) { error = EINVAL; break; } if ((bconf->upstream.mode == NG_CAR_SHAPE) && (bconf->upstream.cir == 0)) { error = EINVAL; break; } if ((bconf->downstream.mode == NG_CAR_SHAPE) && (bconf->downstream.cir == 0)) { error = EINVAL; break; } /* Copy downstream config. */ bcopy(&bconf->downstream, &priv->upper.conf, sizeof(priv->upper.conf)); priv->upper.tc = priv->upper.conf.cbs; if (priv->upper.conf.mode == NG_CAR_RED || priv->upper.conf.mode == NG_CAR_SHAPE) { priv->upper.te = 0; } else { priv->upper.te = priv->upper.conf.ebs; } /* Copy upstream config. */ bcopy(&bconf->upstream, &priv->lower.conf, sizeof(priv->lower.conf)); priv->lower.tc = priv->lower.conf.cbs; if (priv->lower.conf.mode == NG_CAR_RED || priv->lower.conf.mode == NG_CAR_SHAPE) { priv->lower.te = 0; } else { priv->lower.te = priv->lower.conf.ebs; } } break; default: error = EINVAL; break; } break; default: error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Do local shutdown processing. */ static int ng_car_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); ng_uncallout(&priv->upper.q_callout, node); ng_uncallout(&priv->lower.q_callout, node); mtx_destroy(&priv->upper.q_mtx); mtx_destroy(&priv->lower.q_mtx); NG_NODE_UNREF(priv->node); free(priv, M_NETGRAPH); return (0); } /* * Hook disconnection. * * For this type, removal of the last link destroys the node. */ static int ng_car_disconnect(hook_p hook) { struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook); const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (hinfo) { /* Purge queue if not empty. */ while (hinfo->q_first != hinfo->q_last) { NG_FREE_M(hinfo->q[hinfo->q_first]); hinfo->q_first++; if (hinfo->q_first >= NG_CAR_QUEUE_SIZE) hinfo->q_first = 0; } /* Remove hook refs. */ if (hinfo->hook == priv->upper.hook) priv->lower.dest = NULL; else priv->upper.dest = NULL; hinfo->hook = NULL; } /* Already shutting down? */ if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } /* * Hook's token buckets refillment. */ static void ng_car_refillhook(struct hookinfo *h) { struct bintime newt, deltat; unsigned int deltat_us; /* Get current time. */ getbinuptime(&newt); /* Get time delta since last refill. */ deltat = newt; bintime_sub(&deltat, &h->lastRefill); /* Time must go forward. */ if (deltat.sec < 0) { h->lastRefill = newt; return; } /* But not too far forward. */ if (deltat.sec >= 1000) { deltat_us = (1000 << 20); } else { /* convert bintime to the 1/(2^20) of sec */ deltat_us = (deltat.sec << 20) + (deltat.frac >> 44); } if (h->conf.mode == NG_CAR_SINGLE_RATE) { int64_t delta; /* Refill committed token bucket. */ h->tc += (h->conf.cir * deltat_us) >> 23; delta = h->tc - h->conf.cbs; if (delta > 0) { h->tc = h->conf.cbs; /* Refill exceeded token bucket. */ h->te += delta; if (h->te > ((int64_t)h->conf.ebs)) h->te = h->conf.ebs; } } else if (h->conf.mode == NG_CAR_DOUBLE_RATE) { /* Refill committed token bucket. */ h->tc += (h->conf.cir * deltat_us) >> 23; if (h->tc > ((int64_t)h->conf.cbs)) h->tc = h->conf.cbs; /* Refill peak token bucket. */ h->te += (h->conf.pir * deltat_us) >> 23; if (h->te > ((int64_t)h->conf.ebs)) h->te = h->conf.ebs; } else { /* RED or SHAPE mode. */ /* Refill committed token bucket. */ h->tc += (h->conf.cir * deltat_us) >> 23; if (h->tc > ((int64_t)h->conf.cbs)) h->tc = h->conf.cbs; } /* Remember this moment. */ h->lastRefill = newt; } /* * Schedule callout when we will have required tokens. */ static void ng_car_schedule(struct hookinfo *hinfo) { int delay; delay = (-(hinfo->tc)) * hz * 8 / hinfo->conf.cir + 1; ng_callout(&hinfo->q_callout, NG_HOOK_NODE(hinfo->hook), hinfo->hook, delay, &ng_car_q_event, NULL, 0); } /* * Queue processing callout handler. */ void ng_car_q_event(node_p node, hook_p hook, void *arg, int arg2) { struct hookinfo *hinfo = NG_HOOK_PRIVATE(hook); struct mbuf *m; int error; /* Refill tokens for time we have slept. */ ng_car_refillhook(hinfo); /* If we have some tokens */ while (hinfo->tc >= 0) { /* Send packet. */ m = hinfo->q[hinfo->q_first]; NG_SEND_DATA_ONLY(error, hinfo->dest, m); if (error != 0) ++hinfo->stats.errors; ++hinfo->stats.passed_pkts; /* Get next one. */ hinfo->q_first++; if (hinfo->q_first >= NG_CAR_QUEUE_SIZE) hinfo->q_first = 0; /* Stop if none left. */ if (hinfo->q_first == hinfo->q_last) break; /* If we have more packet, try it. */ m = hinfo->q[hinfo->q_first]; if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { hinfo->tc -= 128; } else { hinfo->tc -= m->m_pkthdr.len; } } /* If something left */ if (hinfo->q_first != hinfo->q_last) /* Schedule queue processing. */ ng_car_schedule(hinfo); } /* * Enqueue packet. */ static void ng_car_enqueue(struct hookinfo *hinfo, item_p item) { - struct mbuf *m; - int len; + struct mbuf *m; + int len; + struct m_qos_color *colp; + enum qos_color col; NGI_GET_M(item, m); NG_FREE_ITEM(item); + /* Determine current color of the packet (default green) */ + colp = (void *)m_tag_locate(m, M_QOS_COOKIE, M_QOS_COLOR, NULL); + if ((hinfo->conf.opt & NG_CAR_COLOR_AWARE) && (colp != NULL)) + col = colp->color; + else + col = QOS_COLOR_GREEN; + /* Lock queue mutex. */ mtx_lock(&hinfo->q_mtx); /* Calculate used queue length. */ len = hinfo->q_last - hinfo->q_first; if (len < 0) len += NG_CAR_QUEUE_SIZE; /* If queue is overflowed or we have no RED tokens. */ if ((len >= (NG_CAR_QUEUE_SIZE - 1)) || - (hinfo->te + len >= NG_CAR_QUEUE_SIZE)) { + (hinfo->te + len >= NG_CAR_QUEUE_SIZE) || + (col >= QOS_COLOR_RED)) { /* Drop packet. */ ++hinfo->stats.red_pkts; ++hinfo->stats.droped_pkts; NG_FREE_M(m); hinfo->te = 0; } else { /* This packet is yellow. */ ++hinfo->stats.yellow_pkts; /* Enqueue packet. */ hinfo->q[hinfo->q_last] = m; hinfo->q_last++; if (hinfo->q_last >= NG_CAR_QUEUE_SIZE) hinfo->q_last = 0; /* Use RED tokens. */ if (len > NG_CAR_QUEUE_MIN_TH) hinfo->te += len - NG_CAR_QUEUE_MIN_TH; /* If this is a first packet in the queue. */ if (len == 0) { if (hinfo->conf.opt & NG_CAR_COUNT_PACKETS) { hinfo->tc -= 128; } else { hinfo->tc -= m->m_pkthdr.len; } /* Schedule queue processing. */ ng_car_schedule(hinfo); } } /* Unlock queue mutex. */ mtx_unlock(&hinfo->q_mtx); } diff --git a/sys/netgraph/ng_car.h b/sys/netgraph/ng_car.h index 3fda67512dd0..09a91df0da87 100644 --- a/sys/netgraph/ng_car.h +++ b/sys/netgraph/ng_car.h @@ -1,140 +1,139 @@ /*- * Copyright (c) 2005 Nuno Antunes * Copyright (c) 2007 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _NETGRAPH_NG_CAR_H_ #define _NETGRAPH_NG_CAR_H_ #define NG_CAR_NODE_TYPE "car" #define NGM_CAR_COOKIE 1173648034 /* Hook names */ #define NG_CAR_HOOK_UPPER "upper" #define NG_CAR_HOOK_LOWER "lower" /* Per hook statistics counters */ struct ng_car_hookstats { u_int64_t passed_pkts; /* Counter for passed packets */ u_int64_t droped_pkts; /* Counter for droped packets */ u_int64_t green_pkts; /* Counter for green packets */ u_int64_t yellow_pkts; /* Counter for yellow packets */ u_int64_t red_pkts; /* Counter for red packets */ u_int64_t errors; /* Counter for operation errors */ }; #define NG_CAR_HOOKSTATS { \ { "passed", &ng_parse_uint64_type }, \ { "droped", &ng_parse_uint64_type }, \ { "green", &ng_parse_uint64_type }, \ { "yellow", &ng_parse_uint64_type }, \ { "red", &ng_parse_uint64_type }, \ { "errors", &ng_parse_uint64_type }, \ { NULL } \ } /* Bulk statistics */ struct ng_car_bulkstats { struct ng_car_hookstats upstream; struct ng_car_hookstats downstream; }; #define NG_CAR_BULKSTATS(hstatstype) { \ { "upstream", (hstatstype) }, \ { "downstream", (hstatstype) }, \ { NULL } \ } /* Per hook configuration */ struct ng_car_hookconf { u_int64_t cbs; /* Committed burst size (bytes) */ u_int64_t ebs; /* Exceeded/Peak burst size (bytes) */ u_int64_t cir; /* Committed information rate (bits/s) */ u_int64_t pir; /* Peak information rate (bits/s) */ u_int8_t green_action; /* Action for green packets */ u_int8_t yellow_action; /* Action for yellow packets */ u_int8_t red_action; /* Action for red packets */ u_int8_t mode; /* single/double rate, ... */ u_int8_t opt; /* color-aware or color-blind */ }; /* Keep this definition in sync with the above structure */ #define NG_CAR_HOOKCONF { \ { "cbs", &ng_parse_uint64_type }, \ { "ebs", &ng_parse_uint64_type }, \ { "cir", &ng_parse_uint64_type }, \ { "pir", &ng_parse_uint64_type }, \ { "greenAction", &ng_parse_uint8_type }, \ { "yellowAction", &ng_parse_uint8_type }, \ { "redAction", &ng_parse_uint8_type }, \ { "mode", &ng_parse_uint8_type }, \ { "opt", &ng_parse_uint8_type }, \ { NULL } \ } #define NG_CAR_CBS_MIN 8192 #define NG_CAR_EBS_MIN 8192 #define NG_CAR_CIR_DFLT 10240 /* possible actions (...Action) */ enum { NG_CAR_ACTION_FORWARD = 1, NG_CAR_ACTION_DROP, - NG_CAR_ACTION_MARK, - NG_CAR_ACTION_SET_TOS + NG_CAR_ACTION_MARK }; /* operation modes (mode) */ enum { NG_CAR_SINGLE_RATE = 0, NG_CAR_DOUBLE_RATE, NG_CAR_RED, NG_CAR_SHAPE }; -/* mode options (opt) */ +/* mode options (bits in opt) */ #define NG_CAR_COLOR_AWARE 1 #define NG_CAR_COUNT_PACKETS 2 /* Bulk config */ struct ng_car_bulkconf { struct ng_car_hookconf upstream; struct ng_car_hookconf downstream; }; #define NG_CAR_BULKCONF(hconftype) { \ { "upstream", (hconftype) }, \ { "downstream", (hconftype) }, \ { NULL } \ } /* Commands */ enum { NGM_CAR_GET_STATS = 1, /* Get statistics */ NGM_CAR_CLR_STATS, /* Clear statistics */ NGM_CAR_GETCLR_STATS, /* Get and clear statistics */ NGM_CAR_GET_CONF, /* Get bulk configuration */ NGM_CAR_SET_CONF, /* Set bulk configuration */ }; #endif /* _NETGRAPH_NG_CAR_H_ */ diff --git a/sys/netgraph/qos.h b/sys/netgraph/qos.h new file mode 100644 index 000000000000..0e5dfec479eb --- /dev/null +++ b/sys/netgraph/qos.h @@ -0,0 +1,83 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Lutz Donnerhacke + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _NETGRAPH_QOS_H_ +#define _NETGRAPH_QOS_H_ + +#include + +/* ABI cookie */ +#define M_QOS_COOKIE 1571268051 +#define MTAG_SIZE(X) ( sizeof(struct X) - sizeof(struct m_tag) ) + +/* + * Definition of types within this ABI: + * - Choose a type (16bit) by i.e. "echo $((1000+$(date +%s)%64536))" + * - Retry if the type is already in use + * - Define the structure for the type according to mbuf_tags(9) + * struct m_qos_foo { + * struct m_tag tag; + * ... + * }; + * Preferred usage: + * struct m_qos_foo *p = (void *)m_tag_locate(m, + * M_QOS_COOKIE, M_QOS_FOO, ...); + * or + * p = (void *)m_tag_alloc( + * M_QOS_COOKIE, M_QOS_FOO, MTAG_SIZE(foo), ...); + m_tag_prepend(m, &p->tag); + */ + +/* Color marking type */ +#define M_QOS_COLOR 23568 +/* Keep colors ordered semantically in order to allow use of "<=" or ">=" */ +enum qos_color { + QOS_COLOR_GREEN, + QOS_COLOR_YELLOW, + QOS_COLOR_RED +}; +struct m_qos_color { + struct m_tag tag; + enum qos_color color; +}; + +/* + * Priority class + * + * Processing per priority requires an overhead, which should + * be static (i.e. for alloctating queues) and small (for memory) + * So keep your chosen range limited. + */ +#define M_QOS_PRIORITY 28858 +struct m_qos_priority { + struct m_tag tag; + uint8_t priority; /* 0 - lowest */ +}; + +#endif /* _NETGRAPH_QOS_H_ */