diff --git a/sys/netgraph/ng_car.c b/sys/netgraph/ng_car.c index a4e8c0dd55d6..e828306bd7d5 100644 --- a/sys/netgraph/ng_car.c +++ b/sys/netgraph/ng_car.c @@ -1,806 +1,812 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * 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. */ /* * ng_car - An implementation of committed access rate for netgraph * * TODO: * - Sanitize input config values (impose some limits) * - 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" +#ifdef NG_SEPARATE_MALLOC +static MALLOC_DEFINE(M_NETGRAPH_CAR, "netgraph_car", "netgraph car node"); +#else +#define M_NETGRAPH_CAR M_NETGRAPH +#endif + #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); + priv = malloc(sizeof(*priv), M_NETGRAPH_CAR, 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,col) \ do { \ switch (a) { \ case NG_CAR_ACTION_FORWARD: \ /* Do nothing. */ \ break; \ case NG_CAR_ACTION_MARK: \ 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.dropped_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 && col <= QOS_COLOR_GREEN) { /* This packet is green. */ ++hinfo->stats.green_pkts; hinfo->tc -= len; 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 && col <= QOS_COLOR_GREEN) { /* This packet is green */ ++hinfo->stats.green_pkts; hinfo->tc -= len; 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 || 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, QOS_COLOR_RED); /* Use token bucket to simulate RED-like drop probability. */ } 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, 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, QOS_COLOR_RED); } /* If not green and mode is SINGLE/DOUBLE RATE. */ } else { /* Check extended token bucket. */ 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, QOS_COLOR_YELLOW); } else { /* This packet is red */ ++hinfo->stats.red_pkts; 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 *= 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; } 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); + free(priv, M_NETGRAPH_CAR); 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 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) || (col >= QOS_COLOR_RED)) { /* Drop packet. */ ++hinfo->stats.red_pkts; ++hinfo->stats.dropped_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_nat.c b/sys/netgraph/ng_nat.c index d7492b71e07c..e9de0e5b60c8 100644 --- a/sys/netgraph/ng_nat.c +++ b/sys/netgraph/ng_nat.c @@ -1,966 +1,972 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2005, Gleb Smirnoff * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#ifdef NG_SEPARATE_MALLOC +static MALLOC_DEFINE(M_NETGRAPH_NAT, "netgraph_nat", "netgraph nat node"); +#else +#define M_NETGRAPH_NAT M_NETGRAPH +#endif + static ng_constructor_t ng_nat_constructor; static ng_rcvmsg_t ng_nat_rcvmsg; static ng_shutdown_t ng_nat_shutdown; static ng_newhook_t ng_nat_newhook; static ng_rcvdata_t ng_nat_rcvdata; static ng_disconnect_t ng_nat_disconnect; static unsigned int ng_nat_translate_flags(unsigned int x); /* Parse type for struct ng_nat_mode. */ static const struct ng_parse_struct_field ng_nat_mode_fields[] = NG_NAT_MODE_INFO; static const struct ng_parse_type ng_nat_mode_type = { &ng_parse_struct_type, &ng_nat_mode_fields }; /* Parse type for 'description' field in structs. */ static const struct ng_parse_fixedstring_info ng_nat_description_info = { NG_NAT_DESC_LENGTH }; static const struct ng_parse_type ng_nat_description_type = { &ng_parse_fixedstring_type, &ng_nat_description_info }; /* Parse type for struct ng_nat_redirect_port. */ static const struct ng_parse_struct_field ng_nat_redirect_port_fields[] = NG_NAT_REDIRECT_PORT_TYPE_INFO(&ng_nat_description_type); static const struct ng_parse_type ng_nat_redirect_port_type = { &ng_parse_struct_type, &ng_nat_redirect_port_fields }; /* Parse type for struct ng_nat_redirect_addr. */ static const struct ng_parse_struct_field ng_nat_redirect_addr_fields[] = NG_NAT_REDIRECT_ADDR_TYPE_INFO(&ng_nat_description_type); static const struct ng_parse_type ng_nat_redirect_addr_type = { &ng_parse_struct_type, &ng_nat_redirect_addr_fields }; /* Parse type for struct ng_nat_redirect_proto. */ static const struct ng_parse_struct_field ng_nat_redirect_proto_fields[] = NG_NAT_REDIRECT_PROTO_TYPE_INFO(&ng_nat_description_type); static const struct ng_parse_type ng_nat_redirect_proto_type = { &ng_parse_struct_type, &ng_nat_redirect_proto_fields }; /* Parse type for struct ng_nat_add_server. */ static const struct ng_parse_struct_field ng_nat_add_server_fields[] = NG_NAT_ADD_SERVER_TYPE_INFO; static const struct ng_parse_type ng_nat_add_server_type = { &ng_parse_struct_type, &ng_nat_add_server_fields }; /* Parse type for one struct ng_nat_listrdrs_entry. */ static const struct ng_parse_struct_field ng_nat_listrdrs_entry_fields[] = NG_NAT_LISTRDRS_ENTRY_TYPE_INFO(&ng_nat_description_type); static const struct ng_parse_type ng_nat_listrdrs_entry_type = { &ng_parse_struct_type, &ng_nat_listrdrs_entry_fields }; /* Parse type for 'redirects' array in struct ng_nat_list_redirects. */ static int ng_nat_listrdrs_ary_getLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct ng_nat_list_redirects *lr; lr = (const struct ng_nat_list_redirects *) (buf - offsetof(struct ng_nat_list_redirects, redirects)); return lr->total_count; } static const struct ng_parse_array_info ng_nat_listrdrs_ary_info = { &ng_nat_listrdrs_entry_type, &ng_nat_listrdrs_ary_getLength, NULL }; static const struct ng_parse_type ng_nat_listrdrs_ary_type = { &ng_parse_array_type, &ng_nat_listrdrs_ary_info }; /* Parse type for struct ng_nat_list_redirects. */ static const struct ng_parse_struct_field ng_nat_list_redirects_fields[] = NG_NAT_LIST_REDIRECTS_TYPE_INFO(&ng_nat_listrdrs_ary_type); static const struct ng_parse_type ng_nat_list_redirects_type = { &ng_parse_struct_type, &ng_nat_list_redirects_fields }; /* Parse type for struct ng_nat_libalias_info. */ static const struct ng_parse_struct_field ng_nat_libalias_info_fields[] = NG_NAT_LIBALIAS_INFO; static const struct ng_parse_type ng_nat_libalias_info_type = { &ng_parse_struct_type, &ng_nat_libalias_info_fields }; /* List of commands and how to convert arguments to/from ASCII. */ static const struct ng_cmdlist ng_nat_cmdlist[] = { { NGM_NAT_COOKIE, NGM_NAT_SET_IPADDR, "setaliasaddr", &ng_parse_ipaddr_type, NULL }, { NGM_NAT_COOKIE, NGM_NAT_SET_MODE, "setmode", &ng_nat_mode_type, NULL }, { NGM_NAT_COOKIE, NGM_NAT_SET_TARGET, "settarget", &ng_parse_ipaddr_type, NULL }, { NGM_NAT_COOKIE, NGM_NAT_REDIRECT_PORT, "redirectport", &ng_nat_redirect_port_type, &ng_parse_uint32_type }, { NGM_NAT_COOKIE, NGM_NAT_REDIRECT_ADDR, "redirectaddr", &ng_nat_redirect_addr_type, &ng_parse_uint32_type }, { NGM_NAT_COOKIE, NGM_NAT_REDIRECT_PROTO, "redirectproto", &ng_nat_redirect_proto_type, &ng_parse_uint32_type }, { NGM_NAT_COOKIE, NGM_NAT_REDIRECT_DYNAMIC, "redirectdynamic", &ng_parse_uint32_type, NULL }, { NGM_NAT_COOKIE, NGM_NAT_REDIRECT_DELETE, "redirectdelete", &ng_parse_uint32_type, NULL }, { NGM_NAT_COOKIE, NGM_NAT_ADD_SERVER, "addserver", &ng_nat_add_server_type, NULL }, { NGM_NAT_COOKIE, NGM_NAT_LIST_REDIRECTS, "listredirects", NULL, &ng_nat_list_redirects_type }, { NGM_NAT_COOKIE, NGM_NAT_PROXY_RULE, "proxyrule", &ng_parse_string_type, NULL }, { NGM_NAT_COOKIE, NGM_NAT_LIBALIAS_INFO, "libaliasinfo", NULL, &ng_nat_libalias_info_type }, { NGM_NAT_COOKIE, NGM_NAT_SET_DLT, "setdlt", &ng_parse_uint8_type, NULL }, { NGM_NAT_COOKIE, NGM_NAT_GET_DLT, "getdlt", NULL, &ng_parse_uint8_type }, { 0 } }; /* Netgraph node type descriptor. */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_NAT_NODE_TYPE, .constructor = ng_nat_constructor, .rcvmsg = ng_nat_rcvmsg, .shutdown = ng_nat_shutdown, .newhook = ng_nat_newhook, .rcvdata = ng_nat_rcvdata, .disconnect = ng_nat_disconnect, .cmdlist = ng_nat_cmdlist, }; NETGRAPH_INIT(nat, &typestruct); MODULE_DEPEND(ng_nat, libalias, 1, 1, 1); /* Element for list of redirects. */ struct ng_nat_rdr_lst { STAILQ_ENTRY(ng_nat_rdr_lst) entries; struct alias_link *lnk; struct ng_nat_listrdrs_entry rdr; }; STAILQ_HEAD(rdrhead, ng_nat_rdr_lst); /* Information we store for each node. */ struct ng_nat_priv { node_p node; /* back pointer to node */ hook_p in; /* hook for demasquerading */ hook_p out; /* hook for masquerading */ struct libalias *lib; /* libalias handler */ uint32_t flags; /* status flags */ uint32_t rdrcount; /* number or redirects in list */ uint32_t nextid; /* for next in turn in list */ struct rdrhead redirhead; /* redirect list header */ uint8_t dlt; /* DLT_XXX from bpf.h */ }; typedef struct ng_nat_priv *priv_p; /* Values of flags */ #define NGNAT_CONNECTED 0x1 /* We have both hooks connected */ #define NGNAT_ADDR_DEFINED 0x2 /* NGM_NAT_SET_IPADDR happened */ static int ng_nat_constructor(node_p node) { priv_p priv; /* Initialize private descriptor. */ - priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); + priv = malloc(sizeof(*priv), M_NETGRAPH_NAT, M_WAITOK | M_ZERO); /* Init aliasing engine. */ priv->lib = LibAliasInit(NULL); /* Set same ports on. */ (void )LibAliasSetMode(priv->lib, PKT_ALIAS_SAME_PORTS, PKT_ALIAS_SAME_PORTS); /* Init redirects housekeeping. */ priv->rdrcount = 0; priv->nextid = 1; priv->dlt = DLT_RAW; STAILQ_INIT(&priv->redirhead); /* Link structs together. */ NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* * libalias is not thread safe, so our node * must be single threaded. */ NG_NODE_FORCE_WRITER(node); return (0); } static int ng_nat_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); if (strcmp(name, NG_NAT_HOOK_IN) == 0) { priv->in = hook; } else if (strcmp(name, NG_NAT_HOOK_OUT) == 0) { priv->out = hook; } else return (EINVAL); if (priv->out != NULL && priv->in != NULL) priv->flags |= NGNAT_CONNECTED; return(0); } static int ng_nat_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; struct ng_mesg *msg; int error = 0; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_NAT_COOKIE: switch (msg->header.cmd) { case NGM_NAT_SET_IPADDR: { struct in_addr *const ia = (struct in_addr *)msg->data; if (msg->header.arglen < sizeof(*ia)) { error = EINVAL; break; } LibAliasSetAddress(priv->lib, *ia); priv->flags |= NGNAT_ADDR_DEFINED; } break; case NGM_NAT_SET_MODE: { struct ng_nat_mode *const mode = (struct ng_nat_mode *)msg->data; if (msg->header.arglen < sizeof(*mode)) { error = EINVAL; break; } if (LibAliasSetMode(priv->lib, ng_nat_translate_flags(mode->flags), ng_nat_translate_flags(mode->mask)) < 0) { error = ENOMEM; break; } } break; case NGM_NAT_SET_TARGET: { struct in_addr *const ia = (struct in_addr *)msg->data; if (msg->header.arglen < sizeof(*ia)) { error = EINVAL; break; } LibAliasSetTarget(priv->lib, *ia); } break; case NGM_NAT_REDIRECT_PORT: { struct ng_nat_rdr_lst *entry; struct ng_nat_redirect_port *const rp = (struct ng_nat_redirect_port *)msg->data; if (msg->header.arglen < sizeof(*rp)) { error = EINVAL; break; } if ((entry = malloc(sizeof(struct ng_nat_rdr_lst), - M_NETGRAPH, M_NOWAIT | M_ZERO)) == NULL) { + M_NETGRAPH_NAT, M_NOWAIT | M_ZERO)) == NULL) { error = ENOMEM; break; } /* Try actual redirect. */ entry->lnk = LibAliasRedirectPort(priv->lib, rp->local_addr, htons(rp->local_port), rp->remote_addr, htons(rp->remote_port), rp->alias_addr, htons(rp->alias_port), rp->proto); if (entry->lnk == NULL) { error = ENOMEM; - free(entry, M_NETGRAPH); + free(entry, M_NETGRAPH_NAT); break; } /* Successful, save info in our internal list. */ entry->rdr.local_addr = rp->local_addr; entry->rdr.alias_addr = rp->alias_addr; entry->rdr.remote_addr = rp->remote_addr; entry->rdr.local_port = rp->local_port; entry->rdr.alias_port = rp->alias_port; entry->rdr.remote_port = rp->remote_port; entry->rdr.proto = rp->proto; bcopy(rp->description, entry->rdr.description, NG_NAT_DESC_LENGTH); /* Safety precaution. */ entry->rdr.description[NG_NAT_DESC_LENGTH-1] = '\0'; entry->rdr.id = priv->nextid++; priv->rdrcount++; /* Link to list of redirects. */ STAILQ_INSERT_TAIL(&priv->redirhead, entry, entries); /* Response with id of newly added entry. */ NG_MKRESPONSE(resp, msg, sizeof(entry->rdr.id), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bcopy(&entry->rdr.id, resp->data, sizeof(entry->rdr.id)); } break; case NGM_NAT_REDIRECT_ADDR: { struct ng_nat_rdr_lst *entry; struct ng_nat_redirect_addr *const ra = (struct ng_nat_redirect_addr *)msg->data; if (msg->header.arglen < sizeof(*ra)) { error = EINVAL; break; } if ((entry = malloc(sizeof(struct ng_nat_rdr_lst), - M_NETGRAPH, M_NOWAIT | M_ZERO)) == NULL) { + M_NETGRAPH_NAT, M_NOWAIT | M_ZERO)) == NULL) { error = ENOMEM; break; } /* Try actual redirect. */ entry->lnk = LibAliasRedirectAddr(priv->lib, ra->local_addr, ra->alias_addr); if (entry->lnk == NULL) { error = ENOMEM; - free(entry, M_NETGRAPH); + free(entry, M_NETGRAPH_NAT); break; } /* Successful, save info in our internal list. */ entry->rdr.local_addr = ra->local_addr; entry->rdr.alias_addr = ra->alias_addr; entry->rdr.proto = NG_NAT_REDIRPROTO_ADDR; bcopy(ra->description, entry->rdr.description, NG_NAT_DESC_LENGTH); /* Safety precaution. */ entry->rdr.description[NG_NAT_DESC_LENGTH-1] = '\0'; entry->rdr.id = priv->nextid++; priv->rdrcount++; /* Link to list of redirects. */ STAILQ_INSERT_TAIL(&priv->redirhead, entry, entries); /* Response with id of newly added entry. */ NG_MKRESPONSE(resp, msg, sizeof(entry->rdr.id), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bcopy(&entry->rdr.id, resp->data, sizeof(entry->rdr.id)); } break; case NGM_NAT_REDIRECT_PROTO: { struct ng_nat_rdr_lst *entry; struct ng_nat_redirect_proto *const rp = (struct ng_nat_redirect_proto *)msg->data; if (msg->header.arglen < sizeof(*rp)) { error = EINVAL; break; } if ((entry = malloc(sizeof(struct ng_nat_rdr_lst), - M_NETGRAPH, M_NOWAIT | M_ZERO)) == NULL) { + M_NETGRAPH_NAT, M_NOWAIT | M_ZERO)) == NULL) { error = ENOMEM; break; } /* Try actual redirect. */ entry->lnk = LibAliasRedirectProto(priv->lib, rp->local_addr, rp->remote_addr, rp->alias_addr, rp->proto); if (entry->lnk == NULL) { error = ENOMEM; - free(entry, M_NETGRAPH); + free(entry, M_NETGRAPH_NAT); break; } /* Successful, save info in our internal list. */ entry->rdr.local_addr = rp->local_addr; entry->rdr.alias_addr = rp->alias_addr; entry->rdr.remote_addr = rp->remote_addr; entry->rdr.proto = rp->proto; bcopy(rp->description, entry->rdr.description, NG_NAT_DESC_LENGTH); /* Safety precaution. */ entry->rdr.description[NG_NAT_DESC_LENGTH-1] = '\0'; entry->rdr.id = priv->nextid++; priv->rdrcount++; /* Link to list of redirects. */ STAILQ_INSERT_TAIL(&priv->redirhead, entry, entries); /* Response with id of newly added entry. */ NG_MKRESPONSE(resp, msg, sizeof(entry->rdr.id), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bcopy(&entry->rdr.id, resp->data, sizeof(entry->rdr.id)); } break; case NGM_NAT_REDIRECT_DYNAMIC: case NGM_NAT_REDIRECT_DELETE: { struct ng_nat_rdr_lst *entry; uint32_t *const id = (uint32_t *)msg->data; if (msg->header.arglen < sizeof(*id)) { error = EINVAL; break; } /* Find entry with supplied id. */ STAILQ_FOREACH(entry, &priv->redirhead, entries) { if (entry->rdr.id == *id) break; } /* Not found. */ if (entry == NULL) { error = ENOENT; break; } if (msg->header.cmd == NGM_NAT_REDIRECT_DYNAMIC) { if (LibAliasRedirectDynamic(priv->lib, entry->lnk) == -1) { error = ENOTTY; /* XXX Something better? */ break; } } else { /* NGM_NAT_REDIRECT_DELETE */ LibAliasRedirectDelete(priv->lib, entry->lnk); } /* Delete entry from our internal list. */ priv->rdrcount--; STAILQ_REMOVE(&priv->redirhead, entry, ng_nat_rdr_lst, entries); - free(entry, M_NETGRAPH); + free(entry, M_NETGRAPH_NAT); } break; case NGM_NAT_ADD_SERVER: { struct ng_nat_rdr_lst *entry; struct ng_nat_add_server *const as = (struct ng_nat_add_server *)msg->data; if (msg->header.arglen < sizeof(*as)) { error = EINVAL; break; } /* Find entry with supplied id. */ STAILQ_FOREACH(entry, &priv->redirhead, entries) { if (entry->rdr.id == as->id) break; } /* Not found. */ if (entry == NULL) { error = ENOENT; break; } if (LibAliasAddServer(priv->lib, entry->lnk, as->addr, htons(as->port)) == -1) { error = ENOMEM; break; } entry->rdr.lsnat++; } break; case NGM_NAT_LIST_REDIRECTS: { struct ng_nat_rdr_lst *entry; struct ng_nat_list_redirects *ary; int i = 0; NG_MKRESPONSE(resp, msg, sizeof(*ary) + (priv->rdrcount) * sizeof(*entry), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } ary = (struct ng_nat_list_redirects *)resp->data; ary->total_count = priv->rdrcount; STAILQ_FOREACH(entry, &priv->redirhead, entries) { bcopy(&entry->rdr, &ary->redirects[i++], sizeof(struct ng_nat_listrdrs_entry)); } } break; case NGM_NAT_PROXY_RULE: { char *cmd = (char *)msg->data; if (msg->header.arglen < 6) { error = EINVAL; break; } if (LibAliasProxyRule(priv->lib, cmd) != 0) error = ENOMEM; } break; case NGM_NAT_LIBALIAS_INFO: { struct ng_nat_libalias_info *i; NG_MKRESPONSE(resp, msg, sizeof(struct ng_nat_libalias_info), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } i = (struct ng_nat_libalias_info *)resp->data; #define COPY(F) do { \ if (priv->lib->F >= 0 && priv->lib->F < UINT32_MAX) \ i->F = priv->lib->F; \ else \ i->F = UINT32_MAX; \ } while (0) COPY(icmpLinkCount); COPY(udpLinkCount); COPY(tcpLinkCount); COPY(pptpLinkCount); COPY(sctpLinkCount); COPY(protoLinkCount); COPY(fragmentIdLinkCount); COPY(fragmentPtrLinkCount); COPY(sockCount); #undef COPY } break; case NGM_NAT_SET_DLT: if (msg->header.arglen != sizeof(uint8_t)) { error = EINVAL; break; } switch (*(uint8_t *) msg->data) { case DLT_EN10MB: case DLT_RAW: priv->dlt = *(uint8_t *) msg->data; break; default: error = EINVAL; break; } break; default: error = EINVAL; /* unknown command */ break; } break; case NGM_NAT_GET_DLT: NG_MKRESPONSE(resp, msg, sizeof(uint8_t), M_WAITOK); if (resp == NULL) { error = ENOMEM; break; } *((uint8_t *) resp->data) = priv->dlt; break; default: error = EINVAL; /* unknown cookie type */ break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } static int ng_nat_rcvdata(hook_p hook, item_p item ) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct mbuf *m; struct ip *ip; int rval, ipofs, error = 0; char *c; /* We have no required hooks. */ if (!(priv->flags & NGNAT_CONNECTED)) { NG_FREE_ITEM(item); return (ENXIO); } /* We have no alias address yet to do anything. */ if (!(priv->flags & NGNAT_ADDR_DEFINED)) goto send; m = NGI_M(item); if ((m = m_megapullup(m, m->m_pkthdr.len)) == NULL) { NGI_M(item) = NULL; /* avoid double free */ NG_FREE_ITEM(item); return (ENOBUFS); } NGI_M(item) = m; switch (priv->dlt) { case DLT_RAW: ipofs = 0; break; case DLT_EN10MB: { struct ether_header *eh; if (m->m_pkthdr.len < sizeof(struct ether_header)) { NG_FREE_ITEM(item); return (ENXIO); } eh = mtod(m, struct ether_header *); switch (ntohs(eh->ether_type)) { case ETHERTYPE_IP: ipofs = sizeof(struct ether_header); break; default: goto send; } break; } default: panic("Corrupted priv->dlt: %u", priv->dlt); } if (m->m_pkthdr.len < ipofs + sizeof(struct ip)) goto send; /* packet too short to hold IP */ c = (char *)mtodo(m, ipofs); ip = (struct ip *)mtodo(m, ipofs); if (ip->ip_v != IPVERSION) goto send; /* other IP version, let it pass */ if (m->m_pkthdr.len < ipofs + ntohs(ip->ip_len)) goto send; /* packet too short (i.e. fragmented or broken) */ /* * We drop packet when: * 1. libalias returns PKT_ALIAS_ERROR; * 2. For incoming packets: * a) for unresolved fragments; * b) libalias returns PKT_ALIAS_IGNORED and * PKT_ALIAS_DENY_INCOMING flag is set. */ if (hook == priv->in) { rval = LibAliasIn(priv->lib, c, m->m_len - ipofs + M_TRAILINGSPACE(m)); if (rval == PKT_ALIAS_ERROR || rval == PKT_ALIAS_UNRESOLVED_FRAGMENT || (rval == PKT_ALIAS_IGNORED && (priv->lib->packetAliasMode & PKT_ALIAS_DENY_INCOMING) != 0)) { NG_FREE_ITEM(item); return (EINVAL); } } else if (hook == priv->out) { rval = LibAliasOut(priv->lib, c, m->m_len - ipofs + M_TRAILINGSPACE(m)); if (rval == PKT_ALIAS_ERROR) { NG_FREE_ITEM(item); return (EINVAL); } } else panic("ng_nat: unknown hook!\n"); if (rval == PKT_ALIAS_RESPOND) m->m_flags |= M_SKIP_FIREWALL; m->m_pkthdr.len = m->m_len = ntohs(ip->ip_len) + ipofs; if ((ip->ip_off & htons(IP_OFFMASK)) == 0 && ip->ip_p == IPPROTO_TCP) { struct tcphdr *th = (struct tcphdr *)((caddr_t)ip + (ip->ip_hl << 2)); /* * Here is our terrible HACK. * * Sometimes LibAlias edits contents of TCP packet. * In this case it needs to recompute full TCP * checksum. However, the problem is that LibAlias * doesn't have any idea about checksum offloading * in kernel. To workaround this, we do not do * checksumming in LibAlias, but only mark the * packets with TH_RES1 in the th_x2 field. If we * receive a marked packet, we calculate correct * checksum for it aware of offloading. * * Why do I do such a terrible hack instead of * recalculating checksum for each packet? * Because the previous checksum was not checked! * Recalculating checksums for EVERY packet will * hide ALL transmission errors. Yes, marked packets * still suffer from this problem. But, sigh, natd(8) * has this problem, too. */ if (tcp_get_flags(th) & TH_RES1) { uint16_t ip_len = ntohs(ip->ip_len); tcp_set_flags(th, tcp_get_flags(th) & ~TH_RES1); th->th_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, htons(IPPROTO_TCP + ip_len - (ip->ip_hl << 2))); if ((m->m_pkthdr.csum_flags & CSUM_TCP) == 0) { m->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum); in_delayed_cksum(m); } } } send: if (hook == priv->in) NG_FWD_ITEM_HOOK(error, item, priv->out); else NG_FWD_ITEM_HOOK(error, item, priv->in); return (error); } static int ng_nat_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); /* Free redirects list. */ while (!STAILQ_EMPTY(&priv->redirhead)) { struct ng_nat_rdr_lst *entry = STAILQ_FIRST(&priv->redirhead); STAILQ_REMOVE_HEAD(&priv->redirhead, entries); - free(entry, M_NETGRAPH); + free(entry, M_NETGRAPH_NAT); } /* Final free. */ LibAliasUninit(priv->lib); - free(priv, M_NETGRAPH); + free(priv, M_NETGRAPH_NAT); return (0); } static int ng_nat_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); priv->flags &= ~NGNAT_CONNECTED; if (hook == priv->out) priv->out = NULL; if (hook == priv->in) priv->in = NULL; if (priv->out == NULL && priv->in == NULL) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } static unsigned int ng_nat_translate_flags(unsigned int x) { unsigned int res = 0; if (x & NG_NAT_LOG) res |= PKT_ALIAS_LOG; if (x & NG_NAT_DENY_INCOMING) res |= PKT_ALIAS_DENY_INCOMING; if (x & NG_NAT_SAME_PORTS) res |= PKT_ALIAS_SAME_PORTS; if (x & NG_NAT_UNREGISTERED_ONLY) res |= PKT_ALIAS_UNREGISTERED_ONLY; if (x & NG_NAT_RESET_ON_ADDR_CHANGE) res |= PKT_ALIAS_RESET_ON_ADDR_CHANGE; if (x & NG_NAT_PROXY_ONLY) res |= PKT_ALIAS_PROXY_ONLY; if (x & NG_NAT_REVERSE) res |= PKT_ALIAS_REVERSE; if (x & NG_NAT_UNREGISTERED_CGN) res |= PKT_ALIAS_UNREGISTERED_CGN; return (res); } diff --git a/sys/netgraph/ng_pptpgre.c b/sys/netgraph/ng_pptpgre.c index 8bd866c2881a..ae55a8dae5fd 100644 --- a/sys/netgraph/ng_pptpgre.c +++ b/sys/netgraph/ng_pptpgre.c @@ -1,1284 +1,1290 @@ /* * ng_pptpgre.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * $Whistle: ng_pptpgre.c,v 1.7 1999/12/08 00:10:06 archie Exp $ */ /* * PPTP/GRE netgraph node type. * * This node type does the GRE encapsulation as specified for the PPTP * protocol (RFC 2637, section 4). This includes sequencing and * retransmission of frames, but not the actual packet delivery nor * any of the TCP control stream protocol. * * The "upper" hook of this node is suitable for attaching to a "ppp" * node link hook. The "lower" hook of this node is suitable for attaching * to a "ksocket" node on hook "inet/raw/gre". */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#ifdef NG_SEPARATE_MALLOC +static MALLOC_DEFINE(M_NETGRAPH_PPTP, "netgraph_pptp", "netgraph pptpgre node"); +#else +#define M_NETGRAPH_PPTP M_NETGRAPH +#endif + /* GRE packet format, as used by PPTP */ struct greheader { #if BYTE_ORDER == LITTLE_ENDIAN u_char recursion:3; /* recursion control */ u_char ssr:1; /* strict source route */ u_char hasSeq:1; /* sequence number present */ u_char hasKey:1; /* key present */ u_char hasRoute:1; /* routing present */ u_char hasSum:1; /* checksum present */ u_char vers:3; /* version */ u_char flags:4; /* flags */ u_char hasAck:1; /* acknowlege number present */ #elif BYTE_ORDER == BIG_ENDIAN u_char hasSum:1; /* checksum present */ u_char hasRoute:1; /* routing present */ u_char hasKey:1; /* key present */ u_char hasSeq:1; /* sequence number present */ u_char ssr:1; /* strict source route */ u_char recursion:3; /* recursion control */ u_char hasAck:1; /* acknowlege number present */ u_char flags:4; /* flags */ u_char vers:3; /* version */ #else #error BYTE_ORDER is not defined properly #endif u_int16_t proto; /* protocol (ethertype) */ u_int16_t length; /* payload length */ u_int16_t cid; /* call id */ u_int32_t data[0]; /* opt. seq, ack, then data */ }; /* The PPTP protocol ID used in the GRE 'proto' field */ #define PPTP_GRE_PROTO 0x880b /* Bits that must be set a certain way in all PPTP/GRE packets */ #define PPTP_INIT_VALUE ((0x2001 << 16) | PPTP_GRE_PROTO) #define PPTP_INIT_MASK 0xef7fffff /* Min and max packet length */ #define PPTP_MAX_PAYLOAD (0xffff - sizeof(struct greheader) - 8) /* All times are scaled by this (PPTP_TIME_SCALE time units = 1 sec.) */ #define PPTP_TIME_SCALE 1024 /* milliseconds */ typedef u_int64_t pptptime_t; /* Acknowledgment timeout parameters and functions */ #define PPTP_XMIT_WIN 16 /* max xmit window */ #define PPTP_MIN_TIMEOUT (PPTP_TIME_SCALE / 83) /* 12 milliseconds */ #define PPTP_MAX_TIMEOUT (3 * PPTP_TIME_SCALE) /* 3 seconds */ #define PPTP_REORDER_TIMEOUT 1 /* When we receive a packet, we wait to see if there's an outgoing packet we can piggy-back the ACK off of. These parameters determine the minimum and maxmimum length of time we're willing to wait in order to do that. These have no effect unless "enableDelayedAck" is turned on. */ #define PPTP_MIN_ACK_DELAY (PPTP_TIME_SCALE / 500) /* 2 milliseconds */ #define PPTP_MAX_ACK_DELAY (PPTP_TIME_SCALE / 2) /* 500 milliseconds */ /* See RFC 2637 section 4.4 */ #define PPTP_ACK_ALPHA(x) (((x) + 4) >> 3) /* alpha = 0.125 */ #define PPTP_ACK_BETA(x) (((x) + 2) >> 2) /* beta = 0.25 */ #define PPTP_ACK_CHI(x) ((x) << 2) /* chi = 4 */ #define PPTP_ACK_DELTA(x) ((x) << 1) /* delta = 2 */ #define PPTP_SEQ_DIFF(x,y) ((int32_t)(x) - (int32_t)(y)) #define SESSHASHSIZE 0x0020 #define SESSHASH(x) (((x) ^ ((x) >> 8)) & (SESSHASHSIZE - 1)) SYSCTL_NODE(_net_graph, OID_AUTO, pptpgre, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "PPTPGRE"); /* * Reorder queue maximum length. Zero disables reorder. * * The node may keep reorder_max queue entries per session * if reorder is enabled, plus allocate one more for short time. * * Be conservative in memory consumption by default. * Lots of sessions with large queues can overflow M_NETGRAPH zone. */ static int reorder_max = 1; /* reorder up to two swapped packets in a row */ SYSCTL_UINT(_net_graph_pptpgre, OID_AUTO, reorder_max, CTLFLAG_RWTUN, &reorder_max, 0, "Reorder queue maximum length"); static int reorder_timeout = PPTP_REORDER_TIMEOUT; SYSCTL_UINT(_net_graph_pptpgre, OID_AUTO, reorder_timeout, CTLFLAG_RWTUN, &reorder_timeout, 0, "Reorder timeout is milliseconds"); /* Packet reorder FIFO queue */ struct ng_pptpgre_roq { SLIST_ENTRY(ng_pptpgre_roq) next; /* next entry of the queue */ item_p item; /* netgraph item */ u_int32_t seq; /* packet sequence number */ }; SLIST_HEAD(ng_pptpgre_roq_head, ng_pptpgre_roq); typedef struct ng_pptpgre_roq_head roqh; /* We keep packet retransmit and acknowlegement state in this struct */ struct ng_pptpgre_sess { node_p node; /* this node pointer */ hook_p hook; /* hook to upper layers */ struct ng_pptpgre_conf conf; /* configuration info */ struct mtx mtx; /* session mutex */ u_int32_t recvSeq; /* last seq # we rcv'd */ u_int32_t xmitSeq; /* last seq # we sent */ u_int32_t recvAck; /* last seq # peer ack'd */ u_int32_t xmitAck; /* last seq # we ack'd */ int32_t ato; /* adaptive time-out value */ int32_t rtt; /* round trip time estimate */ int32_t dev; /* deviation estimate */ u_int16_t xmitWin; /* size of xmit window */ struct callout sackTimer; /* send ack timer */ struct callout rackTimer; /* recv ack timer */ u_int32_t winAck; /* seq when xmitWin will grow */ pptptime_t timeSent[PPTP_XMIT_WIN]; LIST_ENTRY(ng_pptpgre_sess) sessions; roqh roq; /* reorder queue head */ u_int8_t roq_len; /* reorder queue length */ struct callout reorderTimer; /* reorder timeout handler */ }; typedef struct ng_pptpgre_sess *hpriv_p; /* Node private data */ struct ng_pptpgre_private { hook_p upper; /* hook to upper layers */ hook_p lower; /* hook to lower layers */ struct ng_pptpgre_sess uppersess; /* default session for compat */ LIST_HEAD(, ng_pptpgre_sess) sesshash[SESSHASHSIZE]; struct ng_pptpgre_stats stats; /* node statistics */ }; typedef struct ng_pptpgre_private *priv_p; /* Netgraph node methods */ static ng_constructor_t ng_pptpgre_constructor; static ng_rcvmsg_t ng_pptpgre_rcvmsg; static ng_shutdown_t ng_pptpgre_shutdown; static ng_newhook_t ng_pptpgre_newhook; static ng_rcvdata_t ng_pptpgre_rcvdata; static ng_rcvdata_t ng_pptpgre_rcvdata_lower; static ng_disconnect_t ng_pptpgre_disconnect; /* Helper functions */ static int ng_pptpgre_xmit(hpriv_p hpriv, item_p item); static void ng_pptpgre_start_send_ack_timer(hpriv_p hpriv); static void ng_pptpgre_start_recv_ack_timer(hpriv_p hpriv); static void ng_pptpgre_start_reorder_timer(hpriv_p hpriv); static void ng_pptpgre_recv_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2); static void ng_pptpgre_send_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2); static void ng_pptpgre_reorder_timeout(node_p node, hook_p hook, void *arg1, int arg2); static hpriv_p ng_pptpgre_find_session(priv_p privp, u_int16_t cid); static void ng_pptpgre_reset(hpriv_p hpriv); static pptptime_t ng_pptpgre_time(void); static void ng_pptpgre_ack(const hpriv_p hpriv); static int ng_pptpgre_sendq(const hpriv_p hpriv, roqh *q, const struct ng_pptpgre_roq *st); /* Parse type for struct ng_pptpgre_conf */ static const struct ng_parse_struct_field ng_pptpgre_conf_type_fields[] = NG_PPTPGRE_CONF_TYPE_INFO; static const struct ng_parse_type ng_pptpgre_conf_type = { &ng_parse_struct_type, &ng_pptpgre_conf_type_fields, }; /* Parse type for struct ng_pptpgre_stats */ static const struct ng_parse_struct_field ng_pptpgre_stats_type_fields[] = NG_PPTPGRE_STATS_TYPE_INFO; static const struct ng_parse_type ng_pptp_stats_type = { &ng_parse_struct_type, &ng_pptpgre_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_pptpgre_cmdlist[] = { { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_SET_CONFIG, "setconfig", &ng_pptpgre_conf_type, NULL }, { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_GET_CONFIG, "getconfig", &ng_parse_hint16_type, &ng_pptpgre_conf_type }, { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_GET_STATS, "getstats", NULL, &ng_pptp_stats_type }, { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_CLR_STATS, "clrstats", NULL, NULL }, { NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_GETCLR_STATS, "getclrstats", NULL, &ng_pptp_stats_type }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_pptpgre_typestruct = { .version = NG_ABI_VERSION, .name = NG_PPTPGRE_NODE_TYPE, .constructor = ng_pptpgre_constructor, .rcvmsg = ng_pptpgre_rcvmsg, .shutdown = ng_pptpgre_shutdown, .newhook = ng_pptpgre_newhook, .rcvdata = ng_pptpgre_rcvdata, .disconnect = ng_pptpgre_disconnect, .cmdlist = ng_pptpgre_cmdlist, }; NETGRAPH_INIT(pptpgre, &ng_pptpgre_typestruct); #define ERROUT(x) do { error = (x); goto done; } while (0) /************************************************************************ NETGRAPH NODE STUFF ************************************************************************/ /* * Node type constructor */ static int ng_pptpgre_constructor(node_p node) { priv_p priv; int i; /* Allocate private structure */ - priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); + priv = malloc(sizeof(*priv), M_NETGRAPH_PPTP, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, priv); /* Initialize state */ mtx_init(&priv->uppersess.mtx, "ng_pptp", NULL, MTX_DEF); ng_callout_init(&priv->uppersess.sackTimer); ng_callout_init(&priv->uppersess.rackTimer); priv->uppersess.node = node; SLIST_INIT(&priv->uppersess.roq); priv->uppersess.roq_len = 0; ng_callout_init(&priv->uppersess.reorderTimer); for (i = 0; i < SESSHASHSIZE; i++) LIST_INIT(&priv->sesshash[i]); LIST_INSERT_HEAD(&priv->sesshash[0], &priv->uppersess, sessions); /* Done */ return (0); } /* * Give our OK for a hook to be added. */ static int ng_pptpgre_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); /* Check hook name */ if (strcmp(name, NG_PPTPGRE_HOOK_UPPER) == 0) { priv->upper = hook; priv->uppersess.hook = hook; NG_HOOK_SET_PRIVATE(hook, &priv->uppersess); } else if (strcmp(name, NG_PPTPGRE_HOOK_LOWER) == 0) { priv->lower = hook; NG_HOOK_SET_RCVDATA(hook, ng_pptpgre_rcvdata_lower); } else { static const char hexdig[16] = "0123456789abcdef"; const char *hex; hpriv_p hpriv; int i, j; uint16_t cid, hash; /* Parse hook name to get session ID */ if (strncmp(name, NG_PPTPGRE_HOOK_SESSION_P, sizeof(NG_PPTPGRE_HOOK_SESSION_P) - 1) != 0) return (EINVAL); hex = name + sizeof(NG_PPTPGRE_HOOK_SESSION_P) - 1; for (cid = i = 0; i < 4; i++) { for (j = 0; j < 16 && hex[i] != hexdig[j]; j++); if (j == 16) return (EINVAL); cid = (cid << 4) | j; } if (hex[i] != '\0') return (EINVAL); - hpriv = malloc(sizeof(*hpriv), M_NETGRAPH, M_NOWAIT | M_ZERO); + hpriv = malloc(sizeof(*hpriv), M_NETGRAPH_PPTP, M_NOWAIT | M_ZERO); if (hpriv == NULL) return (ENOMEM); /* Initialize state */ mtx_init(&hpriv->mtx, "ng_pptp", NULL, MTX_DEF); ng_callout_init(&hpriv->sackTimer); ng_callout_init(&hpriv->rackTimer); hpriv->conf.cid = cid; hpriv->node = node; hpriv->hook = hook; SLIST_INIT(&hpriv->roq); hpriv->roq_len = 0; ng_callout_init(&hpriv->reorderTimer); NG_HOOK_SET_PRIVATE(hook, hpriv); hash = SESSHASH(cid); LIST_INSERT_HEAD(&priv->sesshash[hash], hpriv, sessions); } return (0); } /* * Receive a control message. */ static int ng_pptpgre_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_PPTPGRE_COOKIE: switch (msg->header.cmd) { case NGM_PPTPGRE_SET_CONFIG: { struct ng_pptpgre_conf *const newConf = (struct ng_pptpgre_conf *) msg->data; hpriv_p hpriv; uint16_t hash; /* Check for invalid or illegal config */ if (msg->header.arglen != sizeof(*newConf)) ERROUT(EINVAL); /* Try to find session by cid. */ hpriv = ng_pptpgre_find_session(priv, newConf->cid); /* If not present - use upper. */ if (hpriv == NULL) { hpriv = &priv->uppersess; LIST_REMOVE(hpriv, sessions); hash = SESSHASH(newConf->cid); LIST_INSERT_HEAD(&priv->sesshash[hash], hpriv, sessions); } ng_pptpgre_reset(hpriv); /* reset on configure */ hpriv->conf = *newConf; break; } case NGM_PPTPGRE_GET_CONFIG: { hpriv_p hpriv; if (msg->header.arglen == 2) { /* Try to find session by cid. */ hpriv = ng_pptpgre_find_session(priv, *((uint16_t *)msg->data)); if (hpriv == NULL) ERROUT(EINVAL); } else if (msg->header.arglen == 0) { /* Use upper. */ hpriv = &priv->uppersess; } else ERROUT(EINVAL); NG_MKRESPONSE(resp, msg, sizeof(hpriv->conf), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); bcopy(&hpriv->conf, resp->data, sizeof(hpriv->conf)); break; } case NGM_PPTPGRE_GET_STATS: case NGM_PPTPGRE_CLR_STATS: case NGM_PPTPGRE_GETCLR_STATS: { if (msg->header.cmd != NGM_PPTPGRE_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(priv->stats), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); bcopy(&priv->stats, resp->data, sizeof(priv->stats)); } if (msg->header.cmd != NGM_PPTPGRE_GET_STATS) bzero(&priv->stats, sizeof(priv->stats)); break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive incoming data on a hook. */ static int ng_pptpgre_rcvdata(hook_p hook, item_p item) { const hpriv_p hpriv = NG_HOOK_PRIVATE(hook); int rval; /* If not configured, reject */ if (!hpriv->conf.enabled) { NG_FREE_ITEM(item); return (ENXIO); } mtx_lock(&hpriv->mtx); rval = ng_pptpgre_xmit(hpriv, item); mtx_assert(&hpriv->mtx, MA_NOTOWNED); return (rval); } /* * Hook disconnection */ static int ng_pptpgre_disconnect(hook_p hook) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); const hpriv_p hpriv = NG_HOOK_PRIVATE(hook); /* Zero out hook pointer */ if (hook == priv->upper) { priv->upper = NULL; priv->uppersess.hook = NULL; } else if (hook == priv->lower) { priv->lower = NULL; } else { /* Reset node (stops timers) */ ng_pptpgre_reset(hpriv); LIST_REMOVE(hpriv, sessions); mtx_destroy(&hpriv->mtx); - free(hpriv, M_NETGRAPH); + free(hpriv, M_NETGRAPH_PPTP); } /* Go away if no longer connected to anything */ if ((NG_NODE_NUMHOOKS(node) == 0) && (NG_NODE_IS_VALID(node))) ng_rmnode_self(node); return (0); } /* * Destroy node */ static int ng_pptpgre_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); /* Reset node (stops timers) */ ng_pptpgre_reset(&priv->uppersess); LIST_REMOVE(&priv->uppersess, sessions); mtx_destroy(&priv->uppersess.mtx); - free(priv, M_NETGRAPH); + free(priv, M_NETGRAPH_PPTP); /* Decrement ref count */ NG_NODE_UNREF(node); return (0); } /************************************************************************* TRANSMIT AND RECEIVE FUNCTIONS *************************************************************************/ /* * Transmit an outgoing frame, or just an ack if m is NULL. */ static int ng_pptpgre_xmit(hpriv_p hpriv, item_p item) { const priv_p priv = NG_NODE_PRIVATE(hpriv->node); u_char buf[sizeof(struct greheader) + 2 * sizeof(u_int32_t)]; struct greheader *const gre = (struct greheader *)buf; int grelen, error; struct mbuf *m; mtx_assert(&hpriv->mtx, MA_OWNED); if (item) { NGI_GET_M(item, m); } else { m = NULL; } /* Check if there's data */ if (m != NULL) { /* Check if windowing is enabled */ if (hpriv->conf.enableWindowing) { /* Is our transmit window full? */ if ((u_int32_t)PPTP_SEQ_DIFF(hpriv->xmitSeq, hpriv->recvAck) >= hpriv->xmitWin) { priv->stats.xmitDrops++; ERROUT(ENOBUFS); } } /* Sanity check frame length */ if (m->m_pkthdr.len > PPTP_MAX_PAYLOAD) { priv->stats.xmitTooBig++; ERROUT(EMSGSIZE); } } else { priv->stats.xmitLoneAcks++; } /* Build GRE header */ be32enc(gre, PPTP_INIT_VALUE); be16enc(&gre->length, (m != NULL) ? m->m_pkthdr.len : 0); be16enc(&gre->cid, hpriv->conf.peerCid); /* Include sequence number if packet contains any data */ if (m != NULL) { gre->hasSeq = 1; if (hpriv->conf.enableWindowing) { hpriv->timeSent[hpriv->xmitSeq - hpriv->recvAck] = ng_pptpgre_time(); } hpriv->xmitSeq++; be32enc(&gre->data[0], hpriv->xmitSeq); } /* Include acknowledgement (and stop send ack timer) if needed */ if (hpriv->conf.enableAlwaysAck || hpriv->xmitAck != hpriv->recvSeq) { gre->hasAck = 1; be32enc(&gre->data[gre->hasSeq], hpriv->recvSeq); hpriv->xmitAck = hpriv->recvSeq; if (hpriv->conf.enableDelayedAck) ng_uncallout(&hpriv->sackTimer, hpriv->node); } /* Prepend GRE header to outgoing frame */ grelen = sizeof(*gre) + sizeof(u_int32_t) * (gre->hasSeq + gre->hasAck); if (m == NULL) { MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) { priv->stats.memoryFailures++; ERROUT(ENOBUFS); } m->m_len = m->m_pkthdr.len = grelen; m->m_pkthdr.rcvif = NULL; } else { M_PREPEND(m, grelen, M_NOWAIT); if (m == NULL || (m->m_len < grelen && (m = m_pullup(m, grelen)) == NULL)) { priv->stats.memoryFailures++; ERROUT(ENOBUFS); } } bcopy(gre, mtod(m, u_char *), grelen); /* Update stats */ priv->stats.xmitPackets++; priv->stats.xmitOctets += m->m_pkthdr.len; /* * XXX: we should reset timer only after an item has been sent * successfully. */ if (hpriv->conf.enableWindowing && gre->hasSeq && hpriv->xmitSeq == hpriv->recvAck + 1) ng_pptpgre_start_recv_ack_timer(hpriv); mtx_unlock(&hpriv->mtx); /* Deliver packet */ if (item) { NG_FWD_NEW_DATA(error, item, priv->lower, m); } else { NG_SEND_DATA_ONLY(error, priv->lower, m); } return (error); done: mtx_unlock(&hpriv->mtx); NG_FREE_M(m); if (item) NG_FREE_ITEM(item); return (error); } static void ng_pptpgre_ack(const hpriv_p hpriv) { mtx_assert(&hpriv->mtx, MA_OWNED); if (!(callout_pending(&hpriv->sackTimer))) { /* If delayed ACK is disabled, send it now */ if (!hpriv->conf.enableDelayedAck) { /* ack now */ ng_pptpgre_xmit(hpriv, NULL); /* ng_pptpgre_xmit() drops the mutex */ return; } /* ack later */ ng_pptpgre_start_send_ack_timer(hpriv); mtx_unlock(&hpriv->mtx); return; } mtx_unlock(&hpriv->mtx); } /* * Delivers packets from the queue "q" to upper layers. Frees delivered * entries with the exception of one equal to "st" that is allocated * on caller's stack and not on the heap. */ static int ng_pptpgre_sendq(const hpriv_p hpriv, roqh *q, const struct ng_pptpgre_roq *st) { struct ng_pptpgre_roq *np; struct mbuf *m; int error = 0; mtx_assert(&hpriv->mtx, MA_NOTOWNED); while (!SLIST_EMPTY(q)) { np = SLIST_FIRST(q); SLIST_REMOVE_HEAD(q, next); NGI_GET_M(np->item, m); NG_FWD_NEW_DATA(error, np->item, hpriv->hook, m); if (np != st) - free(np, M_NETGRAPH); + free(np, M_NETGRAPH_PPTP); } return (error); } /* * Handle an incoming packet. The packet includes the IP header. */ static int ng_pptpgre_rcvdata_lower(hook_p hook, item_p item) { hpriv_p hpriv; node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); int iphlen, grelen, extralen; const struct greheader *gre; const struct ip *ip; int error = 0; struct mbuf *m; roqh sendq = SLIST_HEAD_INITIALIZER(sendq); /* send queue on stack */ struct ng_pptpgre_roq *last = NULL; /* last packet in the sendq */ struct ng_pptpgre_roq *np, *prev; struct ng_pptpgre_roq temp = { { NULL }, NULL, 0 }; long diff; u_int32_t seq; m = NGI_M(item); /* Update stats */ priv->stats.recvPackets++; priv->stats.recvOctets += m->m_pkthdr.len; /* Sanity check packet length */ if (m->m_pkthdr.len < sizeof(*ip) + sizeof(*gre)) { priv->stats.recvRunts++; ERROUT(EINVAL); } /* Safely pull up the complete IP+GRE headers */ if (m->m_len < sizeof(*ip) + sizeof(*gre)) { if ((m = m_pullup(m, sizeof(*ip) + sizeof(*gre))) == NULL) { priv->stats.memoryFailures++; _NGI_M(item) = NULL; ERROUT(ENOBUFS); } _NGI_M(item) = m; } ip = mtod(m, const struct ip *); iphlen = ip->ip_hl << 2; if (m->m_len < iphlen + sizeof(*gre)) { if ((m = m_pullup(m, iphlen + sizeof(*gre))) == NULL) { priv->stats.memoryFailures++; _NGI_M(item) = NULL; ERROUT(ENOBUFS); } _NGI_M(item) = m; ip = mtod(m, const struct ip *); } gre = (const struct greheader *)((const u_char *)ip + iphlen); grelen = sizeof(*gre) + sizeof(u_int32_t) * (gre->hasSeq + gre->hasAck); if (m->m_pkthdr.len < iphlen + grelen) { priv->stats.recvRunts++; ERROUT(EINVAL); } if (m->m_len < iphlen + grelen) { if ((m = m_pullup(m, iphlen + grelen)) == NULL) { priv->stats.memoryFailures++; _NGI_M(item) = NULL; ERROUT(ENOBUFS); } _NGI_M(item) = m; ip = mtod(m, const struct ip *); gre = (const struct greheader *)((const u_char *)ip + iphlen); } /* Sanity check packet length and GRE header bits */ extralen = m->m_pkthdr.len - (iphlen + grelen + gre->hasSeq * be16dec(&gre->length)); if (extralen < 0) { priv->stats.recvBadGRE++; ERROUT(EINVAL); } if ((be32dec(gre) & PPTP_INIT_MASK) != PPTP_INIT_VALUE) { priv->stats.recvBadGRE++; ERROUT(EINVAL); } hpriv = ng_pptpgre_find_session(priv, be16dec(&gre->cid)); if (hpriv == NULL || hpriv->hook == NULL || !hpriv->conf.enabled) { priv->stats.recvBadCID++; ERROUT(EINVAL); } mtx_lock(&hpriv->mtx); /* Look for peer ack */ if (gre->hasAck) { const u_int32_t ack = be32dec(&gre->data[gre->hasSeq]); const int index = ack - hpriv->recvAck - 1; long sample; /* Sanity check ack value */ if (PPTP_SEQ_DIFF(ack, hpriv->xmitSeq) > 0) { priv->stats.recvBadAcks++; goto badAck; /* we never sent it! */ } if (PPTP_SEQ_DIFF(ack, hpriv->recvAck) <= 0) goto badAck; /* ack already timed out */ hpriv->recvAck = ack; /* Update adaptive timeout stuff */ if (hpriv->conf.enableWindowing) { sample = ng_pptpgre_time() - hpriv->timeSent[index]; diff = sample - hpriv->rtt; hpriv->rtt += PPTP_ACK_ALPHA(diff); if (diff < 0) diff = -diff; hpriv->dev += PPTP_ACK_BETA(diff - hpriv->dev); /* +2 to compensate low precision of int math */ hpriv->ato = hpriv->rtt + PPTP_ACK_CHI(hpriv->dev + 2); if (hpriv->ato > PPTP_MAX_TIMEOUT) hpriv->ato = PPTP_MAX_TIMEOUT; else if (hpriv->ato < PPTP_MIN_TIMEOUT) hpriv->ato = PPTP_MIN_TIMEOUT; /* Shift packet transmit times in our transmit window */ bcopy(hpriv->timeSent + index + 1, hpriv->timeSent, sizeof(*hpriv->timeSent) * (PPTP_XMIT_WIN - (index + 1))); /* If we sent an entire window, increase window size */ if (PPTP_SEQ_DIFF(ack, hpriv->winAck) >= 0 && hpriv->xmitWin < PPTP_XMIT_WIN) { hpriv->xmitWin++; hpriv->winAck = ack + hpriv->xmitWin; } /* Stop/(re)start receive ACK timer as necessary */ ng_uncallout(&hpriv->rackTimer, hpriv->node); if (hpriv->recvAck != hpriv->xmitSeq) ng_pptpgre_start_recv_ack_timer(hpriv); } } badAck: /* See if frame contains any data */ if (!gre->hasSeq) { /* no data to deliver */ priv->stats.recvLoneAcks++; mtx_unlock(&hpriv->mtx); ERROUT(0); } seq = be32dec(&gre->data[0]); diff = PPTP_SEQ_DIFF(seq, hpriv->recvSeq); if (diff <= 0) { /* late or duplicate packet */ if (diff < 0 && reorder_max == 0) /* reorder disabled */ priv->stats.recvOutOfOrder++; /* late */ else priv->stats.recvDuplicates++; /* duplicate */ mtx_unlock(&hpriv->mtx); ERROUT(EINVAL); } /* Trim mbuf down to internal payload */ m_adj(m, iphlen + grelen); if (extralen > 0) m_adj(m, -extralen); #define INIT_SENDQ(t) do { \ t.item = item; \ t.seq = seq; \ SLIST_INSERT_HEAD(&sendq, &t, next); \ last = &t; \ hpriv->recvSeq = seq; \ goto deliver; \ } while(0) if (diff == 1) /* the packet came in order, place it at the start of sendq */ INIT_SENDQ(temp); /* The packet came too early, try to enqueue it. * * Check for duplicate in the queue. After this loop, "prev" will be * NULL if the packet should become new head of the queue, * or else it should be inserted after the "prev". */ prev = SLIST_FIRST(&hpriv->roq); SLIST_FOREACH(np, &hpriv->roq, next) { diff = PPTP_SEQ_DIFF(np->seq, seq); if (diff == 0) { /* do not add duplicate, drop it */ priv->stats.recvDuplicates++; mtx_unlock(&hpriv->mtx); ERROUT(EINVAL); } if (diff > 0) { /* we found newer packet */ if (np == prev) /* that is the head of the queue */ prev = NULL; /* put current packet to the head */ break; } prev = np; } priv->stats.recvOutOfOrder++; /* duplicate not found */ if (hpriv->roq_len < reorder_max) goto enqueue; /* reorder enabled and there is a room */ /* * There is no room in the queue or reorder disabled. * * It the latter case, we may still have non-empty reorder queue * if reorder was disabled in process of reordering. * Then we correctly deliver the queue without growing it. * * In both cases, no malloc()'s until the queue is shortened. */ priv->stats.recvReorderOverflow++; if (prev == NULL) { /* new packet goes before the head */ INIT_SENDQ(temp); /* of reorder queue, so put it to sendq */ } #undef INIT_SENDQ /* * Current packet goes after the head of reorder queue. * Move the head to sendq to make room for current packet. */ np = SLIST_FIRST(&hpriv->roq); if (prev == np) prev = NULL; SLIST_REMOVE_HEAD(&hpriv->roq, next); hpriv->roq_len--; /* we are allowed to use malloc() now */ SLIST_INSERT_HEAD(&sendq, np, next); last = np; hpriv->recvSeq = np->seq; enqueue: - np = malloc(sizeof(*np), M_NETGRAPH, M_NOWAIT | M_ZERO); + np = malloc(sizeof(*np), M_NETGRAPH_PPTP, M_NOWAIT | M_ZERO); if (np == NULL) { priv->stats.memoryFailures++; /* * Emergency: we cannot save new data. * Flush the queue delivering all queued packets preceeding * current one despite of gaps. */ while (!SLIST_EMPTY(&hpriv->roq)) { np = SLIST_FIRST(&hpriv->roq); if (np->seq > seq) break; SLIST_REMOVE_HEAD(&hpriv->roq, next); hpriv->roq_len--; if (last == NULL) SLIST_INSERT_HEAD(&sendq, np, next); else SLIST_INSERT_AFTER(last, np, next); last = np; } /* * Pretend we got all packets till the current one * and acknowledge it. */ hpriv->recvSeq = seq; ng_pptpgre_ack(hpriv); /* drops lock */ ng_pptpgre_sendq(hpriv, &sendq, &temp); NG_FWD_NEW_DATA(error, item, hpriv->hook, m); ERROUT(ENOMEM); } /* Add current (early) packet to the reorder queue. */ np->item = item; np->seq = seq; if (prev == NULL) SLIST_INSERT_HEAD(&hpriv->roq, np, next); else SLIST_INSERT_AFTER(prev, np, next); hpriv->roq_len++; deliver: /* Look if we have some packets in sequence after sendq. */ while (!SLIST_EMPTY(&hpriv->roq)) { np = SLIST_FIRST(&hpriv->roq); if (PPTP_SEQ_DIFF(np->seq, hpriv->recvSeq) > 1) break; /* the gap in the sequence */ /* "np" is in sequence, move it to the sendq. */ SLIST_REMOVE_HEAD(&hpriv->roq, next); hpriv->roq_len--; hpriv->recvSeq = np->seq; if (last == NULL) SLIST_INSERT_HEAD(&sendq, np, next); else SLIST_INSERT_AFTER(last, np, next); last = np; } if (SLIST_EMPTY(&hpriv->roq)) { if (callout_pending(&hpriv->reorderTimer)) ng_uncallout(&hpriv->reorderTimer, hpriv->node); } else { if (!callout_pending(&hpriv->reorderTimer)) ng_pptpgre_start_reorder_timer(hpriv); } if (SLIST_EMPTY(&sendq)) { /* Current packet has been queued, nothing to free/deliver. */ mtx_unlock(&hpriv->mtx); return (error); } /* We need to acknowledge last packet; do it soon... */ ng_pptpgre_ack(hpriv); /* drops lock */ ng_pptpgre_sendq(hpriv, &sendq, &temp); return (error); done: NG_FREE_ITEM(item); return (error); } /************************************************************************* TIMER RELATED FUNCTIONS *************************************************************************/ /* * Start a timer for the peer's acknowledging our oldest unacknowledged * sequence number. If we get an ack for this sequence number before * the timer goes off, we cancel the timer. Resets currently running * recv ack timer, if any. */ static void ng_pptpgre_start_recv_ack_timer(hpriv_p hpriv) { int remain, ticks; /* Compute how long until oldest unack'd packet times out, and reset the timer to that time. */ remain = (hpriv->timeSent[0] + hpriv->ato) - ng_pptpgre_time(); if (remain < 0) remain = 0; /* Be conservative: timeout can happen up to 1 tick early */ ticks = howmany(remain * hz, PPTP_TIME_SCALE) + 1; ng_callout(&hpriv->rackTimer, hpriv->node, hpriv->hook, ticks, ng_pptpgre_recv_ack_timeout, hpriv, 0); } /* * The peer has failed to acknowledge the oldest unacknowledged sequence * number within the time allotted. Update our adaptive timeout parameters * and reset/restart the recv ack timer. */ static void ng_pptpgre_recv_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2) { const priv_p priv = NG_NODE_PRIVATE(node); const hpriv_p hpriv = arg1; /* Update adaptive timeout stuff */ priv->stats.recvAckTimeouts++; hpriv->rtt = PPTP_ACK_DELTA(hpriv->rtt) + 1; /* +1 to avoid delta*0 case */ hpriv->ato = hpriv->rtt + PPTP_ACK_CHI(hpriv->dev); if (hpriv->ato > PPTP_MAX_TIMEOUT) hpriv->ato = PPTP_MAX_TIMEOUT; else if (hpriv->ato < PPTP_MIN_TIMEOUT) hpriv->ato = PPTP_MIN_TIMEOUT; /* Reset ack and sliding window */ hpriv->recvAck = hpriv->xmitSeq; /* pretend we got the ack */ hpriv->xmitWin = (hpriv->xmitWin + 1) / 2; /* shrink transmit window */ hpriv->winAck = hpriv->recvAck + hpriv->xmitWin; /* reset win expand time */ } /* * Start the send ack timer. This assumes the timer is not * already running. */ static void ng_pptpgre_start_send_ack_timer(hpriv_p hpriv) { int ackTimeout, ticks; /* Take 1/4 of the estimated round trip time */ ackTimeout = (hpriv->rtt >> 2); if (ackTimeout < PPTP_MIN_ACK_DELAY) ackTimeout = PPTP_MIN_ACK_DELAY; else if (ackTimeout > PPTP_MAX_ACK_DELAY) ackTimeout = PPTP_MAX_ACK_DELAY; /* Be conservative: timeout can happen up to 1 tick early */ ticks = howmany(ackTimeout * hz, PPTP_TIME_SCALE); ng_callout(&hpriv->sackTimer, hpriv->node, hpriv->hook, ticks, ng_pptpgre_send_ack_timeout, hpriv, 0); } /* * We've waited as long as we're willing to wait before sending an * acknowledgement to the peer for received frames. We had hoped to * be able to piggy back our acknowledgement on an outgoing data frame, * but apparently there haven't been any since. So send the ack now. */ static void ng_pptpgre_send_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2) { const hpriv_p hpriv = arg1; mtx_lock(&hpriv->mtx); /* Send a frame with an ack but no payload */ ng_pptpgre_xmit(hpriv, NULL); mtx_assert(&hpriv->mtx, MA_NOTOWNED); } /* * Start a timer for the reorder queue. This assumes the timer is not * already running. */ static void ng_pptpgre_start_reorder_timer(hpriv_p hpriv) { int ticks; /* Be conservative: timeout can happen up to 1 tick early */ ticks = (((reorder_timeout * hz) + 1000 - 1) / 1000) + 1; ng_callout(&hpriv->reorderTimer, hpriv->node, hpriv->hook, ticks, ng_pptpgre_reorder_timeout, hpriv, 0); } /* * The oldest packet spent too much time in the reorder queue. * Deliver it and next packets in sequence, if any. */ static void ng_pptpgre_reorder_timeout(node_p node, hook_p hook, void *arg1, int arg2) { const priv_p priv = NG_NODE_PRIVATE(node); const hpriv_p hpriv = arg1; roqh sendq = SLIST_HEAD_INITIALIZER(sendq); struct ng_pptpgre_roq *np, *last = NULL; priv->stats.recvReorderTimeouts++; mtx_lock(&hpriv->mtx); if (SLIST_EMPTY(&hpriv->roq)) { /* should not happen */ mtx_unlock(&hpriv->mtx); return; } last = np = SLIST_FIRST(&hpriv->roq); hpriv->roq_len--; SLIST_REMOVE_HEAD(&hpriv->roq, next); SLIST_INSERT_HEAD(&sendq, np, next); /* Look if we have more packets in sequence */ while (!SLIST_EMPTY(&hpriv->roq)) { np = SLIST_FIRST(&hpriv->roq); if (PPTP_SEQ_DIFF(np->seq, last->seq) > 1) break; /* the gap in the sequence */ /* Next packet is in sequence, move it to the sendq. */ hpriv->roq_len--; SLIST_REMOVE_HEAD(&hpriv->roq, next); SLIST_INSERT_AFTER(last, np, next); last = np; } hpriv->recvSeq = last->seq; if (!SLIST_EMPTY(&hpriv->roq)) ng_pptpgre_start_reorder_timer(hpriv); /* We need to acknowledge last packet; do it soon... */ ng_pptpgre_ack(hpriv); /* drops lock */ ng_pptpgre_sendq(hpriv, &sendq, NULL); mtx_assert(&hpriv->mtx, MA_NOTOWNED); } /************************************************************************* MISC FUNCTIONS *************************************************************************/ /* * Find the hook with a given session ID. */ static hpriv_p ng_pptpgre_find_session(priv_p privp, u_int16_t cid) { uint16_t hash = SESSHASH(cid); hpriv_p hpriv = NULL; LIST_FOREACH(hpriv, &privp->sesshash[hash], sessions) { if (hpriv->conf.cid == cid) break; } return (hpriv); } /* * Reset state (must be called with lock held or from writer) */ static void ng_pptpgre_reset(hpriv_p hpriv) { struct ng_pptpgre_roq *np; /* Reset adaptive timeout state */ hpriv->ato = PPTP_MAX_TIMEOUT; hpriv->rtt = PPTP_TIME_SCALE / 10; if (hpriv->conf.peerPpd > 1) /* ppd = 0 treat as = 1 */ hpriv->rtt *= hpriv->conf.peerPpd; hpriv->dev = 0; hpriv->xmitWin = (hpriv->conf.recvWin + 1) / 2; if (hpriv->xmitWin < 2) /* often the first packet is lost */ hpriv->xmitWin = 2; /* because the peer isn't ready */ else if (hpriv->xmitWin > PPTP_XMIT_WIN) hpriv->xmitWin = PPTP_XMIT_WIN; hpriv->winAck = hpriv->xmitWin; /* Reset sequence numbers */ hpriv->recvSeq = ~0; hpriv->recvAck = ~0; hpriv->xmitSeq = ~0; hpriv->xmitAck = ~0; /* Stop timers */ ng_uncallout(&hpriv->sackTimer, hpriv->node); ng_uncallout(&hpriv->rackTimer, hpriv->node); ng_uncallout(&hpriv->reorderTimer, hpriv->node); /* Clear reorder queue */ while (!SLIST_EMPTY(&hpriv->roq)) { np = SLIST_FIRST(&hpriv->roq); SLIST_REMOVE_HEAD(&hpriv->roq, next); NG_FREE_ITEM(np->item); - free(np, M_NETGRAPH); + free(np, M_NETGRAPH_PPTP); } hpriv->roq_len = 0; } /* * Return the current time scaled & translated to our internally used format. */ static pptptime_t ng_pptpgre_time(void) { struct timeval tv; pptptime_t t; microuptime(&tv); t = (pptptime_t)tv.tv_sec * PPTP_TIME_SCALE; t += tv.tv_usec / (1000000 / PPTP_TIME_SCALE); return(t); } diff --git a/sys/netgraph/ng_tcpmss.c b/sys/netgraph/ng_tcpmss.c index 3c5b4051ed92..02fa097369ad 100644 --- a/sys/netgraph/ng_tcpmss.c +++ b/sys/netgraph/ng_tcpmss.c @@ -1,445 +1,451 @@ /*- * ng_tcpmss.c * * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2004, Alexey Popov * 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 unmodified, 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. * * This software includes fragments of the following programs: * tcpmssd Ruslan Ermilov */ /* * This node is netgraph tool for workaround of PMTUD problem. It acts * like filter for IP packets. If configured, it reduces MSS of TCP SYN * packets. * * Configuration can be done by sending NGM_TCPMSS_CONFIG message. The * message sets filter for incoming packets on hook 'inHook'. Packet's * TCP MSS field is lowered to 'maxMSS' parameter and resulting packet * is sent to 'outHook'. * * XXX: statistics are updated not atomically, so they may broke on SMP. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#ifdef NG_SEPARATE_MALLOC +static MALLOC_DEFINE(M_NETGRAPH_TCPMSS, "netgraph_tcpmss", "netgraph tcpmss node"); +#else +#define M_NETGRAPH_TCPMSS M_NETGRAPH +#endif + /* Per hook info. */ typedef struct { hook_p outHook; struct ng_tcpmss_hookstat stats; } *hpriv_p; /* Netgraph methods. */ static ng_constructor_t ng_tcpmss_constructor; static ng_rcvmsg_t ng_tcpmss_rcvmsg; static ng_newhook_t ng_tcpmss_newhook; static ng_rcvdata_t ng_tcpmss_rcvdata; static ng_disconnect_t ng_tcpmss_disconnect; static int correct_mss(struct tcphdr *, int, uint16_t, int); /* Parse type for struct ng_tcpmss_hookstat. */ static const struct ng_parse_struct_field ng_tcpmss_hookstat_type_fields[] = NG_TCPMSS_HOOKSTAT_INFO; static const struct ng_parse_type ng_tcpmss_hookstat_type = { &ng_parse_struct_type, &ng_tcpmss_hookstat_type_fields }; /* Parse type for struct ng_tcpmss_config. */ static const struct ng_parse_struct_field ng_tcpmss_config_type_fields[] = NG_TCPMSS_CONFIG_INFO; static const struct ng_parse_type ng_tcpmss_config_type = { &ng_parse_struct_type, ng_tcpmss_config_type_fields }; /* List of commands and how to convert arguments to/from ASCII. */ static const struct ng_cmdlist ng_tcpmss_cmds[] = { { NGM_TCPMSS_COOKIE, NGM_TCPMSS_GET_STATS, "getstats", &ng_parse_hookbuf_type, &ng_tcpmss_hookstat_type }, { NGM_TCPMSS_COOKIE, NGM_TCPMSS_CLR_STATS, "clrstats", &ng_parse_hookbuf_type, NULL }, { NGM_TCPMSS_COOKIE, NGM_TCPMSS_GETCLR_STATS, "getclrstats", &ng_parse_hookbuf_type, &ng_tcpmss_hookstat_type }, { NGM_TCPMSS_COOKIE, NGM_TCPMSS_CONFIG, "config", &ng_tcpmss_config_type, NULL }, { 0 } }; /* Netgraph type descriptor. */ static struct ng_type ng_tcpmss_typestruct = { .version = NG_ABI_VERSION, .name = NG_TCPMSS_NODE_TYPE, .constructor = ng_tcpmss_constructor, .rcvmsg = ng_tcpmss_rcvmsg, .newhook = ng_tcpmss_newhook, .rcvdata = ng_tcpmss_rcvdata, .disconnect = ng_tcpmss_disconnect, .cmdlist = ng_tcpmss_cmds, }; NETGRAPH_INIT(tcpmss, &ng_tcpmss_typestruct); #define ERROUT(x) { error = (x); goto done; } /* * Node constructor. No special actions required. */ static int ng_tcpmss_constructor(node_p node) { return (0); } /* * Add a hook. Any unique name is OK. */ static int ng_tcpmss_newhook(node_p node, hook_p hook, const char *name) { hpriv_p priv; - priv = malloc(sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); + priv = malloc(sizeof(*priv), M_NETGRAPH_TCPMSS, M_NOWAIT | M_ZERO); if (priv == NULL) return (ENOMEM); NG_HOOK_SET_PRIVATE(hook, priv); return (0); } /* * Receive a control message. */ static int ng_tcpmss_rcvmsg (node_p node, item_p item, hook_p lasthook) { struct ng_mesg *msg, *resp = NULL; int error = 0; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_TCPMSS_COOKIE: switch (msg->header.cmd) { case NGM_TCPMSS_GET_STATS: case NGM_TCPMSS_CLR_STATS: case NGM_TCPMSS_GETCLR_STATS: { hook_p hook; hpriv_p priv; /* Check that message is long enough. */ if (msg->header.arglen != NG_HOOKSIZ) ERROUT(EINVAL); /* Find this hook. */ hook = ng_findhook(node, (char *)msg->data); if (hook == NULL) ERROUT(ENOENT); priv = NG_HOOK_PRIVATE(hook); /* Create response. */ if (msg->header.cmd != NGM_TCPMSS_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(struct ng_tcpmss_hookstat), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); bcopy(&priv->stats, resp->data, sizeof(struct ng_tcpmss_hookstat)); } if (msg->header.cmd != NGM_TCPMSS_GET_STATS) bzero(&priv->stats, sizeof(struct ng_tcpmss_hookstat)); break; } case NGM_TCPMSS_CONFIG: { struct ng_tcpmss_config *set; hook_p in, out; hpriv_p priv; /* Check that message is long enough. */ if (msg->header.arglen != sizeof(struct ng_tcpmss_config)) ERROUT(EINVAL); set = (struct ng_tcpmss_config *)msg->data; in = ng_findhook(node, set->inHook); out = ng_findhook(node, set->outHook); if (in == NULL || out == NULL) ERROUT(ENOENT); /* Configure MSS hack. */ priv = NG_HOOK_PRIVATE(in); priv->outHook = out; priv->stats.maxMSS = set->maxMSS; break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook, and hack MSS. * */ static int ng_tcpmss_rcvdata(hook_p hook, item_p item) { hpriv_p priv = NG_HOOK_PRIVATE(hook); struct mbuf *m = NULL; struct ip *ip; struct tcphdr *tcp; int iphlen, tcphlen, pktlen; int pullup_len = 0; int error = 0; /* Drop packets if filter is not configured on this hook. */ if (priv->outHook == NULL) goto done; NGI_GET_M(item, m); /* Update stats on incoming hook. */ pktlen = m->m_pkthdr.len; priv->stats.Octets += pktlen; priv->stats.Packets++; /* Check whether we configured to fix MSS. */ if (priv->stats.maxMSS == 0) goto send; #define M_CHECK(length) do { \ pullup_len += length; \ if ((m)->m_pkthdr.len < pullup_len) \ goto send; \ if ((m)->m_len < pullup_len && \ (((m) = m_pullup((m), pullup_len)) == NULL)) \ ERROUT(ENOBUFS); \ } while (0) /* Check mbuf packet size and arrange for IP header. */ M_CHECK(sizeof(struct ip)); ip = mtod(m, struct ip *); /* Check IP version. */ if (ip->ip_v != IPVERSION) ERROUT(EINVAL); /* Check IP header length. */ iphlen = ip->ip_hl << 2; if (iphlen < sizeof(struct ip) || iphlen > pktlen ) ERROUT(EINVAL); /* Check if it is TCP. */ if (!(ip->ip_p == IPPROTO_TCP)) goto send; /* Check mbuf packet size and arrange for IP+TCP header */ M_CHECK(iphlen - sizeof(struct ip) + sizeof(struct tcphdr)); ip = mtod(m, struct ip *); tcp = (struct tcphdr *)((caddr_t )ip + iphlen); /* Check TCP header length. */ tcphlen = tcp->th_off << 2; if (tcphlen < sizeof(struct tcphdr) || tcphlen > pktlen - iphlen) ERROUT(EINVAL); /* Check SYN packet and has options. */ if (!(tcp->th_flags & TH_SYN) || tcphlen == sizeof(struct tcphdr)) goto send; /* Update SYN stats. */ priv->stats.SYNPkts++; M_CHECK(tcphlen - sizeof(struct tcphdr)); ip = mtod(m, struct ip *); tcp = (struct tcphdr *)((caddr_t )ip + iphlen); #undef M_CHECK /* Fix MSS and update stats. */ if (correct_mss(tcp, tcphlen, priv->stats.maxMSS, m->m_pkthdr.csum_flags)) priv->stats.FixedPkts++; send: /* Deliver frame out destination hook. */ NG_FWD_NEW_DATA(error, item, priv->outHook, m); return (error); done: NG_FREE_ITEM(item); NG_FREE_M(m); return (error); } /* * Hook disconnection. * We must check all hooks, since they may reference this one. */ static int ng_tcpmss_disconnect(hook_p hook) { node_p node = NG_HOOK_NODE(hook); hook_p hook2; LIST_FOREACH(hook2, &node->nd_hooks, hk_hooks) { hpriv_p priv = NG_HOOK_PRIVATE(hook2); if (priv->outHook == hook) priv->outHook = NULL; } - free(NG_HOOK_PRIVATE(hook), M_NETGRAPH); + free(NG_HOOK_PRIVATE(hook), M_NETGRAPH_TCPMSS); if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } /* * Code from tcpmssd. */ /*- * The following macro is used to update an * internet checksum. "acc" is a 32-bit * accumulation of all the changes to the * checksum (adding in old 16-bit words and * subtracting out new words), and "cksum" * is the checksum value to be updated. */ #define TCPMSS_ADJUST_CHECKSUM(acc, cksum) do { \ acc += cksum; \ if (acc < 0) { \ acc = -acc; \ acc = (acc >> 16) + (acc & 0xffff); \ acc += acc >> 16; \ cksum = (u_short) ~acc; \ } else { \ acc = (acc >> 16) + (acc & 0xffff); \ acc += acc >> 16; \ cksum = (u_short) acc; \ } \ } while (0); static int correct_mss(struct tcphdr *tc, int hlen, uint16_t maxmss, int flags) { int olen, optlen; u_char *opt; int accumulate; int res = 0; uint16_t sum; for (olen = hlen - sizeof(struct tcphdr), opt = (u_char *)(tc + 1); olen > 0; olen -= optlen, opt += optlen) { if (*opt == TCPOPT_EOL) break; else if (*opt == TCPOPT_NOP) optlen = 1; else { optlen = *(opt + 1); if (optlen <= 0 || optlen > olen) break; if (*opt == TCPOPT_MAXSEG) { if (optlen != TCPOLEN_MAXSEG) continue; accumulate = be16dec(opt + 2); if (accumulate > maxmss) { if ((flags & CSUM_TCP) == 0) { accumulate -= maxmss; sum = be16dec(&tc->th_sum); TCPMSS_ADJUST_CHECKSUM(accumulate, sum); be16enc(&tc->th_sum, sum); } be16enc(opt + 2, maxmss); res = 1; } } } } return (res); } diff --git a/sys/netgraph/ng_tee.c b/sys/netgraph/ng_tee.c index 391355cd756a..b2cceda29a40 100644 --- a/sys/netgraph/ng_tee.c +++ b/sys/netgraph/ng_tee.c @@ -1,393 +1,399 @@ /* * ng_tee.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Julian Elischer * $Whistle: ng_tee.c,v 1.18 1999/11/01 09:24:52 julian Exp $ */ /* * This node is like the tee(1) command and is useful for ``snooping.'' * It has 4 hooks: left, right, left2right, and right2left. Data * entering from the right is passed to the left and duplicated on * right2left, and data entering from the left is passed to the right * and duplicated on left2right. Data entering from left2right is * sent to left, and data from right2left to right. */ #include #include #include #include #include #include #include #include #include #include +#ifdef NG_SEPARATE_MALLOC +static MALLOC_DEFINE(M_NETGRAPH_TEE, "netgraph_tee", "netgraph tee node"); +#else +#define M_NETGRAPH_TEE M_NETGRAPH +#endif + /* Per hook info */ struct hookinfo { hook_p hook; struct hookinfo *dest, *dup; struct ng_tee_hookstat stats; }; typedef struct hookinfo *hi_p; /* Per node info */ struct privdata { struct hookinfo left; struct hookinfo right; struct hookinfo left2right; struct hookinfo right2left; }; typedef struct privdata *sc_p; /* Netgraph methods */ static ng_constructor_t ng_tee_constructor; static ng_rcvmsg_t ng_tee_rcvmsg; static ng_close_t ng_tee_close; static ng_shutdown_t ng_tee_shutdown; static ng_newhook_t ng_tee_newhook; static ng_rcvdata_t ng_tee_rcvdata; static ng_disconnect_t ng_tee_disconnect; /* Parse type for struct ng_tee_hookstat */ static const struct ng_parse_struct_field ng_tee_hookstat_type_fields[] = NG_TEE_HOOKSTAT_INFO; static const struct ng_parse_type ng_tee_hookstat_type = { &ng_parse_struct_type, &ng_tee_hookstat_type_fields }; /* Parse type for struct ng_tee_stats */ static const struct ng_parse_struct_field ng_tee_stats_type_fields[] = NG_TEE_STATS_INFO(&ng_tee_hookstat_type); static const struct ng_parse_type ng_tee_stats_type = { &ng_parse_struct_type, &ng_tee_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_tee_cmds[] = { { NGM_TEE_COOKIE, NGM_TEE_GET_STATS, "getstats", NULL, &ng_tee_stats_type }, { NGM_TEE_COOKIE, NGM_TEE_CLR_STATS, "clrstats", NULL, NULL }, { NGM_TEE_COOKIE, NGM_TEE_GETCLR_STATS, "getclrstats", NULL, &ng_tee_stats_type }, { 0 } }; /* Netgraph type descriptor */ static struct ng_type ng_tee_typestruct = { .version = NG_ABI_VERSION, .name = NG_TEE_NODE_TYPE, .constructor = ng_tee_constructor, .rcvmsg = ng_tee_rcvmsg, .close = ng_tee_close, .shutdown = ng_tee_shutdown, .newhook = ng_tee_newhook, .rcvdata = ng_tee_rcvdata, .disconnect = ng_tee_disconnect, .cmdlist = ng_tee_cmds, }; NETGRAPH_INIT(tee, &ng_tee_typestruct); /* * Node constructor */ static int ng_tee_constructor(node_p node) { sc_p privdata; - privdata = malloc(sizeof(*privdata), M_NETGRAPH, M_WAITOK | M_ZERO); + privdata = malloc(sizeof(*privdata), M_NETGRAPH_TEE, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, privdata); return (0); } /* * Add a hook */ static int ng_tee_newhook(node_p node, hook_p hook, const char *name) { sc_p privdata = NG_NODE_PRIVATE(node); hi_p hinfo; /* Precalculate internal paths. */ if (strcmp(name, NG_TEE_HOOK_RIGHT) == 0) { hinfo = &privdata->right; if (privdata->left.dest) privdata->left.dup = privdata->left.dest; privdata->left.dest = hinfo; privdata->right2left.dest = hinfo; } else if (strcmp(name, NG_TEE_HOOK_LEFT) == 0) { hinfo = &privdata->left; if (privdata->right.dest) privdata->right.dup = privdata->right.dest; privdata->right.dest = hinfo; privdata->left2right.dest = hinfo; } else if (strcmp(name, NG_TEE_HOOK_RIGHT2LEFT) == 0) { hinfo = &privdata->right2left; if (privdata->right.dest) privdata->right.dup = hinfo; else privdata->right.dest = hinfo; } else if (strcmp(name, NG_TEE_HOOK_LEFT2RIGHT) == 0) { hinfo = &privdata->left2right; if (privdata->left.dest) privdata->left.dup = hinfo; else privdata->left.dest = hinfo; } else return (EINVAL); hinfo->hook = hook; bzero(&hinfo->stats, sizeof(hinfo->stats)); NG_HOOK_SET_PRIVATE(hook, hinfo); return (0); } /* * Receive a control message */ static int ng_tee_rcvmsg(node_p node, item_p item, hook_p lasthook) { const sc_p sc = 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_TEE_COOKIE: switch (msg->header.cmd) { case NGM_TEE_GET_STATS: case NGM_TEE_CLR_STATS: case NGM_TEE_GETCLR_STATS: { struct ng_tee_stats *stats; if (msg->header.cmd != NGM_TEE_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; goto done; } stats = (struct ng_tee_stats *)resp->data; bcopy(&sc->right.stats, &stats->right, sizeof(stats->right)); bcopy(&sc->left.stats, &stats->left, sizeof(stats->left)); bcopy(&sc->right2left.stats, &stats->right2left, sizeof(stats->right2left)); bcopy(&sc->left2right.stats, &stats->left2right, sizeof(stats->left2right)); } if (msg->header.cmd != NGM_TEE_GET_STATS) { bzero(&sc->right.stats, sizeof(sc->right.stats)); bzero(&sc->left.stats, sizeof(sc->left.stats)); bzero(&sc->right2left.stats, sizeof(sc->right2left.stats)); bzero(&sc->left2right.stats, sizeof(sc->left2right.stats)); } break; } default: error = EINVAL; break; } break; case NGM_FLOW_COOKIE: if (lasthook == sc->left.hook || lasthook == sc->right.hook) { hi_p const hinfo = NG_HOOK_PRIVATE(lasthook); if (hinfo && hinfo->dest) { NGI_MSG(item) = msg; NG_FWD_ITEM_HOOK(error, item, hinfo->dest->hook); return (error); } } break; default: error = EINVAL; break; } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook * * If data comes in the right link send a copy out right2left, and then * send the original onwards out through the left link. * Do the opposite for data coming in from the left link. * Data coming in right2left or left2right is forwarded * on through the appropriate destination hook as if it had come * from the other side. */ static int ng_tee_rcvdata(hook_p hook, item_p item) { const hi_p hinfo = NG_HOOK_PRIVATE(hook); hi_p h; int error = 0; struct mbuf *m; m = NGI_M(item); /* Update stats on incoming hook */ hinfo->stats.inOctets += m->m_pkthdr.len; hinfo->stats.inFrames++; /* Duplicate packet if requried */ if (hinfo->dup) { struct mbuf *m2; /* Copy packet (failure will not stop the original)*/ m2 = m_dup(m, M_NOWAIT); if (m2) { /* Deliver duplicate */ h = hinfo->dup; NG_SEND_DATA_ONLY(error, h->hook, m2); if (error == 0) { h->stats.outOctets += m->m_pkthdr.len; h->stats.outFrames++; } } } /* Deliver frame out destination hook */ if (hinfo->dest) { h = hinfo->dest; h->stats.outOctets += m->m_pkthdr.len; h->stats.outFrames++; NG_FWD_ITEM_HOOK(error, item, h->hook); } else NG_FREE_ITEM(item); return (error); } /* * We are going to be shut down soon * * If we have both a left and right hook, then we probably want to extricate * ourselves and leave the two peers still linked to each other. Otherwise we * should just shut down as a normal node would. */ static int ng_tee_close(node_p node) { const sc_p privdata = NG_NODE_PRIVATE(node); if (privdata->left.hook && privdata->right.hook) ng_bypass(privdata->left.hook, privdata->right.hook); return (0); } /* * Shutdown processing */ static int ng_tee_shutdown(node_p node) { const sc_p privdata = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); - free(privdata, M_NETGRAPH); + free(privdata, M_NETGRAPH_TEE); NG_NODE_UNREF(node); return (0); } /* * Hook disconnection */ static int ng_tee_disconnect(hook_p hook) { sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); hi_p const hinfo = NG_HOOK_PRIVATE(hook); KASSERT(hinfo != NULL, ("%s: null info", __func__)); hinfo->hook = NULL; /* Recalculate internal paths. */ if (sc->left.dest == hinfo) { sc->left.dest = sc->left.dup; sc->left.dup = NULL; } else if (sc->left.dup == hinfo) sc->left.dup = NULL; if (sc->right.dest == hinfo) { sc->right.dest = sc->right.dup; sc->right.dup = NULL; } else if (sc->right.dup == hinfo) sc->right.dup = NULL; if (sc->left2right.dest == hinfo) sc->left2right.dest = NULL; if (sc->right2left.dest == hinfo) sc->right2left.dest = NULL; /* Die when last hook disconnected. */ 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); }