Index: head/sys/netgraph/ng_bridge.c =================================================================== --- head/sys/netgraph/ng_bridge.c (revision 328023) +++ head/sys/netgraph/ng_bridge.c (revision 328024) @@ -1,1055 +1,1055 @@ /* * ng_bridge.c */ /*- * Copyright (c) 2000 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 * * $FreeBSD$ */ /* * ng_bridge(4) netgraph node type * * The node performs standard intelligent Ethernet bridging over * each of its connected hooks, or links. A simple loop detection * algorithm is included which disables a link for priv->conf.loopTimeout * seconds when a host is seen to have jumped from one link to * another within priv->conf.minStableAge seconds. * * We keep a hashtable that maps Ethernet addresses to host info, * which is contained in struct ng_bridge_host's. These structures * tell us on which link the host may be found. A host's entry will * expire after priv->conf.maxStaleness seconds. * * This node is optimzed for stable networks, where machines jump * from one port to the other only rarely. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 /* not used yet */ #include #endif #include #include #include #include #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_BRIDGE, "netgraph_bridge", "netgraph bridge node"); #else #define M_NETGRAPH_BRIDGE M_NETGRAPH #endif /* Per-link private data */ struct ng_bridge_link { hook_p hook; /* netgraph hook */ u_int16_t loopCount; /* loop ignore timer */ struct ng_bridge_link_stats stats; /* link stats */ }; /* Per-node private data */ struct ng_bridge_private { struct ng_bridge_bucket *tab; /* hash table bucket array */ struct ng_bridge_link *links[NG_BRIDGE_MAX_LINKS]; struct ng_bridge_config conf; /* node configuration */ node_p node; /* netgraph node */ u_int numHosts; /* num entries in table */ u_int numBuckets; /* num buckets in table */ u_int hashMask; /* numBuckets - 1 */ int numLinks; /* num connected links */ int persistent; /* can exist w/o hooks */ struct callout timer; /* one second periodic timer */ }; typedef struct ng_bridge_private *priv_p; /* Information about a host, stored in a hash table entry */ struct ng_bridge_hent { struct ng_bridge_host host; /* actual host info */ SLIST_ENTRY(ng_bridge_hent) next; /* next entry in bucket */ }; /* Hash table bucket declaration */ SLIST_HEAD(ng_bridge_bucket, ng_bridge_hent); /* Netgraph node methods */ static ng_constructor_t ng_bridge_constructor; static ng_rcvmsg_t ng_bridge_rcvmsg; static ng_shutdown_t ng_bridge_shutdown; static ng_newhook_t ng_bridge_newhook; static ng_rcvdata_t ng_bridge_rcvdata; static ng_disconnect_t ng_bridge_disconnect; /* Other internal functions */ static struct ng_bridge_host *ng_bridge_get(priv_p priv, const u_char *addr); static int ng_bridge_put(priv_p priv, const u_char *addr, int linkNum); static void ng_bridge_rehash(priv_p priv); static void ng_bridge_remove_hosts(priv_p priv, int linkNum); static void ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2); static const char *ng_bridge_nodename(node_p node); /* Ethernet broadcast */ static const u_char ng_bridge_bcast_addr[ETHER_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; /* Store each hook's link number in the private field */ #define LINK_NUM(hook) (*(u_int16_t *)(&(hook)->private)) /* Compare Ethernet addresses using 32 and 16 bit words instead of bytewise */ #define ETHER_EQUAL(a,b) (((const u_int32_t *)(a))[0] \ == ((const u_int32_t *)(b))[0] \ && ((const u_int16_t *)(a))[2] \ == ((const u_int16_t *)(b))[2]) /* Minimum and maximum number of hash buckets. Must be a power of two. */ #define MIN_BUCKETS (1 << 5) /* 32 */ #define MAX_BUCKETS (1 << 14) /* 16384 */ /* Configuration default values */ #define DEFAULT_LOOP_TIMEOUT 60 #define DEFAULT_MAX_STALENESS (15 * 60) /* same as ARP timeout */ #define DEFAULT_MIN_STABLE_AGE 1 /****************************************************************** NETGRAPH PARSE TYPES ******************************************************************/ /* * How to determine the length of the table returned by NGM_BRIDGE_GET_TABLE */ static int ng_bridge_getTableLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct ng_bridge_host_ary *const hary = (const struct ng_bridge_host_ary *)(buf - sizeof(u_int32_t)); return hary->numHosts; } /* Parse type for struct ng_bridge_host_ary */ static const struct ng_parse_struct_field ng_bridge_host_type_fields[] = NG_BRIDGE_HOST_TYPE_INFO(&ng_parse_enaddr_type); static const struct ng_parse_type ng_bridge_host_type = { &ng_parse_struct_type, &ng_bridge_host_type_fields }; static const struct ng_parse_array_info ng_bridge_hary_type_info = { &ng_bridge_host_type, ng_bridge_getTableLength }; static const struct ng_parse_type ng_bridge_hary_type = { &ng_parse_array_type, &ng_bridge_hary_type_info }; static const struct ng_parse_struct_field ng_bridge_host_ary_type_fields[] = NG_BRIDGE_HOST_ARY_TYPE_INFO(&ng_bridge_hary_type); static const struct ng_parse_type ng_bridge_host_ary_type = { &ng_parse_struct_type, &ng_bridge_host_ary_type_fields }; /* Parse type for struct ng_bridge_config */ static const struct ng_parse_fixedarray_info ng_bridge_ipfwary_type_info = { &ng_parse_uint8_type, NG_BRIDGE_MAX_LINKS }; static const struct ng_parse_type ng_bridge_ipfwary_type = { &ng_parse_fixedarray_type, &ng_bridge_ipfwary_type_info }; static const struct ng_parse_struct_field ng_bridge_config_type_fields[] = NG_BRIDGE_CONFIG_TYPE_INFO(&ng_bridge_ipfwary_type); static const struct ng_parse_type ng_bridge_config_type = { &ng_parse_struct_type, &ng_bridge_config_type_fields }; /* Parse type for struct ng_bridge_link_stat */ static const struct ng_parse_struct_field ng_bridge_stats_type_fields[] = NG_BRIDGE_STATS_TYPE_INFO; static const struct ng_parse_type ng_bridge_stats_type = { &ng_parse_struct_type, &ng_bridge_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_bridge_cmdlist[] = { { NGM_BRIDGE_COOKIE, NGM_BRIDGE_SET_CONFIG, "setconfig", &ng_bridge_config_type, NULL }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GET_CONFIG, "getconfig", NULL, &ng_bridge_config_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_RESET, "reset", NULL, NULL }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GET_STATS, "getstats", &ng_parse_uint32_type, &ng_bridge_stats_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_CLR_STATS, "clrstats", &ng_parse_uint32_type, NULL }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GETCLR_STATS, "getclrstats", &ng_parse_uint32_type, &ng_bridge_stats_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GET_TABLE, "gettable", NULL, &ng_bridge_host_ary_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_SET_PERSISTENT, "setpersistent", NULL, NULL }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_bridge_typestruct = { .version = NG_ABI_VERSION, .name = NG_BRIDGE_NODE_TYPE, .constructor = ng_bridge_constructor, .rcvmsg = ng_bridge_rcvmsg, .shutdown = ng_bridge_shutdown, .newhook = ng_bridge_newhook, .rcvdata = ng_bridge_rcvdata, .disconnect = ng_bridge_disconnect, .cmdlist = ng_bridge_cmdlist, }; NETGRAPH_INIT(bridge, &ng_bridge_typestruct); /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * Node constructor */ static int ng_bridge_constructor(node_p node) { priv_p priv; /* Allocate and initialize private info */ priv = malloc(sizeof(*priv), M_NETGRAPH_BRIDGE, M_WAITOK | M_ZERO); ng_callout_init(&priv->timer); /* Allocate and initialize hash table, etc. */ priv->tab = malloc(MIN_BUCKETS * sizeof(*priv->tab), M_NETGRAPH_BRIDGE, M_WAITOK | M_ZERO); priv->numBuckets = MIN_BUCKETS; priv->hashMask = MIN_BUCKETS - 1; priv->conf.debugLevel = 1; priv->conf.loopTimeout = DEFAULT_LOOP_TIMEOUT; priv->conf.maxStaleness = DEFAULT_MAX_STALENESS; priv->conf.minStableAge = DEFAULT_MIN_STABLE_AGE; /* * This node has all kinds of stuff that could be screwed by SMP. * Until it gets it's own internal protection, we go through in * single file. This could hurt a machine bridging between two * GB ethernets so it should be fixed. * When it's fixed the process SHOULD NOT SLEEP, spinlocks please! * (and atomic ops ) */ NG_NODE_FORCE_WRITER(node); NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* Start timer; timer is always running while node is alive */ ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0); /* Done */ return (0); } /* * Method for attaching a new hook */ static int ng_bridge_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); /* Check for a link hook */ if (strncmp(name, NG_BRIDGE_HOOK_LINK_PREFIX, strlen(NG_BRIDGE_HOOK_LINK_PREFIX)) == 0) { const char *cp; char *eptr; u_long linkNum; cp = name + strlen(NG_BRIDGE_HOOK_LINK_PREFIX); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); linkNum = strtoul(cp, &eptr, 10); if (*eptr != '\0' || linkNum >= NG_BRIDGE_MAX_LINKS) return (EINVAL); if (priv->links[linkNum] != NULL) return (EISCONN); priv->links[linkNum] = malloc(sizeof(*priv->links[linkNum]), M_NETGRAPH_BRIDGE, M_NOWAIT|M_ZERO); if (priv->links[linkNum] == NULL) return (ENOMEM); priv->links[linkNum]->hook = hook; NG_HOOK_SET_PRIVATE(hook, (void *)linkNum); priv->numLinks++; return (0); } /* Unknown hook name */ return (EINVAL); } /* * Receive a control message */ static int ng_bridge_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_BRIDGE_COOKIE: switch (msg->header.cmd) { case NGM_BRIDGE_GET_CONFIG: { struct ng_bridge_config *conf; NG_MKRESPONSE(resp, msg, sizeof(struct ng_bridge_config), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_bridge_config *)resp->data; *conf = priv->conf; /* no sanity checking needed */ break; } case NGM_BRIDGE_SET_CONFIG: { struct ng_bridge_config *conf; int i; if (msg->header.arglen != sizeof(struct ng_bridge_config)) { error = EINVAL; break; } conf = (struct ng_bridge_config *)msg->data; priv->conf = *conf; for (i = 0; i < NG_BRIDGE_MAX_LINKS; i++) priv->conf.ipfw[i] = !!priv->conf.ipfw[i]; break; } case NGM_BRIDGE_RESET: { int i; /* Flush all entries in the hash table */ ng_bridge_remove_hosts(priv, -1); /* Reset all loop detection counters and stats */ for (i = 0; i < NG_BRIDGE_MAX_LINKS; i++) { if (priv->links[i] == NULL) continue; priv->links[i]->loopCount = 0; bzero(&priv->links[i]->stats, sizeof(priv->links[i]->stats)); } break; } case NGM_BRIDGE_GET_STATS: case NGM_BRIDGE_CLR_STATS: case NGM_BRIDGE_GETCLR_STATS: { struct ng_bridge_link *link; int linkNum; /* Get link number */ if (msg->header.arglen != sizeof(u_int32_t)) { error = EINVAL; break; } linkNum = *((u_int32_t *)msg->data); if (linkNum < 0 || linkNum >= NG_BRIDGE_MAX_LINKS) { error = EINVAL; break; } if ((link = priv->links[linkNum]) == NULL) { error = ENOTCONN; break; } /* Get/clear stats */ if (msg->header.cmd != NGM_BRIDGE_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(link->stats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bcopy(&link->stats, resp->data, sizeof(link->stats)); } if (msg->header.cmd != NGM_BRIDGE_GET_STATS) bzero(&link->stats, sizeof(link->stats)); break; } case NGM_BRIDGE_GET_TABLE: { struct ng_bridge_host_ary *ary; struct ng_bridge_hent *hent; int i = 0, bucket; NG_MKRESPONSE(resp, msg, sizeof(*ary) + (priv->numHosts * sizeof(*ary->hosts)), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } ary = (struct ng_bridge_host_ary *)resp->data; ary->numHosts = priv->numHosts; for (bucket = 0; bucket < priv->numBuckets; bucket++) { SLIST_FOREACH(hent, &priv->tab[bucket], next) ary->hosts[i++] = hent->host; } break; } case NGM_BRIDGE_SET_PERSISTENT: { priv->persistent = 1; 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 */ static int ng_bridge_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct ng_bridge_host *host; struct ng_bridge_link *link; struct ether_header *eh; int error = 0, linkNum, linksSeen; int manycast; struct mbuf *m; struct ng_bridge_link *firstLink; NGI_GET_M(item, m); /* Get link number */ linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); KASSERT(linkNum >= 0 && linkNum < NG_BRIDGE_MAX_LINKS, ("%s: linkNum=%u", __func__, linkNum)); link = priv->links[linkNum]; KASSERT(link != NULL, ("%s: link%d null", __func__, linkNum)); /* Sanity check packet and pull up header */ if (m->m_pkthdr.len < ETHER_HDR_LEN) { link->stats.recvRunts++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EINVAL); } if (m->m_len < ETHER_HDR_LEN && !(m = m_pullup(m, ETHER_HDR_LEN))) { link->stats.memoryFailures++; NG_FREE_ITEM(item); return (ENOBUFS); } eh = mtod(m, struct ether_header *); if ((eh->ether_shost[0] & 1) != 0) { link->stats.recvInvalid++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EINVAL); } /* Is link disabled due to a loopback condition? */ if (link->loopCount != 0) { link->stats.loopDrops++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ELOOP); /* XXX is this an appropriate error? */ } /* Update stats */ link->stats.recvPackets++; link->stats.recvOctets += m->m_pkthdr.len; if ((manycast = (eh->ether_dhost[0] & 1)) != 0) { if (ETHER_EQUAL(eh->ether_dhost, ng_bridge_bcast_addr)) { link->stats.recvBroadcasts++; manycast = 2; } else link->stats.recvMulticasts++; } /* Look up packet's source Ethernet address in hashtable */ if ((host = ng_bridge_get(priv, eh->ether_shost)) != NULL) { /* Update time since last heard from this host */ host->staleness = 0; /* Did host jump to a different link? */ if (host->linkNum != linkNum) { /* * If the host's old link was recently established * on the old link and it's already jumped to a new * link, declare a loopback condition. */ if (host->age < priv->conf.minStableAge) { /* Log the problem */ if (priv->conf.debugLevel >= 2) { struct ifnet *ifp = m->m_pkthdr.rcvif; char suffix[32]; if (ifp != NULL) snprintf(suffix, sizeof(suffix), " (%s)", ifp->if_xname); else *suffix = '\0'; log(LOG_WARNING, "ng_bridge: %s:" " loopback detected on %s%s\n", ng_bridge_nodename(node), NG_HOOK_NAME(hook), suffix); } /* Mark link as linka non grata */ link->loopCount = priv->conf.loopTimeout; link->stats.loopDetects++; /* Forget all hosts on this link */ ng_bridge_remove_hosts(priv, linkNum); /* Drop packet */ link->stats.loopDrops++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ELOOP); /* XXX appropriate? */ } /* Move host over to new link */ host->linkNum = linkNum; host->age = 0; } } else { if (!ng_bridge_put(priv, eh->ether_shost, linkNum)) { link->stats.memoryFailures++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ENOMEM); } } /* Run packet through ipfw processing, if enabled */ #if 0 if (priv->conf.ipfw[linkNum] && V_fw_enable && V_ip_fw_chk_ptr != NULL) { /* XXX not implemented yet */ } #endif /* * If unicast and destination host known, deliver to host's link, * unless it is the same link as the packet came in on. */ if (!manycast) { /* Determine packet destination link */ if ((host = ng_bridge_get(priv, eh->ether_dhost)) != NULL) { struct ng_bridge_link *const destLink = priv->links[host->linkNum]; /* If destination same as incoming link, do nothing */ KASSERT(destLink != NULL, ("%s: link%d null", __func__, host->linkNum)); if (destLink == link) { NG_FREE_ITEM(item); NG_FREE_M(m); return (0); } /* Deliver packet out the destination link */ destLink->stats.xmitPackets++; destLink->stats.xmitOctets += m->m_pkthdr.len; NG_FWD_NEW_DATA(error, item, destLink->hook, m); return (error); } /* Destination host is not known */ link->stats.recvUnknown++; } /* Distribute unknown, multicast, broadcast pkts to all other links */ firstLink = NULL; for (linkNum = linksSeen = 0; linksSeen <= priv->numLinks; linkNum++) { struct ng_bridge_link *destLink; struct mbuf *m2 = NULL; /* * If we have checked all the links then now * send the original on its reserved link */ if (linksSeen == priv->numLinks) { /* If we never saw a good link, leave. */ if (firstLink == NULL) { NG_FREE_ITEM(item); NG_FREE_M(m); return (0); } destLink = firstLink; } else { destLink = priv->links[linkNum]; if (destLink != NULL) linksSeen++; /* Skip incoming link and disconnected links */ if (destLink == NULL || destLink == link) { continue; } if (firstLink == NULL) { /* * This is the first usable link we have found. * Reserve it for the originals. * If we never find another we save a copy. */ firstLink = destLink; continue; } /* * It's usable link but not the reserved (first) one. * Copy mbuf info for sending. */ m2 = m_dup(m, M_NOWAIT); /* XXX m_copypacket() */ if (m2 == NULL) { link->stats.memoryFailures++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ENOBUFS); } } /* Update stats */ destLink->stats.xmitPackets++; destLink->stats.xmitOctets += m->m_pkthdr.len; switch (manycast) { case 0: /* unicast */ break; case 1: /* multicast */ destLink->stats.xmitMulticasts++; break; case 2: /* broadcast */ destLink->stats.xmitBroadcasts++; break; } /* Send packet */ if (destLink == firstLink) { /* * If we've sent all the others, send the original * on the first link we found. */ NG_FWD_NEW_DATA(error, item, destLink->hook, m); break; /* always done last - not really needed. */ } else { NG_SEND_DATA_ONLY(error, destLink->hook, m2); } } return (error); } /* * Shutdown node */ static int ng_bridge_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); /* * Shut down everything including the timer. Even if the * callout has already been dequeued and is about to be * run, ng_bridge_timeout() won't be fired as the node * is already marked NGF_INVALID, so we're safe to free * the node now. */ KASSERT(priv->numLinks == 0 && priv->numHosts == 0, ("%s: numLinks=%d numHosts=%d", __func__, priv->numLinks, priv->numHosts)); ng_uncallout(&priv->timer, node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); free(priv->tab, M_NETGRAPH_BRIDGE); free(priv, M_NETGRAPH_BRIDGE); return (0); } /* * Hook disconnection. */ static int ng_bridge_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); int linkNum; /* Get link number */ linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); KASSERT(linkNum >= 0 && linkNum < NG_BRIDGE_MAX_LINKS, ("%s: linkNum=%u", __func__, linkNum)); /* Remove all hosts associated with this link */ ng_bridge_remove_hosts(priv, linkNum); /* Free associated link information */ KASSERT(priv->links[linkNum] != NULL, ("%s: no link", __func__)); free(priv->links[linkNum], M_NETGRAPH_BRIDGE); priv->links[linkNum] = NULL; priv->numLinks--; /* If no more hooks, go away */ if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) && !priv->persistent) { ng_rmnode_self(NG_HOOK_NODE(hook)); } return (0); } /****************************************************************** HASH TABLE FUNCTIONS ******************************************************************/ /* * Hash algorithm */ #define HASH(addr,mask) ( (((const u_int16_t *)(addr))[0] \ ^ ((const u_int16_t *)(addr))[1] \ ^ ((const u_int16_t *)(addr))[2]) & (mask) ) /* * Find a host entry in the table. */ static struct ng_bridge_host * ng_bridge_get(priv_p priv, const u_char *addr) { const int bucket = HASH(addr, priv->hashMask); struct ng_bridge_hent *hent; SLIST_FOREACH(hent, &priv->tab[bucket], next) { if (ETHER_EQUAL(hent->host.addr, addr)) return (&hent->host); } return (NULL); } /* * Add a new host entry to the table. This assumes the host doesn't * already exist in the table. Returns 1 on success, 0 if there * was a memory allocation failure. */ static int ng_bridge_put(priv_p priv, const u_char *addr, int linkNum) { const int bucket = HASH(addr, priv->hashMask); struct ng_bridge_hent *hent; #ifdef INVARIANTS /* Assert that entry does not already exist in hashtable */ SLIST_FOREACH(hent, &priv->tab[bucket], next) { KASSERT(!ETHER_EQUAL(hent->host.addr, addr), ("%s: entry %6D exists in table", __func__, addr, ":")); } #endif /* Allocate and initialize new hashtable entry */ hent = malloc(sizeof(*hent), M_NETGRAPH_BRIDGE, M_NOWAIT); if (hent == NULL) return (0); bcopy(addr, hent->host.addr, ETHER_ADDR_LEN); hent->host.linkNum = linkNum; hent->host.staleness = 0; hent->host.age = 0; /* Add new element to hash bucket */ SLIST_INSERT_HEAD(&priv->tab[bucket], hent, next); priv->numHosts++; /* Resize table if necessary */ ng_bridge_rehash(priv); return (1); } /* * Resize the hash table. We try to maintain the number of buckets * such that the load factor is in the range 0.25 to 1.0. * * If we can't get the new memory then we silently fail. This is OK * because things will still work and we'll try again soon anyway. */ static void ng_bridge_rehash(priv_p priv) { struct ng_bridge_bucket *newTab; int oldBucket, newBucket; int newNumBuckets; u_int newMask; /* Is table too full or too empty? */ if (priv->numHosts > priv->numBuckets && (priv->numBuckets << 1) <= MAX_BUCKETS) newNumBuckets = priv->numBuckets << 1; else if (priv->numHosts < (priv->numBuckets >> 2) && (priv->numBuckets >> 2) >= MIN_BUCKETS) newNumBuckets = priv->numBuckets >> 2; else return; newMask = newNumBuckets - 1; /* Allocate and initialize new table */ - newTab = malloc(newNumBuckets * sizeof(*newTab), + newTab = mallocarray(newNumBuckets, sizeof(*newTab), M_NETGRAPH_BRIDGE, M_NOWAIT | M_ZERO); if (newTab == NULL) return; /* Move all entries from old table to new table */ for (oldBucket = 0; oldBucket < priv->numBuckets; oldBucket++) { struct ng_bridge_bucket *const oldList = &priv->tab[oldBucket]; while (!SLIST_EMPTY(oldList)) { struct ng_bridge_hent *const hent = SLIST_FIRST(oldList); SLIST_REMOVE_HEAD(oldList, next); newBucket = HASH(hent->host.addr, newMask); SLIST_INSERT_HEAD(&newTab[newBucket], hent, next); } } /* Replace old table with new one */ if (priv->conf.debugLevel >= 3) { log(LOG_INFO, "ng_bridge: %s: table size %d -> %d\n", ng_bridge_nodename(priv->node), priv->numBuckets, newNumBuckets); } free(priv->tab, M_NETGRAPH_BRIDGE); priv->numBuckets = newNumBuckets; priv->hashMask = newMask; priv->tab = newTab; return; } /****************************************************************** MISC FUNCTIONS ******************************************************************/ /* * Remove all hosts associated with a specific link from the hashtable. * If linkNum == -1, then remove all hosts in the table. */ static void ng_bridge_remove_hosts(priv_p priv, int linkNum) { int bucket; for (bucket = 0; bucket < priv->numBuckets; bucket++) { struct ng_bridge_hent **hptr = &SLIST_FIRST(&priv->tab[bucket]); while (*hptr != NULL) { struct ng_bridge_hent *const hent = *hptr; if (linkNum == -1 || hent->host.linkNum == linkNum) { *hptr = SLIST_NEXT(hent, next); free(hent, M_NETGRAPH_BRIDGE); priv->numHosts--; } else hptr = &SLIST_NEXT(hent, next); } } } /* * Handle our once-per-second timeout event. We do two things: * we decrement link->loopCount for those links being muted due to * a detected loopback condition, and we remove any hosts from * the hashtable whom we haven't heard from in a long while. */ static void ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2) { const priv_p priv = NG_NODE_PRIVATE(node); int bucket; int counter = 0; int linkNum; /* Update host time counters and remove stale entries */ for (bucket = 0; bucket < priv->numBuckets; bucket++) { struct ng_bridge_hent **hptr = &SLIST_FIRST(&priv->tab[bucket]); while (*hptr != NULL) { struct ng_bridge_hent *const hent = *hptr; /* Make sure host's link really exists */ KASSERT(priv->links[hent->host.linkNum] != NULL, ("%s: host %6D on nonexistent link %d\n", __func__, hent->host.addr, ":", hent->host.linkNum)); /* Remove hosts we haven't heard from in a while */ if (++hent->host.staleness >= priv->conf.maxStaleness) { *hptr = SLIST_NEXT(hent, next); free(hent, M_NETGRAPH_BRIDGE); priv->numHosts--; } else { if (hent->host.age < 0xffff) hent->host.age++; hptr = &SLIST_NEXT(hent, next); counter++; } } } KASSERT(priv->numHosts == counter, ("%s: hosts: %d != %d", __func__, priv->numHosts, counter)); /* Decrease table size if necessary */ ng_bridge_rehash(priv); /* Decrease loop counter on muted looped back links */ for (counter = linkNum = 0; linkNum < NG_BRIDGE_MAX_LINKS; linkNum++) { struct ng_bridge_link *const link = priv->links[linkNum]; if (link != NULL) { if (link->loopCount != 0) { link->loopCount--; if (link->loopCount == 0 && priv->conf.debugLevel >= 2) { log(LOG_INFO, "ng_bridge: %s:" " restoring looped back link%d\n", ng_bridge_nodename(node), linkNum); } } counter++; } } KASSERT(priv->numLinks == counter, ("%s: links: %d != %d", __func__, priv->numLinks, counter)); /* Register a new timeout, keeping the existing node reference */ ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0); } /* * Return node's "name", even if it doesn't have one. */ static const char * ng_bridge_nodename(node_p node) { static char name[NG_NODESIZ]; if (NG_NODE_HAS_NAME(node)) snprintf(name, sizeof(name), "%s", NG_NODE_NAME(node)); else snprintf(name, sizeof(name), "[%x]", ng_node2ID(node)); return name; } Index: head/sys/netgraph/ng_deflate.c =================================================================== --- head/sys/netgraph/ng_deflate.c (revision 328023) +++ head/sys/netgraph/ng_deflate.c (revision 328024) @@ -1,700 +1,700 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2006 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice 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. * * $FreeBSD$ */ /* * Deflate PPP compression netgraph node type. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_netgraph.h" static MALLOC_DEFINE(M_NETGRAPH_DEFLATE, "netgraph_deflate", "netgraph deflate node"); /* DEFLATE header length */ #define DEFLATE_HDRLEN 2 #define PROT_COMPD 0x00fd #define DEFLATE_BUF_SIZE 4096 /* Node private data */ struct ng_deflate_private { struct ng_deflate_config cfg; /* configuration */ u_char inbuf[DEFLATE_BUF_SIZE]; /* input buffer */ u_char outbuf[DEFLATE_BUF_SIZE]; /* output buffer */ z_stream cx; /* compression context */ struct ng_deflate_stats stats; /* statistics */ ng_ID_t ctrlnode; /* path to controlling node */ uint16_t seqnum; /* sequence number */ u_char compress; /* compress/decompress flag */ }; typedef struct ng_deflate_private *priv_p; /* Netgraph node methods */ static ng_constructor_t ng_deflate_constructor; static ng_rcvmsg_t ng_deflate_rcvmsg; static ng_shutdown_t ng_deflate_shutdown; static ng_newhook_t ng_deflate_newhook; static ng_rcvdata_t ng_deflate_rcvdata; static ng_disconnect_t ng_deflate_disconnect; /* Helper functions */ static void *z_alloc(void *, u_int items, u_int size); static void z_free(void *, void *ptr); static int ng_deflate_compress(node_p node, struct mbuf *m, struct mbuf **resultp); static int ng_deflate_decompress(node_p node, struct mbuf *m, struct mbuf **resultp); static void ng_deflate_reset_req(node_p node); /* Parse type for struct ng_deflate_config. */ static const struct ng_parse_struct_field ng_deflate_config_type_fields[] = NG_DEFLATE_CONFIG_INFO; static const struct ng_parse_type ng_deflate_config_type = { &ng_parse_struct_type, ng_deflate_config_type_fields }; /* Parse type for struct ng_deflate_stat. */ static const struct ng_parse_struct_field ng_deflate_stats_type_fields[] = NG_DEFLATE_STATS_INFO; static const struct ng_parse_type ng_deflate_stat_type = { &ng_parse_struct_type, ng_deflate_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII. */ static const struct ng_cmdlist ng_deflate_cmds[] = { { NGM_DEFLATE_COOKIE, NGM_DEFLATE_CONFIG, "config", &ng_deflate_config_type, NULL }, { NGM_DEFLATE_COOKIE, NGM_DEFLATE_RESETREQ, "resetreq", NULL, NULL }, { NGM_DEFLATE_COOKIE, NGM_DEFLATE_GET_STATS, "getstats", NULL, &ng_deflate_stat_type }, { NGM_DEFLATE_COOKIE, NGM_DEFLATE_CLR_STATS, "clrstats", NULL, NULL }, { NGM_DEFLATE_COOKIE, NGM_DEFLATE_GETCLR_STATS, "getclrstats", NULL, &ng_deflate_stat_type }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_deflate_typestruct = { .version = NG_ABI_VERSION, .name = NG_DEFLATE_NODE_TYPE, .constructor = ng_deflate_constructor, .rcvmsg = ng_deflate_rcvmsg, .shutdown = ng_deflate_shutdown, .newhook = ng_deflate_newhook, .rcvdata = ng_deflate_rcvdata, .disconnect = ng_deflate_disconnect, .cmdlist = ng_deflate_cmds, }; NETGRAPH_INIT(deflate, &ng_deflate_typestruct); /* Depend on separate zlib module. */ MODULE_DEPEND(ng_deflate, zlib, 1, 1, 1); #define ERROUT(x) do { error = (x); goto done; } while (0) /************************************************************************ NETGRAPH NODE STUFF ************************************************************************/ /* * Node type constructor */ static int ng_deflate_constructor(node_p node) { priv_p priv; /* Allocate private structure. */ priv = malloc(sizeof(*priv), M_NETGRAPH_DEFLATE, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, priv); /* This node is not thread safe. */ NG_NODE_FORCE_WRITER(node); /* Done */ return (0); } /* * Give our OK for a hook to be added. */ static int ng_deflate_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); if (NG_NODE_NUMHOOKS(node) > 0) return (EINVAL); if (strcmp(name, NG_DEFLATE_HOOK_COMP) == 0) priv->compress = 1; else if (strcmp(name, NG_DEFLATE_HOOK_DECOMP) == 0) priv->compress = 0; else return (EINVAL); return (0); } /* * Receive a control message */ static int ng_deflate_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); if (msg->header.typecookie != NGM_DEFLATE_COOKIE) ERROUT(EINVAL); switch (msg->header.cmd) { case NGM_DEFLATE_CONFIG: { struct ng_deflate_config *const cfg = (struct ng_deflate_config *)msg->data; /* Check configuration. */ if (msg->header.arglen != sizeof(*cfg)) ERROUT(EINVAL); if (cfg->enable) { if (cfg->windowBits < 8 || cfg->windowBits > 15) ERROUT(EINVAL); } else cfg->windowBits = 0; /* Clear previous state. */ if (priv->cfg.enable) { if (priv->compress) deflateEnd(&priv->cx); else inflateEnd(&priv->cx); priv->cfg.enable = 0; } /* Configuration is OK, reset to it. */ priv->cfg = *cfg; if (priv->cfg.enable) { priv->cx.next_in = NULL; priv->cx.zalloc = z_alloc; priv->cx.zfree = z_free; int res; if (priv->compress) { if ((res = deflateInit2(&priv->cx, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -cfg->windowBits, 8, Z_DEFAULT_STRATEGY)) != Z_OK) { log(LOG_NOTICE, "deflateInit2: error %d, %s\n", res, priv->cx.msg); priv->cfg.enable = 0; ERROUT(ENOMEM); } } else { if ((res = inflateInit2(&priv->cx, -cfg->windowBits)) != Z_OK) { log(LOG_NOTICE, "inflateInit2: error %d, %s\n", res, priv->cx.msg); priv->cfg.enable = 0; ERROUT(ENOMEM); } } } /* Initialize other state. */ priv->seqnum = 0; /* Save return address so we can send reset-req's */ priv->ctrlnode = NGI_RETADDR(item); break; } case NGM_DEFLATE_RESETREQ: ng_deflate_reset_req(node); break; case NGM_DEFLATE_GET_STATS: case NGM_DEFLATE_CLR_STATS: case NGM_DEFLATE_GETCLR_STATS: /* Create response if requested. */ if (msg->header.cmd != NGM_DEFLATE_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(struct ng_deflate_stats), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); bcopy(&priv->stats, resp->data, sizeof(struct ng_deflate_stats)); } /* Clear stats if requested. */ if (msg->header.cmd != NGM_DEFLATE_GET_STATS) bzero(&priv->stats, sizeof(struct ng_deflate_stats)); break; default: error = EINVAL; break; } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive incoming data on our hook. */ static int ng_deflate_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct mbuf *m, *out; int error; if (!priv->cfg.enable) { NG_FREE_ITEM(item); return (ENXIO); } NGI_GET_M(item, m); /* Compress */ if (priv->compress) { if ((error = ng_deflate_compress(node, m, &out)) != 0) { NG_FREE_ITEM(item); log(LOG_NOTICE, "%s: error: %d\n", __func__, error); return (error); } } else { /* Decompress */ if ((error = ng_deflate_decompress(node, m, &out)) != 0) { NG_FREE_ITEM(item); log(LOG_NOTICE, "%s: error: %d\n", __func__, error); if (priv->ctrlnode != 0) { struct ng_mesg *msg; /* Need to send a reset-request. */ NG_MKMESSAGE(msg, NGM_DEFLATE_COOKIE, NGM_DEFLATE_RESETREQ, 0, M_NOWAIT); if (msg == NULL) return (error); NG_SEND_MSG_ID(error, node, msg, priv->ctrlnode, 0); } return (error); } } NG_FWD_NEW_DATA(error, item, hook, out); return (error); } /* * Destroy node. */ static int ng_deflate_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); /* Take down netgraph node. */ if (priv->cfg.enable) { if (priv->compress) deflateEnd(&priv->cx); else inflateEnd(&priv->cx); } free(priv, M_NETGRAPH_DEFLATE); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); /* let the node escape */ return (0); } /* * Hook disconnection */ static int ng_deflate_disconnect(hook_p hook) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); if (priv->cfg.enable) { if (priv->compress) deflateEnd(&priv->cx); else inflateEnd(&priv->cx); priv->cfg.enable = 0; } /* Go away if no longer connected. */ if ((NG_NODE_NUMHOOKS(node) == 0) && NG_NODE_IS_VALID(node)) ng_rmnode_self(node); return (0); } /************************************************************************ HELPER STUFF ************************************************************************/ /* * Space allocation and freeing routines for use by zlib routines. */ static void * z_alloc(void *notused, u_int items, u_int size) { - return (malloc(items * size, M_NETGRAPH_DEFLATE, M_NOWAIT)); + return (mallocarray(items, size, M_NETGRAPH_DEFLATE, M_NOWAIT)); } static void z_free(void *notused, void *ptr) { free(ptr, M_NETGRAPH_DEFLATE); } /* * Compress/encrypt a packet and put the result in a new mbuf at *resultp. * The original mbuf is not free'd. */ static int ng_deflate_compress(node_p node, struct mbuf *m, struct mbuf **resultp) { const priv_p priv = NG_NODE_PRIVATE(node); int outlen, inlen; int rtn; /* Initialize. */ *resultp = NULL; inlen = m->m_pkthdr.len; priv->stats.FramesPlain++; priv->stats.InOctets+=inlen; if (inlen > DEFLATE_BUF_SIZE) { priv->stats.Errors++; NG_FREE_M(m); return (ENOMEM); } /* We must own the mbuf chain exclusively to modify it. */ m = m_unshare(m, M_NOWAIT); if (m == NULL) { priv->stats.Errors++; return (ENOMEM); } /* Work with contiguous regions of memory. */ m_copydata(m, 0, inlen, (caddr_t)priv->inbuf); outlen = DEFLATE_BUF_SIZE; /* Compress "inbuf" into "outbuf". */ /* Prepare to compress. */ if (priv->inbuf[0] != 0) { priv->cx.next_in = priv->inbuf; priv->cx.avail_in = inlen; } else { priv->cx.next_in = priv->inbuf + 1; /* compress protocol */ priv->cx.avail_in = inlen - 1; } priv->cx.next_out = priv->outbuf + 2 + DEFLATE_HDRLEN; priv->cx.avail_out = outlen - 2 - DEFLATE_HDRLEN; /* Compress. */ rtn = deflate(&priv->cx, Z_PACKET_FLUSH); /* Check return value. */ if (rtn != Z_OK) { priv->stats.Errors++; log(LOG_NOTICE, "ng_deflate: compression error: %d (%s)\n", rtn, priv->cx.msg); NG_FREE_M(m); return (EINVAL); } /* Calculate resulting size. */ outlen -= priv->cx.avail_out; /* If we can't compress this packet, send it as-is. */ if (outlen > inlen) { /* Return original packet uncompressed. */ *resultp = m; priv->stats.FramesUncomp++; priv->stats.OutOctets+=inlen; } else { /* Install header. */ be16enc(priv->outbuf, PROT_COMPD); be16enc(priv->outbuf + 2, priv->seqnum); /* Return packet in an mbuf. */ m_copyback(m, 0, outlen, (caddr_t)priv->outbuf); if (m->m_pkthdr.len < outlen) { m_freem(m); priv->stats.Errors++; return (ENOMEM); } else if (outlen < m->m_pkthdr.len) m_adj(m, outlen - m->m_pkthdr.len); *resultp = m; priv->stats.FramesComp++; priv->stats.OutOctets+=outlen; } /* Update sequence number. */ priv->seqnum++; return (0); } /* * Decompress/decrypt packet and put the result in a new mbuf at *resultp. * The original mbuf is not free'd. */ static int ng_deflate_decompress(node_p node, struct mbuf *m, struct mbuf **resultp) { const priv_p priv = NG_NODE_PRIVATE(node); int outlen, inlen; int rtn; uint16_t proto; int offset; uint16_t rseqnum; /* Initialize. */ *resultp = NULL; inlen = m->m_pkthdr.len; if (inlen > DEFLATE_BUF_SIZE) { priv->stats.Errors++; NG_FREE_M(m); priv->seqnum = 0; return (ENOMEM); } /* We must own the mbuf chain exclusively to modify it. */ m = m_unshare(m, M_NOWAIT); if (m == NULL) { priv->stats.Errors++; return (ENOMEM); } /* Work with contiguous regions of memory. */ m_copydata(m, 0, inlen, (caddr_t)priv->inbuf); /* Separate proto. */ if ((priv->inbuf[0] & 0x01) != 0) { proto = priv->inbuf[0]; offset = 1; } else { proto = be16dec(priv->inbuf); offset = 2; } priv->stats.InOctets += inlen; /* Packet is compressed, so decompress. */ if (proto == PROT_COMPD) { priv->stats.FramesComp++; /* Check sequence number. */ rseqnum = be16dec(priv->inbuf + offset); offset += 2; if (rseqnum != priv->seqnum) { priv->stats.Errors++; log(LOG_NOTICE, "ng_deflate: wrong sequence: %u " "instead of %u\n", rseqnum, priv->seqnum); NG_FREE_M(m); priv->seqnum = 0; return (EPIPE); } outlen = DEFLATE_BUF_SIZE; /* Decompress "inbuf" into "outbuf". */ /* Prepare to decompress. */ priv->cx.next_in = priv->inbuf + offset; priv->cx.avail_in = inlen - offset; /* Reserve space for protocol decompression. */ priv->cx.next_out = priv->outbuf + 1; priv->cx.avail_out = outlen - 1; /* Decompress. */ rtn = inflate(&priv->cx, Z_PACKET_FLUSH); /* Check return value. */ if (rtn != Z_OK && rtn != Z_STREAM_END) { priv->stats.Errors++; NG_FREE_M(m); priv->seqnum = 0; log(LOG_NOTICE, "%s: decompression error: %d (%s)\n", __func__, rtn, priv->cx.msg); switch (rtn) { case Z_MEM_ERROR: return (ENOMEM); case Z_DATA_ERROR: return (EIO); default: return (EINVAL); } } /* Calculate resulting size. */ outlen -= priv->cx.avail_out; /* Decompress protocol. */ if ((priv->outbuf[1] & 0x01) != 0) { priv->outbuf[0] = 0; /* Return packet in an mbuf. */ m_copyback(m, 0, outlen, (caddr_t)priv->outbuf); } else { outlen--; /* Return packet in an mbuf. */ m_copyback(m, 0, outlen, (caddr_t)(priv->outbuf + 1)); } if (m->m_pkthdr.len < outlen) { m_freem(m); priv->stats.Errors++; priv->seqnum = 0; return (ENOMEM); } else if (outlen < m->m_pkthdr.len) m_adj(m, outlen - m->m_pkthdr.len); *resultp = m; priv->stats.FramesPlain++; priv->stats.OutOctets+=outlen; } else { /* Packet is not compressed, just update dictionary. */ priv->stats.FramesUncomp++; if (priv->inbuf[0] == 0) { priv->cx.next_in = priv->inbuf + 1; /* compress protocol */ priv->cx.avail_in = inlen - 1; } else { priv->cx.next_in = priv->inbuf; priv->cx.avail_in = inlen; } rtn = inflateIncomp(&priv->cx); /* Check return value */ if (rtn != Z_OK) { priv->stats.Errors++; log(LOG_NOTICE, "%s: inflateIncomp error: %d (%s)\n", __func__, rtn, priv->cx.msg); NG_FREE_M(m); priv->seqnum = 0; return (EINVAL); } *resultp = m; priv->stats.FramesPlain++; priv->stats.OutOctets += inlen; } /* Update sequence number. */ priv->seqnum++; return (0); } /* * The peer has sent us a CCP ResetRequest, so reset our transmit state. */ static void ng_deflate_reset_req(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); priv->seqnum = 0; if (priv->cfg.enable) { if (priv->compress) deflateReset(&priv->cx); else inflateReset(&priv->cx); } } Index: head/sys/netgraph/ng_parse.c =================================================================== --- head/sys/netgraph/ng_parse.c (revision 328023) +++ head/sys/netgraph/ng_parse.c (revision 328024) @@ -1,1902 +1,1903 @@ /* * ng_parse.c */ /*- * Copyright (c) 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_parse.c,v 1.3 1999/11/29 01:43:48 archie Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_PARSE, "netgraph_parse", "netgraph parse info"); #else #define M_NETGRAPH_PARSE M_NETGRAPH #endif /* Compute alignment for primitive integral types */ struct int16_temp { char x; int16_t y; }; struct int32_temp { char x; int32_t y; }; struct int64_temp { char x; int64_t y; }; #define INT8_ALIGNMENT 1 #define INT16_ALIGNMENT ((size_t)&((struct int16_temp *)0)->y) #define INT32_ALIGNMENT ((size_t)&((struct int32_temp *)0)->y) #define INT64_ALIGNMENT ((size_t)&((struct int64_temp *)0)->y) /* Output format for integral types */ #define INT_UNSIGNED 0 #define INT_SIGNED 1 #define INT_HEX 2 /* Type of composite object: struct, array, or fixedarray */ enum comptype { CT_STRUCT, CT_ARRAY, CT_FIXEDARRAY, }; /* Composite types helper functions */ static int ng_parse_composite(const struct ng_parse_type *type, const char *s, int *off, const u_char *start, u_char *const buf, int *buflen, enum comptype ctype); static int ng_unparse_composite(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen, enum comptype ctype); static int ng_get_composite_elem_default(const struct ng_parse_type *type, int index, const u_char *start, u_char *buf, int *buflen, enum comptype ctype); static int ng_get_composite_len(const struct ng_parse_type *type, const u_char *start, const u_char *buf, enum comptype ctype); static const struct ng_parse_type *ng_get_composite_etype(const struct ng_parse_type *type, int index, enum comptype ctype); static int ng_parse_get_elem_pad(const struct ng_parse_type *type, int index, enum comptype ctype, int posn); /* Parsing helper functions */ static int ng_parse_skip_value(const char *s, int off, int *lenp); static int ng_parse_append(char **cbufp, int *cbuflenp, const char *fmt, ...); /* Poor man's virtual method calls */ #define METHOD(t,m) (ng_get_ ## m ## _method(t)) #define INVOKE(t,m) (*METHOD(t,m)) static ng_parse_t *ng_get_parse_method(const struct ng_parse_type *t); static ng_unparse_t *ng_get_unparse_method(const struct ng_parse_type *t); static ng_getDefault_t *ng_get_getDefault_method(const struct ng_parse_type *t); static ng_getAlign_t *ng_get_getAlign_method(const struct ng_parse_type *t); #define ALIGNMENT(t) (METHOD(t, getAlign) == NULL ? \ 0 : INVOKE(t, getAlign)(t)) /************************************************************************ PUBLIC FUNCTIONS ************************************************************************/ /* * Convert an ASCII string to binary according to the supplied type descriptor */ int ng_parse(const struct ng_parse_type *type, const char *string, int *off, u_char *buf, int *buflen) { return INVOKE(type, parse)(type, string, off, buf, buf, buflen); } /* * Convert binary to an ASCII string according to the supplied type descriptor */ int ng_unparse(const struct ng_parse_type *type, const u_char *data, char *cbuf, int cbuflen) { int off = 0; return INVOKE(type, unparse)(type, data, &off, cbuf, cbuflen); } /* * Fill in the default value according to the supplied type descriptor */ int ng_parse_getDefault(const struct ng_parse_type *type, u_char *buf, int *buflen) { ng_getDefault_t *const func = METHOD(type, getDefault); if (func == NULL) return (EOPNOTSUPP); return (*func)(type, buf, buf, buflen); } /************************************************************************ STRUCTURE TYPE ************************************************************************/ static int ng_struct_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { return ng_parse_composite(type, s, off, start, buf, buflen, CT_STRUCT); } static int ng_struct_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { return ng_unparse_composite(type, data, off, cbuf, cbuflen, CT_STRUCT); } static int ng_struct_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { int off = 0; return ng_parse_composite(type, "{}", &off, start, buf, buflen, CT_STRUCT); } static int ng_struct_getAlign(const struct ng_parse_type *type) { const struct ng_parse_struct_field *field; int align = 0; for (field = type->info; field->name != NULL; field++) { int falign = ALIGNMENT(field->type); if (falign > align) align = falign; } return align; } const struct ng_parse_type ng_parse_struct_type = { NULL, NULL, NULL, ng_struct_parse, ng_struct_unparse, ng_struct_getDefault, ng_struct_getAlign }; /************************************************************************ FIXED LENGTH ARRAY TYPE ************************************************************************/ static int ng_fixedarray_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { return ng_parse_composite(type, s, off, start, buf, buflen, CT_FIXEDARRAY); } static int ng_fixedarray_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { return ng_unparse_composite(type, data, off, cbuf, cbuflen, CT_FIXEDARRAY); } static int ng_fixedarray_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { int off = 0; return ng_parse_composite(type, "[]", &off, start, buf, buflen, CT_FIXEDARRAY); } static int ng_fixedarray_getAlign(const struct ng_parse_type *type) { const struct ng_parse_fixedarray_info *fi = type->info; return ALIGNMENT(fi->elementType); } const struct ng_parse_type ng_parse_fixedarray_type = { NULL, NULL, NULL, ng_fixedarray_parse, ng_fixedarray_unparse, ng_fixedarray_getDefault, ng_fixedarray_getAlign }; /************************************************************************ VARIABLE LENGTH ARRAY TYPE ************************************************************************/ static int ng_array_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { return ng_parse_composite(type, s, off, start, buf, buflen, CT_ARRAY); } static int ng_array_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { return ng_unparse_composite(type, data, off, cbuf, cbuflen, CT_ARRAY); } static int ng_array_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { int off = 0; return ng_parse_composite(type, "[]", &off, start, buf, buflen, CT_ARRAY); } static int ng_array_getAlign(const struct ng_parse_type *type) { const struct ng_parse_array_info *ai = type->info; return ALIGNMENT(ai->elementType); } const struct ng_parse_type ng_parse_array_type = { NULL, NULL, NULL, ng_array_parse, ng_array_unparse, ng_array_getDefault, ng_array_getAlign }; /************************************************************************ INT8 TYPE ************************************************************************/ static int ng_int8_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { long val; int8_t val8; char *eptr; val = strtol(s + *off, &eptr, 0); if (val < (int8_t)0x80 || val > (u_int8_t)0xff || eptr == s + *off) return (EINVAL); *off = eptr - s; val8 = (int8_t)val; bcopy(&val8, buf, sizeof(int8_t)); *buflen = sizeof(int8_t); return (0); } static int ng_int8_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { const char *fmt; int fval; int error; int8_t val; bcopy(data + *off, &val, sizeof(int8_t)); switch ((intptr_t)type->info) { case INT_SIGNED: fmt = "%d"; fval = val; break; case INT_UNSIGNED: fmt = "%u"; fval = (u_int8_t)val; break; case INT_HEX: fmt = "0x%x"; fval = (u_int8_t)val; break; default: panic("%s: unknown type", __func__); } if ((error = ng_parse_append(&cbuf, &cbuflen, fmt, fval)) != 0) return (error); *off += sizeof(int8_t); return (0); } static int ng_int8_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { int8_t val; if (*buflen < sizeof(int8_t)) return (ERANGE); val = 0; bcopy(&val, buf, sizeof(int8_t)); *buflen = sizeof(int8_t); return (0); } static int ng_int8_getAlign(const struct ng_parse_type *type) { return INT8_ALIGNMENT; } const struct ng_parse_type ng_parse_int8_type = { NULL, (void *)INT_SIGNED, NULL, ng_int8_parse, ng_int8_unparse, ng_int8_getDefault, ng_int8_getAlign }; const struct ng_parse_type ng_parse_uint8_type = { &ng_parse_int8_type, (void *)INT_UNSIGNED }; const struct ng_parse_type ng_parse_hint8_type = { &ng_parse_int8_type, (void *)INT_HEX }; /************************************************************************ INT16 TYPE ************************************************************************/ static int ng_int16_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { long val; int16_t val16; char *eptr; val = strtol(s + *off, &eptr, 0); if (val < (int16_t)0x8000 || val > (u_int16_t)0xffff || eptr == s + *off) return (EINVAL); *off = eptr - s; val16 = (int16_t)val; bcopy(&val16, buf, sizeof(int16_t)); *buflen = sizeof(int16_t); return (0); } static int ng_int16_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { const char *fmt; int fval; int error; int16_t val; bcopy(data + *off, &val, sizeof(int16_t)); switch ((intptr_t)type->info) { case INT_SIGNED: fmt = "%d"; fval = val; break; case INT_UNSIGNED: fmt = "%u"; fval = (u_int16_t)val; break; case INT_HEX: fmt = "0x%x"; fval = (u_int16_t)val; break; default: panic("%s: unknown type", __func__); } if ((error = ng_parse_append(&cbuf, &cbuflen, fmt, fval)) != 0) return (error); *off += sizeof(int16_t); return (0); } static int ng_int16_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { int16_t val; if (*buflen < sizeof(int16_t)) return (ERANGE); val = 0; bcopy(&val, buf, sizeof(int16_t)); *buflen = sizeof(int16_t); return (0); } static int ng_int16_getAlign(const struct ng_parse_type *type) { return INT16_ALIGNMENT; } const struct ng_parse_type ng_parse_int16_type = { NULL, (void *)INT_SIGNED, NULL, ng_int16_parse, ng_int16_unparse, ng_int16_getDefault, ng_int16_getAlign }; const struct ng_parse_type ng_parse_uint16_type = { &ng_parse_int16_type, (void *)INT_UNSIGNED }; const struct ng_parse_type ng_parse_hint16_type = { &ng_parse_int16_type, (void *)INT_HEX }; /************************************************************************ INT32 TYPE ************************************************************************/ static int ng_int32_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { long val; /* assumes long is at least 32 bits */ int32_t val32; char *eptr; if ((intptr_t)type->info == INT_SIGNED) val = strtol(s + *off, &eptr, 0); else val = strtoul(s + *off, &eptr, 0); if (val < (int32_t)0x80000000 || val > (u_int32_t)0xffffffff || eptr == s + *off) return (EINVAL); *off = eptr - s; val32 = (int32_t)val; bcopy(&val32, buf, sizeof(int32_t)); *buflen = sizeof(int32_t); return (0); } static int ng_int32_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { const char *fmt; long fval; int error; int32_t val; bcopy(data + *off, &val, sizeof(int32_t)); switch ((intptr_t)type->info) { case INT_SIGNED: fmt = "%ld"; fval = val; break; case INT_UNSIGNED: fmt = "%lu"; fval = (u_int32_t)val; break; case INT_HEX: fmt = "0x%lx"; fval = (u_int32_t)val; break; default: panic("%s: unknown type", __func__); } if ((error = ng_parse_append(&cbuf, &cbuflen, fmt, fval)) != 0) return (error); *off += sizeof(int32_t); return (0); } static int ng_int32_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { int32_t val; if (*buflen < sizeof(int32_t)) return (ERANGE); val = 0; bcopy(&val, buf, sizeof(int32_t)); *buflen = sizeof(int32_t); return (0); } static int ng_int32_getAlign(const struct ng_parse_type *type) { return INT32_ALIGNMENT; } const struct ng_parse_type ng_parse_int32_type = { NULL, (void *)INT_SIGNED, NULL, ng_int32_parse, ng_int32_unparse, ng_int32_getDefault, ng_int32_getAlign }; const struct ng_parse_type ng_parse_uint32_type = { &ng_parse_int32_type, (void *)INT_UNSIGNED }; const struct ng_parse_type ng_parse_hint32_type = { &ng_parse_int32_type, (void *)INT_HEX }; /************************************************************************ INT64 TYPE ************************************************************************/ static int ng_int64_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { quad_t val; int64_t val64; char *eptr; val = strtoq(s + *off, &eptr, 0); if (eptr == s + *off) return (EINVAL); *off = eptr - s; val64 = (int64_t)val; bcopy(&val64, buf, sizeof(int64_t)); *buflen = sizeof(int64_t); return (0); } static int ng_int64_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { const char *fmt; long long fval; int64_t val; int error; bcopy(data + *off, &val, sizeof(int64_t)); switch ((intptr_t)type->info) { case INT_SIGNED: fmt = "%lld"; fval = val; break; case INT_UNSIGNED: fmt = "%llu"; fval = (u_int64_t)val; break; case INT_HEX: fmt = "0x%llx"; fval = (u_int64_t)val; break; default: panic("%s: unknown type", __func__); } if ((error = ng_parse_append(&cbuf, &cbuflen, fmt, fval)) != 0) return (error); *off += sizeof(int64_t); return (0); } static int ng_int64_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { int64_t val; if (*buflen < sizeof(int64_t)) return (ERANGE); val = 0; bcopy(&val, buf, sizeof(int64_t)); *buflen = sizeof(int64_t); return (0); } static int ng_int64_getAlign(const struct ng_parse_type *type) { return INT64_ALIGNMENT; } const struct ng_parse_type ng_parse_int64_type = { NULL, (void *)INT_SIGNED, NULL, ng_int64_parse, ng_int64_unparse, ng_int64_getDefault, ng_int64_getAlign }; const struct ng_parse_type ng_parse_uint64_type = { &ng_parse_int64_type, (void *)INT_UNSIGNED }; const struct ng_parse_type ng_parse_hint64_type = { &ng_parse_int64_type, (void *)INT_HEX }; /************************************************************************ STRING TYPE ************************************************************************/ static int ng_string_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { char *sval; int len; int slen; if ((sval = ng_get_string_token(s, off, &len, &slen)) == NULL) return (EINVAL); *off += len; bcopy(sval, buf, slen + 1); free(sval, M_NETGRAPH_PARSE); *buflen = slen + 1; return (0); } static int ng_string_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { const char *const raw = (const char *)data + *off; char *const s = ng_encode_string(raw, strlen(raw)); int error; if (s == NULL) return (ENOMEM); if ((error = ng_parse_append(&cbuf, &cbuflen, "%s", s)) != 0) { free(s, M_NETGRAPH_PARSE); return (error); } *off += strlen(raw) + 1; free(s, M_NETGRAPH_PARSE); return (0); } static int ng_string_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { if (*buflen < 1) return (ERANGE); buf[0] = (u_char)'\0'; *buflen = 1; return (0); } const struct ng_parse_type ng_parse_string_type = { NULL, NULL, NULL, ng_string_parse, ng_string_unparse, ng_string_getDefault, NULL }; /************************************************************************ FIXED BUFFER STRING TYPE ************************************************************************/ static int ng_fixedstring_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { const struct ng_parse_fixedstring_info *const fi = type->info; char *sval; int len; int slen; if ((sval = ng_get_string_token(s, off, &len, &slen)) == NULL) return (EINVAL); if (slen + 1 > fi->bufSize) { free(sval, M_NETGRAPH_PARSE); return (E2BIG); } *off += len; bcopy(sval, buf, slen); free(sval, M_NETGRAPH_PARSE); bzero(buf + slen, fi->bufSize - slen); *buflen = fi->bufSize; return (0); } static int ng_fixedstring_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { const struct ng_parse_fixedstring_info *const fi = type->info; int error, temp = *off; if ((error = ng_string_unparse(type, data, &temp, cbuf, cbuflen)) != 0) return (error); *off += fi->bufSize; return (0); } static int ng_fixedstring_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { const struct ng_parse_fixedstring_info *const fi = type->info; if (*buflen < fi->bufSize) return (ERANGE); bzero(buf, fi->bufSize); *buflen = fi->bufSize; return (0); } const struct ng_parse_type ng_parse_fixedstring_type = { NULL, NULL, NULL, ng_fixedstring_parse, ng_fixedstring_unparse, ng_fixedstring_getDefault, NULL }; const struct ng_parse_fixedstring_info ng_parse_nodebuf_info = { NG_NODESIZ }; const struct ng_parse_type ng_parse_nodebuf_type = { &ng_parse_fixedstring_type, &ng_parse_nodebuf_info }; const struct ng_parse_fixedstring_info ng_parse_hookbuf_info = { NG_HOOKSIZ }; const struct ng_parse_type ng_parse_hookbuf_type = { &ng_parse_fixedstring_type, &ng_parse_hookbuf_info }; const struct ng_parse_fixedstring_info ng_parse_pathbuf_info = { NG_PATHSIZ }; const struct ng_parse_type ng_parse_pathbuf_type = { &ng_parse_fixedstring_type, &ng_parse_pathbuf_info }; const struct ng_parse_fixedstring_info ng_parse_typebuf_info = { NG_TYPESIZ }; const struct ng_parse_type ng_parse_typebuf_type = { &ng_parse_fixedstring_type, &ng_parse_typebuf_info }; const struct ng_parse_fixedstring_info ng_parse_cmdbuf_info = { NG_CMDSTRSIZ }; const struct ng_parse_type ng_parse_cmdbuf_type = { &ng_parse_fixedstring_type, &ng_parse_cmdbuf_info }; /************************************************************************ EXPLICITLY SIZED STRING TYPE ************************************************************************/ static int ng_sizedstring_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { char *sval; int len; int slen; if ((sval = ng_get_string_token(s, off, &len, &slen)) == NULL) return (EINVAL); if (slen > USHRT_MAX) { free(sval, M_NETGRAPH_PARSE); return (EINVAL); } *off += len; *((u_int16_t *)buf) = (u_int16_t)slen; bcopy(sval, buf + 2, slen); free(sval, M_NETGRAPH_PARSE); *buflen = 2 + slen; return (0); } static int ng_sizedstring_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { const char *const raw = (const char *)data + *off + 2; const int slen = *((const u_int16_t *)(data + *off)); char *const s = ng_encode_string(raw, slen); int error; if (s == NULL) return (ENOMEM); if ((error = ng_parse_append(&cbuf, &cbuflen, "%s", s)) != 0) { free(s, M_NETGRAPH_PARSE); return (error); } free(s, M_NETGRAPH_PARSE); *off += slen + 2; return (0); } static int ng_sizedstring_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { if (*buflen < 2) return (ERANGE); bzero(buf, 2); *buflen = 2; return (0); } const struct ng_parse_type ng_parse_sizedstring_type = { NULL, NULL, NULL, ng_sizedstring_parse, ng_sizedstring_unparse, ng_sizedstring_getDefault, NULL }; /************************************************************************ IP ADDRESS TYPE ************************************************************************/ static int ng_ipaddr_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { int i, error; for (i = 0; i < 4; i++) { if ((error = ng_int8_parse(&ng_parse_int8_type, s, off, start, buf + i, buflen)) != 0) return (error); if (i < 3 && s[*off] != '.') return (EINVAL); (*off)++; } *buflen = 4; return (0); } static int ng_ipaddr_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { struct in_addr ip; int error; bcopy(data + *off, &ip, sizeof(ip)); if ((error = ng_parse_append(&cbuf, &cbuflen, "%d.%d.%d.%d", ((u_char *)&ip)[0], ((u_char *)&ip)[1], ((u_char *)&ip)[2], ((u_char *)&ip)[3])) != 0) return (error); *off += sizeof(ip); return (0); } static int ng_ipaddr_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { struct in_addr ip = { 0 }; if (*buflen < sizeof(ip)) return (ERANGE); bcopy(&ip, buf, sizeof(ip)); *buflen = sizeof(ip); return (0); } const struct ng_parse_type ng_parse_ipaddr_type = { NULL, NULL, NULL, ng_ipaddr_parse, ng_ipaddr_unparse, ng_ipaddr_getDefault, ng_int32_getAlign }; /************************************************************************ ETHERNET ADDRESS TYPE ************************************************************************/ static int ng_enaddr_parse(const struct ng_parse_type *type, const char *s, int *const off, const u_char *const start, u_char *const buf, int *const buflen) { char *eptr; u_long val; int i; if (*buflen < ETHER_ADDR_LEN) return (ERANGE); for (i = 0; i < ETHER_ADDR_LEN; i++) { val = strtoul(s + *off, &eptr, 16); if (val > 0xff || eptr == s + *off) return (EINVAL); buf[i] = (u_char)val; *off = (eptr - s); if (i < ETHER_ADDR_LEN - 1) { if (*eptr != ':') return (EINVAL); (*off)++; } } *buflen = ETHER_ADDR_LEN; return (0); } static int ng_enaddr_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { int len; len = snprintf(cbuf, cbuflen, "%02x:%02x:%02x:%02x:%02x:%02x", data[*off], data[*off + 1], data[*off + 2], data[*off + 3], data[*off + 4], data[*off + 5]); if (len >= cbuflen) return (ERANGE); *off += ETHER_ADDR_LEN; return (0); } const struct ng_parse_type ng_parse_enaddr_type = { NULL, NULL, NULL, ng_enaddr_parse, ng_enaddr_unparse, NULL, 0 }; /************************************************************************ BYTE ARRAY TYPE ************************************************************************/ /* Get the length of a byte array */ static int ng_parse_bytearray_subtype_getLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { ng_parse_array_getLength_t *const getLength = type->private; return (*getLength)(type, start, buf); } /* Byte array element type is hex int8 */ static const struct ng_parse_array_info ng_parse_bytearray_subtype_info = { &ng_parse_hint8_type, &ng_parse_bytearray_subtype_getLength, NULL }; static const struct ng_parse_type ng_parse_bytearray_subtype = { &ng_parse_array_type, &ng_parse_bytearray_subtype_info }; static int ng_bytearray_parse(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen) { char *str; int toklen; int slen; /* We accept either an array of bytes or a string constant */ if ((str = ng_get_string_token(s, off, &toklen, &slen)) != NULL) { ng_parse_array_getLength_t *const getLength = type->info; int arraylen; arraylen = (*getLength)(type, start, buf); if (arraylen > *buflen) { free(str, M_NETGRAPH_PARSE); return (ERANGE); } if (slen > arraylen) { free(str, M_NETGRAPH_PARSE); return (E2BIG); } bcopy(str, buf, slen); bzero(buf + slen, arraylen - slen); free(str, M_NETGRAPH_PARSE); *off += toklen; *buflen = arraylen; return (0); } else { struct ng_parse_type subtype; subtype = ng_parse_bytearray_subtype; subtype.private = __DECONST(void *, type->info); return ng_array_parse(&subtype, s, off, start, buf, buflen); } } static int ng_bytearray_unparse(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen) { struct ng_parse_type subtype; subtype = ng_parse_bytearray_subtype; subtype.private = __DECONST(void *, type->info); return ng_array_unparse(&subtype, data, off, cbuf, cbuflen); } static int ng_bytearray_getDefault(const struct ng_parse_type *type, const u_char *const start, u_char *buf, int *buflen) { struct ng_parse_type subtype; subtype = ng_parse_bytearray_subtype; subtype.private = __DECONST(void *, type->info); return ng_array_getDefault(&subtype, start, buf, buflen); } const struct ng_parse_type ng_parse_bytearray_type = { NULL, NULL, NULL, ng_bytearray_parse, ng_bytearray_unparse, ng_bytearray_getDefault, NULL }; /************************************************************************ STRUCT NG_MESG TYPE ************************************************************************/ /* Get msg->header.arglen when "buf" is pointing to msg->data */ static int ng_parse_ng_mesg_getLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct ng_mesg *msg; msg = (const struct ng_mesg *)(buf - sizeof(*msg)); return msg->header.arglen; } /* Type for the variable length data portion of a struct ng_mesg */ static const struct ng_parse_type ng_msg_data_type = { &ng_parse_bytearray_type, &ng_parse_ng_mesg_getLength }; /* Type for the entire struct ng_mesg header with data section */ static const struct ng_parse_struct_field ng_parse_ng_mesg_type_fields[] = NG_GENERIC_NG_MESG_INFO(&ng_msg_data_type); const struct ng_parse_type ng_parse_ng_mesg_type = { &ng_parse_struct_type, &ng_parse_ng_mesg_type_fields, }; /************************************************************************ COMPOSITE HELPER ROUTINES ************************************************************************/ /* * Convert a structure or array from ASCII to binary */ static int ng_parse_composite(const struct ng_parse_type *type, const char *s, int *off, const u_char *const start, u_char *const buf, int *buflen, const enum comptype ctype) { const int num = ng_get_composite_len(type, start, buf, ctype); int nextIndex = 0; /* next implicit array index */ u_int index; /* field or element index */ int *foff; /* field value offsets in string */ int align, len, blen, error = 0; /* Initialize */ - foff = malloc(num * sizeof(*foff), M_NETGRAPH_PARSE, M_NOWAIT | M_ZERO); + foff = mallocarray(num, sizeof(*foff), M_NETGRAPH_PARSE, + M_NOWAIT | M_ZERO); if (foff == NULL) { error = ENOMEM; goto done; } /* Get opening brace/bracket */ if (ng_parse_get_token(s, off, &len) != (ctype == CT_STRUCT ? T_LBRACE : T_LBRACKET)) { error = EINVAL; goto done; } *off += len; /* Get individual element value positions in the string */ for (;;) { enum ng_parse_token tok; /* Check for closing brace/bracket */ tok = ng_parse_get_token(s, off, &len); if (tok == (ctype == CT_STRUCT ? T_RBRACE : T_RBRACKET)) { *off += len; break; } /* For arrays, the 'name' (ie, index) is optional, so distinguish name from values by seeing if the next token is an equals sign */ if (ctype != CT_STRUCT) { u_long ul; int len2, off2; char *eptr; /* If an opening brace/bracket, index is implied */ if (tok == T_LBRACE || tok == T_LBRACKET) { index = nextIndex++; goto gotIndex; } /* Might be an index, might be a value, either way... */ if (tok != T_WORD) { error = EINVAL; goto done; } /* If no equals sign follows, index is implied */ off2 = *off + len; if (ng_parse_get_token(s, &off2, &len2) != T_EQUALS) { index = nextIndex++; goto gotIndex; } /* Index was specified explicitly; parse it */ ul = strtoul(s + *off, &eptr, 0); if (ul == ULONG_MAX || eptr - (s + *off) != len) { error = EINVAL; goto done; } index = (u_int)ul; nextIndex = index + 1; *off += len + len2; } else { /* a structure field */ const struct ng_parse_struct_field *const fields = type->info; /* Find the field by name (required) in field list */ if (tok != T_WORD) { error = EINVAL; goto done; } for (index = 0; index < num; index++) { const struct ng_parse_struct_field *const field = &fields[index]; if (strncmp(&s[*off], field->name, len) == 0 && field->name[len] == '\0') break; } if (index == num) { error = ENOENT; goto done; } *off += len; /* Get equals sign */ if (ng_parse_get_token(s, off, &len) != T_EQUALS) { error = EINVAL; goto done; } *off += len; } gotIndex: /* Check array index */ if (index >= num) { error = E2BIG; goto done; } /* Save value's position and skip over it for now */ if (foff[index] != 0) { error = EALREADY; /* duplicate */ goto done; } while (isspace(s[*off])) (*off)++; foff[index] = *off; if ((error = ng_parse_skip_value(s, *off, &len)) != 0) goto done; *off += len; } /* Now build binary structure from supplied values and defaults */ for (blen = index = 0; index < num; index++) { const struct ng_parse_type *const etype = ng_get_composite_etype(type, index, ctype); int k, pad, vlen; /* Zero-pad any alignment bytes */ pad = ng_parse_get_elem_pad(type, index, ctype, blen); for (k = 0; k < pad; k++) { if (blen >= *buflen) { error = ERANGE; goto done; } buf[blen++] = 0; } /* Get value */ vlen = *buflen - blen; if (foff[index] == 0) { /* use default value */ error = ng_get_composite_elem_default(type, index, start, buf + blen, &vlen, ctype); } else { /* parse given value */ *off = foff[index]; error = INVOKE(etype, parse)(etype, s, off, start, buf + blen, &vlen); } if (error != 0) goto done; blen += vlen; } /* Make total composite structure size a multiple of its alignment */ if ((align = ALIGNMENT(type)) != 0) { while (blen % align != 0) { if (blen >= *buflen) { error = ERANGE; goto done; } buf[blen++] = 0; } } /* Done */ *buflen = blen; done: if (foff != NULL) free(foff, M_NETGRAPH_PARSE); return (error); } /* * Convert an array or structure from binary to ASCII */ static int ng_unparse_composite(const struct ng_parse_type *type, const u_char *data, int *off, char *cbuf, int cbuflen, const enum comptype ctype) { const struct ng_mesg *const hdr = (const struct ng_mesg *)(data - sizeof(*hdr)); const int num = ng_get_composite_len(type, data, data + *off, ctype); const int workSize = 20 * 1024; /* XXX hard coded constant */ int nextIndex = 0, didOne = 0; int error, index; u_char *workBuf; /* Get workspace for checking default values */ workBuf = malloc(workSize, M_NETGRAPH_PARSE, M_NOWAIT); if (workBuf == NULL) return (ENOMEM); /* Opening brace/bracket */ if ((error = ng_parse_append(&cbuf, &cbuflen, "%c", (ctype == CT_STRUCT) ? '{' : '[')) != 0) goto fail; /* Do each item */ for (index = 0; index < num; index++) { const struct ng_parse_type *const etype = ng_get_composite_etype(type, index, ctype); /* Skip any alignment pad bytes */ *off += ng_parse_get_elem_pad(type, index, ctype, *off); /* * See if element is equal to its default value; skip if so. * Copy struct ng_mesg header for types that peek into it. */ if (sizeof(*hdr) + *off < workSize) { int tempsize = workSize - sizeof(*hdr) - *off; bcopy(hdr, workBuf, sizeof(*hdr) + *off); if (ng_get_composite_elem_default(type, index, workBuf + sizeof(*hdr), workBuf + sizeof(*hdr) + *off, &tempsize, ctype) == 0 && bcmp(workBuf + sizeof(*hdr) + *off, data + *off, tempsize) == 0) { *off += tempsize; continue; } } /* Print name= */ if ((error = ng_parse_append(&cbuf, &cbuflen, " ")) != 0) goto fail; if (ctype != CT_STRUCT) { if (index != nextIndex) { nextIndex = index; if ((error = ng_parse_append(&cbuf, &cbuflen, "%d=", index)) != 0) goto fail; } nextIndex++; } else { const struct ng_parse_struct_field *const fields = type->info; if ((error = ng_parse_append(&cbuf, &cbuflen, "%s=", fields[index].name)) != 0) goto fail; } /* Print value */ if ((error = INVOKE(etype, unparse) (etype, data, off, cbuf, cbuflen)) != 0) { free(workBuf, M_NETGRAPH_PARSE); return (error); } cbuflen -= strlen(cbuf); cbuf += strlen(cbuf); didOne = 1; } /* Closing brace/bracket */ error = ng_parse_append(&cbuf, &cbuflen, "%s%c", didOne ? " " : "", (ctype == CT_STRUCT) ? '}' : ']'); fail: /* Clean up after failure */ free(workBuf, M_NETGRAPH_PARSE); return (error); } /* * Generate the default value for an element of an array or structure * Returns EOPNOTSUPP if default value is unspecified. */ static int ng_get_composite_elem_default(const struct ng_parse_type *type, int index, const u_char *const start, u_char *buf, int *buflen, const enum comptype ctype) { const struct ng_parse_type *etype; ng_getDefault_t *func; switch (ctype) { case CT_STRUCT: break; case CT_ARRAY: { const struct ng_parse_array_info *const ai = type->info; if (ai->getDefault != NULL) { return (*ai->getDefault)(type, index, start, buf, buflen); } break; } case CT_FIXEDARRAY: { const struct ng_parse_fixedarray_info *const fi = type->info; if (*fi->getDefault != NULL) { return (*fi->getDefault)(type, index, start, buf, buflen); } break; } default: panic("%s", __func__); } /* Default to element type default */ etype = ng_get_composite_etype(type, index, ctype); func = METHOD(etype, getDefault); if (func == NULL) return (EOPNOTSUPP); return (*func)(etype, start, buf, buflen); } /* * Get the number of elements in a struct, variable or fixed array. */ static int ng_get_composite_len(const struct ng_parse_type *type, const u_char *const start, const u_char *buf, const enum comptype ctype) { switch (ctype) { case CT_STRUCT: { const struct ng_parse_struct_field *const fields = type->info; int numFields = 0; for (numFields = 0; ; numFields++) { const struct ng_parse_struct_field *const fi = &fields[numFields]; if (fi->name == NULL) break; } return (numFields); } case CT_ARRAY: { const struct ng_parse_array_info *const ai = type->info; return (*ai->getLength)(type, start, buf); } case CT_FIXEDARRAY: { const struct ng_parse_fixedarray_info *const fi = type->info; return fi->length; } default: panic("%s", __func__); } return (0); } /* * Return the type of the index'th element of a composite structure */ static const struct ng_parse_type * ng_get_composite_etype(const struct ng_parse_type *type, int index, const enum comptype ctype) { const struct ng_parse_type *etype = NULL; switch (ctype) { case CT_STRUCT: { const struct ng_parse_struct_field *const fields = type->info; etype = fields[index].type; break; } case CT_ARRAY: { const struct ng_parse_array_info *const ai = type->info; etype = ai->elementType; break; } case CT_FIXEDARRAY: { const struct ng_parse_fixedarray_info *const fi = type->info; etype = fi->elementType; break; } default: panic("%s", __func__); } return (etype); } /* * Get the number of bytes to skip to align for the next * element in a composite structure. */ static int ng_parse_get_elem_pad(const struct ng_parse_type *type, int index, enum comptype ctype, int posn) { const struct ng_parse_type *const etype = ng_get_composite_etype(type, index, ctype); int align; /* Get element's alignment, and possibly override */ align = ALIGNMENT(etype); if (ctype == CT_STRUCT) { const struct ng_parse_struct_field *const fields = type->info; if (fields[index].alignment != 0) align = fields[index].alignment; } /* Return number of bytes to skip to align */ return (align ? (align - (posn % align)) % align : 0); } /************************************************************************ PARSING HELPER ROUTINES ************************************************************************/ /* * Append to a fixed length string buffer. */ static int ng_parse_append(char **cbufp, int *cbuflenp, const char *fmt, ...) { va_list args; int len; va_start(args, fmt); len = vsnprintf(*cbufp, *cbuflenp, fmt, args); va_end(args); if (len >= *cbuflenp) return ERANGE; *cbufp += len; *cbuflenp -= len; return (0); } /* * Skip over a value */ static int ng_parse_skip_value(const char *s, int off0, int *lenp) { int len, nbracket, nbrace; int off = off0; len = nbracket = nbrace = 0; do { switch (ng_parse_get_token(s, &off, &len)) { case T_LBRACKET: nbracket++; break; case T_LBRACE: nbrace++; break; case T_RBRACKET: if (nbracket-- == 0) return (EINVAL); break; case T_RBRACE: if (nbrace-- == 0) return (EINVAL); break; case T_EOF: return (EINVAL); default: break; } off += len; } while (nbracket > 0 || nbrace > 0); *lenp = off - off0; return (0); } /* * Find the next token in the string, starting at offset *startp. * Returns the token type, with *startp pointing to the first char * and *lenp the length. */ enum ng_parse_token ng_parse_get_token(const char *s, int *startp, int *lenp) { char *t; int i; while (isspace(s[*startp])) (*startp)++; switch (s[*startp]) { case '\0': *lenp = 0; return T_EOF; case '{': *lenp = 1; return T_LBRACE; case '}': *lenp = 1; return T_RBRACE; case '[': *lenp = 1; return T_LBRACKET; case ']': *lenp = 1; return T_RBRACKET; case '=': *lenp = 1; return T_EQUALS; case '"': if ((t = ng_get_string_token(s, startp, lenp, NULL)) == NULL) return T_ERROR; free(t, M_NETGRAPH_PARSE); return T_STRING; default: for (i = *startp + 1; s[i] != '\0' && !isspace(s[i]) && s[i] != '{' && s[i] != '}' && s[i] != '[' && s[i] != ']' && s[i] != '=' && s[i] != '"'; i++) ; *lenp = i - *startp; return T_WORD; } } /* * Get a string token, which must be enclosed in double quotes. * The normal C backslash escapes are recognized. */ char * ng_get_string_token(const char *s, int *startp, int *lenp, int *slenp) { char *cbuf, *p; int start, off; int slen; while (isspace(s[*startp])) (*startp)++; start = *startp; if (s[*startp] != '"') return (NULL); cbuf = malloc(strlen(s + start), M_NETGRAPH_PARSE, M_NOWAIT); if (cbuf == NULL) return (NULL); strcpy(cbuf, s + start + 1); for (slen = 0, off = 1, p = cbuf; *p != '\0'; slen++, off++, p++) { if (*p == '"') { *p = '\0'; *lenp = off + 1; if (slenp != NULL) *slenp = slen; return (cbuf); } else if (p[0] == '\\' && p[1] != '\0') { int x, k; char *v; strcpy(p, p + 1); v = p; switch (*p) { case 't': *v = '\t'; off++; continue; case 'n': *v = '\n'; off++; continue; case 'r': *v = '\r'; off++; continue; case 'v': *v = '\v'; off++; continue; case 'f': *v = '\f'; off++; continue; case '"': *v = '"'; off++; continue; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': for (x = k = 0; k < 3 && *v >= '0' && *v <= '7'; v++) { x = (x << 3) + (*v - '0'); off++; } *--v = (char)x; break; case 'x': for (v++, x = k = 0; k < 2 && isxdigit(*v); v++) { x = (x << 4) + (isdigit(*v) ? (*v - '0') : (tolower(*v) - 'a' + 10)); off++; } *--v = (char)x; break; default: continue; } strcpy(p, v); } } free(cbuf, M_NETGRAPH_PARSE); return (NULL); /* no closing quote */ } /* * Encode a string so it can be safely put in double quotes. * Caller must free the result. Exactly "slen" characters * are encoded. */ char * ng_encode_string(const char *raw, int slen) { char *cbuf; int off = 0; int i; cbuf = malloc(strlen(raw) * 4 + 3, M_NETGRAPH_PARSE, M_NOWAIT); if (cbuf == NULL) return (NULL); cbuf[off++] = '"'; for (i = 0; i < slen; i++, raw++) { switch (*raw) { case '\t': cbuf[off++] = '\\'; cbuf[off++] = 't'; break; case '\f': cbuf[off++] = '\\'; cbuf[off++] = 'f'; break; case '\n': cbuf[off++] = '\\'; cbuf[off++] = 'n'; break; case '\r': cbuf[off++] = '\\'; cbuf[off++] = 'r'; break; case '\v': cbuf[off++] = '\\'; cbuf[off++] = 'v'; break; case '"': case '\\': cbuf[off++] = '\\'; cbuf[off++] = *raw; break; default: if (*raw < 0x20 || *raw > 0x7e) { off += sprintf(cbuf + off, "\\x%02x", (u_char)*raw); break; } cbuf[off++] = *raw; break; } } cbuf[off++] = '"'; cbuf[off] = '\0'; return (cbuf); } /************************************************************************ VIRTUAL METHOD LOOKUP ************************************************************************/ static ng_parse_t * ng_get_parse_method(const struct ng_parse_type *t) { while (t != NULL && t->parse == NULL) t = t->supertype; return (t ? t->parse : NULL); } static ng_unparse_t * ng_get_unparse_method(const struct ng_parse_type *t) { while (t != NULL && t->unparse == NULL) t = t->supertype; return (t ? t->unparse : NULL); } static ng_getDefault_t * ng_get_getDefault_method(const struct ng_parse_type *t) { while (t != NULL && t->getDefault == NULL) t = t->supertype; return (t ? t->getDefault : NULL); } static ng_getAlign_t * ng_get_getAlign_method(const struct ng_parse_type *t) { while (t != NULL && t->getAlign == NULL) t = t->supertype; return (t ? t->getAlign : NULL); }