Index: head/sys/netgraph/ng_bridge.c =================================================================== --- head/sys/netgraph/ng_bridge.c (revision 131154) +++ head/sys/netgraph/ng_bridge.c (revision 131155) @@ -1,1062 +1,1051 @@ /* * 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 #include #ifdef NG_SEPARATE_MALLOC 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 */ 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(void *arg); 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 }, { 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 */ MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH_BRIDGE, M_NOWAIT | M_ZERO); if (priv == NULL) return (ENOMEM); callout_init(&priv->timer, 0); /* Allocate and initialize hash table, etc. */ MALLOC(priv->tab, struct ng_bridge_bucket *, MIN_BUCKETS * sizeof(*priv->tab), M_NETGRAPH_BRIDGE, M_NOWAIT | M_ZERO); if (priv->tab == NULL) { FREE(priv, M_NETGRAPH_BRIDGE); return (ENOMEM); } 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 beteen 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 */ callout_reset(&priv->timer, hz, ng_bridge_timeout, priv->node); /* 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); MALLOC(priv->links[linkNum], struct ng_bridge_link *, 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; } 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; - meta_p meta; 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 (priv->conf.ipfw[linkNum] && fw_enable && ip_fw_chk_ptr != NULL) { /* XXX not implemented yet */ } /* * 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 */ - meta = NGI_META(item); /* peek.. */ firstLink = NULL; for (linkNum = linksSeen = 0; linksSeen <= priv->numLinks; linkNum++) { struct ng_bridge_link *destLink; - meta_p meta2 = NULL; 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 and meta info for sending. + * Copy mbuf info for sending. */ m2 = m_dup(m, M_DONTWAIT); /* XXX m_copypacket() */ if (m2 == NULL) { link->stats.memoryFailures++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ENOBUFS); } - if (meta != NULL - && (meta2 = ng_copy_meta(meta)) == NULL) { - link->stats.memoryFailures++; - m_freem(m2); - NG_FREE_ITEM(item); - NG_FREE_M(m); - return (ENOMEM); - } } /* 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(error, destLink->hook, m2, meta2); + 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 except the timer. There's no way to * avoid another possible timeout event (it may have already * been dequeued), so we can't free the node yet. */ KASSERT(priv->numLinks == 0 && priv->numHosts == 0, ("%s: numLinks=%d numHosts=%d", __func__, priv->numLinks, priv->numHosts)); FREE(priv->tab, M_NETGRAPH_BRIDGE); /* NG_INVALID flag is now set so node will be freed at next timeout */ 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)))) { 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 */ MALLOC(hent, struct ng_bridge_hent *, 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 */ MALLOC(newTab, struct ng_bridge_bucket *, 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. * * If the node has the NG_INVALID flag set, our job is to kill it. */ static void ng_bridge_timeout(void *arg) { const node_p node = arg; const priv_p priv = NG_NODE_PRIVATE(node); int s, bucket; int counter = 0; int linkNum; /* If node was shut down, this is the final lingering timeout */ s = splnet(); if (NG_NODE_NOT_VALID(node)) { FREE(priv, M_NETGRAPH_BRIDGE); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); splx(s); return; } /* Register a new timeout, keeping the existing node reference */ callout_reset(&priv->timer, hz, ng_bridge_timeout, node); /* 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)); /* Done */ splx(s); } /* * 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_NAME(node) != NULL) 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_etf.c =================================================================== --- head/sys/netgraph/ng_etf.c (revision 131154) +++ head/sys/netgraph/ng_etf.c (revision 131155) @@ -1,499 +1,499 @@ /*- * ng_etf.c Ethertype filter * * Copyright (c) 2001, FreeBSD Incorporated * 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. * * Author: Julian Elischer * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* If you do complicated mallocs you may want to do this */ /* and use it for your mallocs */ #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_ETF, "netgraph_etf", "netgraph etf node "); #else #define M_NETGRAPH_ETF M_NETGRAPH #endif /* * This section contains the netgraph method declarations for the * etf node. These methods define the netgraph 'type'. */ static ng_constructor_t ng_etf_constructor; static ng_rcvmsg_t ng_etf_rcvmsg; static ng_shutdown_t ng_etf_shutdown; static ng_newhook_t ng_etf_newhook; static ng_connect_t ng_etf_connect; static ng_rcvdata_t ng_etf_rcvdata; /* note these are both ng_rcvdata_t */ static ng_disconnect_t ng_etf_disconnect; /* Parse type for struct ng_etfstat */ static const struct ng_parse_struct_field ng_etf_stat_type_fields[] = NG_ETF_STATS_TYPE_INFO; static const struct ng_parse_type ng_etf_stat_type = { &ng_parse_struct_type, &ng_etf_stat_type_fields }; /* Parse type for struct ng_setfilter */ static const struct ng_parse_struct_field ng_etf_filter_type_fields[] = NG_ETF_FILTER_TYPE_INFO; static const struct ng_parse_type ng_etf_filter_type = { &ng_parse_struct_type, &ng_etf_filter_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_etf_cmdlist[] = { { NGM_ETF_COOKIE, NGM_ETF_GET_STATUS, "getstatus", NULL, &ng_etf_stat_type, }, { NGM_ETF_COOKIE, NGM_ETF_SET_FLAG, "setflag", &ng_parse_int32_type, NULL }, { NGM_ETF_COOKIE, NGM_ETF_SET_FILTER, "setfilter", &ng_etf_filter_type, NULL }, { 0 } }; /* Netgraph node type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_ETF_NODE_TYPE, .constructor = ng_etf_constructor, .rcvmsg = ng_etf_rcvmsg, .shutdown = ng_etf_shutdown, .newhook = ng_etf_newhook, .connect = ng_etf_connect, .rcvdata = ng_etf_rcvdata, .disconnect = ng_etf_disconnect, .cmdlist = ng_etf_cmdlist, }; NETGRAPH_INIT(etf, &typestruct); /* Information we store for each hook on each node */ struct ETF_hookinfo { hook_p hook; }; struct filter { LIST_ENTRY(filter) next; u_int16_t ethertype; /* network order ethertype */ hook_p match_hook; /* Hook to use on a match */ }; #define HASHSIZE 16 /* Dont change this without changing HASH() */ #define HASH(et) ((((et)>>12)+((et)>>8)+((et)>>4)+(et)) & 0x0f) LIST_HEAD(filterhead, filter); /* Information we store for each node */ struct ETF { struct ETF_hookinfo downstream_hook; struct ETF_hookinfo nomatch_hook; node_p node; /* back pointer to node */ u_int packets_in; /* packets in from downstream */ u_int packets_out; /* packets out towards downstream */ u_int32_t flags; struct filterhead hashtable[HASHSIZE]; }; typedef struct ETF *etf_p; static struct filter * ng_etf_findentry(etf_p etfp, u_int16_t ethertype) { struct filterhead *chain = etfp->hashtable + HASH(ethertype); struct filter *fil; LIST_FOREACH(fil, chain, next) { if (fil->ethertype == ethertype) { return (fil); } } return (NULL); } /* * Allocate the private data structure. The generic node has already * been created. Link them together. We arrive with a reference to the node * i.e. the reference count is incremented for us already. */ static int ng_etf_constructor(node_p node) { etf_p privdata; int i; /* Initialize private descriptor */ MALLOC(privdata, etf_p, sizeof(*privdata), M_NETGRAPH_ETF, M_NOWAIT | M_ZERO); if (privdata == NULL) return (ENOMEM); for (i = 0; i < HASHSIZE; i++) { LIST_INIT((privdata->hashtable + i)); } /* Link structs together; this counts as our one reference to node */ NG_NODE_SET_PRIVATE(node, privdata); privdata->node = node; return (0); } /* * Give our ok for a hook to be added... * All names are ok. Two names are special. */ static int ng_etf_newhook(node_p node, hook_p hook, const char *name) { const etf_p etfp = NG_NODE_PRIVATE(node); struct ETF_hookinfo *hpriv; if (strcmp(name, NG_ETF_HOOK_DOWNSTREAM) == 0) { etfp->downstream_hook.hook = hook; NG_HOOK_SET_PRIVATE(hook, &etfp->downstream_hook); etfp->packets_in = 0; etfp->packets_out = 0; } else if (strcmp(name, NG_ETF_HOOK_NOMATCH) == 0) { etfp->nomatch_hook.hook = hook; NG_HOOK_SET_PRIVATE(hook, &etfp->nomatch_hook); } else { /* * Any other hook name is valid and can * later be associated with a filter rule. */ MALLOC(hpriv, struct ETF_hookinfo *, sizeof(*hpriv), M_NETGRAPH_ETF, M_NOWAIT | M_ZERO); if (hpriv == NULL) { return (ENOMEM); } NG_HOOK_SET_PRIVATE(hook, hpriv); hpriv->hook = hook; } return(0); } /* * Get a netgraph control message. * We actually recieve a queue item that has a pointer to the message. * If we free the item, the message will be freed too, unless we remove * it from the item using NGI_GET_MSG(); * The return address is also stored in the item, as an ng_ID_t, * accessible as NGI_RETADDR(item); * Check it is one we understand. If needed, send a response. * We could save the address for an async action later, but don't here. * Always free the message. * The response should be in a malloc'd region that the caller can 'free'. * The NG_MKRESPONSE macro does all this for us. * A response is not required. * Theoretically you could respond defferently to old message types if * the cookie in the header didn't match what we consider to be current * (so that old userland programs could continue to work). */ static int ng_etf_rcvmsg(node_p node, item_p item, hook_p lasthook) { const etf_p etfp = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); /* Deal with message according to cookie and command */ switch (msg->header.typecookie) { case NGM_ETF_COOKIE: switch (msg->header.cmd) { case NGM_ETF_GET_STATUS: { struct ng_etfstat *stats; NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); if (!resp) { error = ENOMEM; break; } stats = (struct ng_etfstat *) resp->data; stats->packets_in = etfp->packets_in; stats->packets_out = etfp->packets_out; break; } case NGM_ETF_SET_FLAG: if (msg->header.arglen != sizeof(u_int32_t)) { error = EINVAL; break; } etfp->flags = *((u_int32_t *) msg->data); break; case NGM_ETF_SET_FILTER: { struct ng_etffilter *f; struct filter *fil; hook_p hook; /* Check message long enough for this command */ if (msg->header.arglen != sizeof(*f)) { error = EINVAL; break; } /* Make sure hook referenced exists */ f = (struct ng_etffilter *)msg->data; hook = ng_findhook(node, f->matchhook); if (hook == NULL) { error = ENOENT; break; } /* and is not the downstream hook */ if (hook == etfp->downstream_hook.hook) { error = EINVAL; break; } /* Check we don't already trap this ethertype */ if (ng_etf_findentry(etfp, htons(f->ethertype))) { error = EEXIST; break; } /* * Ok, make the filter and put it in the * hashtable ready for matching. */ MALLOC(fil, struct filter *, sizeof(*fil), M_NETGRAPH_ETF, M_NOWAIT | M_ZERO); if (fil == NULL) { error = ENOMEM; break; } fil->match_hook = hook; fil->ethertype = htons(f->ethertype); LIST_INSERT_HEAD( etfp->hashtable + HASH(fil->ethertype), fil, next); } break; default: error = EINVAL; /* unknown command */ break; } break; default: error = EINVAL; /* unknown cookie type */ break; } /* Take care of synchronous response, if any */ NG_RESPOND_MSG(error, node, item, resp); /* Free the message and return */ NG_FREE_MSG(msg); return(error); } /* * Receive data, and do something with it. * Actually we receive a queue item which holds the data. - * If we free the item it wil also froo the data and metadata unless - * we have previously disassociated them using the NGI_GET_etf() macros. + * If we free the item it will also free the data unless we have previously + * disassociated it using the NGI_GET_etf() macro. * Possibly send it out on another link after processing. * Possibly do something different if it comes from different - * hooks. the caller will never free m or meta, so - * if we use up this data or abort we must free BOTH of these. + * hooks. The caller will never free m , so if we use up this data + * or abort we must free it. * * If we want, we may decide to force this data to be queued and reprocessed * at the netgraph NETISR time. * We would do that by setting the HK_QUEUE flag on our hook. We would do that * in the connect() method. */ static int ng_etf_rcvdata(hook_p hook, item_p item ) { const etf_p etfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ether_header *eh; int error = 0; struct mbuf *m; u_int16_t ethertype; struct filter *fil; if (NG_HOOK_PRIVATE(hook) == NULL) { /* Shouldn't happen but.. */ NG_FREE_ITEM(item); } /* * Everything not from the downstream hook goes to the * downstream hook. But only if it matches the ethertype * of the source hook. Un matching must go to/from 'nomatch'. */ /* Make sure we have an entire header */ NGI_GET_M(item, m); if (m->m_len < sizeof(*eh) ) { m = m_pullup(m, sizeof(*eh)); if (m == NULL) { NG_FREE_ITEM(item); return(EINVAL); } } eh = mtod(m, struct ether_header *); ethertype = eh->ether_type; fil = ng_etf_findentry(etfp, ethertype); /* * if from downstream, select between a match hook or * the nomatch hook */ if (hook == etfp->downstream_hook.hook) { etfp->packets_in++; if (fil && fil->match_hook) { NG_FWD_NEW_DATA(error, item, fil->match_hook, m); } else { NG_FWD_NEW_DATA(error, item,etfp->nomatch_hook.hook, m); } } else { /* * It must be heading towards the downstream. * Check that it's ethertype matches * the filters for it's input hook. * If it doesn't have one, check it's from nomatch. */ if ((fil && (fil->match_hook != hook)) || ((fil == NULL) && (hook != etfp->nomatch_hook.hook))) { NG_FREE_ITEM(item); NG_FREE_M(m); return (EPROTOTYPE); } NG_FWD_NEW_DATA( error, item, etfp->downstream_hook.hook, m); if (error == 0) { etfp->packets_out++; } } return (error); } /* * Do local shutdown processing.. * All our links and the name have already been removed. */ static int ng_etf_shutdown(node_p node) { const etf_p privdata = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(privdata->node); FREE(privdata, M_NETGRAPH_ETF); return (0); } /* * This is called once we've already connected a new hook to the other node. * It gives us a chance to balk at the last minute. */ static int ng_etf_connect(hook_p hook) { return (0); } /* * Hook disconnection * * For this type, removal of the last link destroys the node */ static int ng_etf_disconnect(hook_p hook) { const etf_p etfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); int i; struct filter *fil1, *fil2; /* purge any rules that refer to this filter */ for (i = 0; i < HASHSIZE; i++) { fil1 = LIST_FIRST(&etfp->hashtable[i]); while (fil1 != NULL) { fil2 = LIST_NEXT(fil1, next); if (fil1->match_hook == hook) { LIST_REMOVE(fil1, next); FREE(fil1, M_NETGRAPH_ETF); } fil1 = fil2; } } /* If it's not one of the special hooks, then free it */ if (hook == etfp->downstream_hook.hook) { etfp->downstream_hook.hook = NULL; } else if (hook == etfp->nomatch_hook.hook) { etfp->nomatch_hook.hook = NULL; } else { if (NG_HOOK_PRIVATE(hook)) /* Paranoia */ FREE(NG_HOOK_PRIVATE(hook), M_NETGRAPH_ETF); } NG_HOOK_SET_PRIVATE(hook, NULL); if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) /* already shutting down? */ ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } Index: head/sys/netgraph/ng_ether.c =================================================================== --- head/sys/netgraph/ng_ether.c (revision 131154) +++ head/sys/netgraph/ng_ether.c (revision 131155) @@ -1,684 +1,677 @@ /* * ng_ether.c * * Copyright (c) 1996-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. * * Authors: Archie Cobbs * Julian Elischer * * $FreeBSD$ */ /* * ng_ether(4) netgraph node type */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IFP2NG(ifp) ((struct ng_node *)((struct arpcom *)(ifp))->ac_netgraph) /* Per-node private data */ struct private { struct ifnet *ifp; /* associated interface */ hook_p upper; /* upper hook connection */ hook_p lower; /* lower hook connection */ hook_p orphan; /* orphan hook connection */ u_char autoSrcAddr; /* always overwrite source address */ u_char promisc; /* promiscuous mode enabled */ u_long hwassist; /* hardware checksum capabilities */ u_int flags; /* flags e.g. really die */ }; typedef struct private *priv_p; /* Hook pointers used by if_ethersubr.c to callback to netgraph */ extern void (*ng_ether_input_p)(struct ifnet *ifp, struct mbuf **mp); extern void (*ng_ether_input_orphan_p)(struct ifnet *ifp, struct mbuf *m); extern int (*ng_ether_output_p)(struct ifnet *ifp, struct mbuf **mp); extern void (*ng_ether_attach_p)(struct ifnet *ifp); extern void (*ng_ether_detach_p)(struct ifnet *ifp); /* Functional hooks called from if_ethersubr.c */ static void ng_ether_input(struct ifnet *ifp, struct mbuf **mp); static void ng_ether_input_orphan(struct ifnet *ifp, struct mbuf *m); static int ng_ether_output(struct ifnet *ifp, struct mbuf **mp); static void ng_ether_attach(struct ifnet *ifp); static void ng_ether_detach(struct ifnet *ifp); /* Other functions */ -static int ng_ether_rcv_lower(node_p node, struct mbuf *m, meta_p meta); -static int ng_ether_rcv_upper(node_p node, struct mbuf *m, meta_p meta); +static int ng_ether_rcv_lower(node_p node, struct mbuf *m); +static int ng_ether_rcv_upper(node_p node, struct mbuf *m); /* Netgraph node methods */ static ng_constructor_t ng_ether_constructor; static ng_rcvmsg_t ng_ether_rcvmsg; static ng_shutdown_t ng_ether_shutdown; static ng_newhook_t ng_ether_newhook; static ng_connect_t ng_ether_connect; static ng_rcvdata_t ng_ether_rcvdata; static ng_disconnect_t ng_ether_disconnect; static int ng_ether_mod_event(module_t mod, int event, void *data); /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_ether_cmdlist[] = { { NGM_ETHER_COOKIE, NGM_ETHER_GET_IFNAME, "getifname", NULL, &ng_parse_string_type }, { NGM_ETHER_COOKIE, NGM_ETHER_GET_IFINDEX, "getifindex", NULL, &ng_parse_int32_type }, { NGM_ETHER_COOKIE, NGM_ETHER_GET_ENADDR, "getenaddr", NULL, &ng_parse_enaddr_type }, { NGM_ETHER_COOKIE, NGM_ETHER_SET_ENADDR, "setenaddr", &ng_parse_enaddr_type, NULL }, { NGM_ETHER_COOKIE, NGM_ETHER_GET_PROMISC, "getpromisc", NULL, &ng_parse_int32_type }, { NGM_ETHER_COOKIE, NGM_ETHER_SET_PROMISC, "setpromisc", &ng_parse_int32_type, NULL }, { NGM_ETHER_COOKIE, NGM_ETHER_GET_AUTOSRC, "getautosrc", NULL, &ng_parse_int32_type }, { NGM_ETHER_COOKIE, NGM_ETHER_SET_AUTOSRC, "setautosrc", &ng_parse_int32_type, NULL }, { 0 } }; static struct ng_type ng_ether_typestruct = { .version = NG_ABI_VERSION, .name = NG_ETHER_NODE_TYPE, .mod_event = ng_ether_mod_event, .constructor = ng_ether_constructor, .rcvmsg = ng_ether_rcvmsg, .shutdown = ng_ether_shutdown, .newhook = ng_ether_newhook, .connect = ng_ether_connect, .rcvdata = ng_ether_rcvdata, .disconnect = ng_ether_disconnect, .cmdlist = ng_ether_cmdlist, }; MODULE_VERSION(ng_ether, 1); NETGRAPH_INIT(ether, &ng_ether_typestruct); /****************************************************************** ETHERNET FUNCTION HOOKS ******************************************************************/ /* * Handle a packet that has come in on an interface. We get to * look at it here before any upper layer protocols do. * * NOTE: this function will get called at splimp() */ static void ng_ether_input(struct ifnet *ifp, struct mbuf **mp) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); int error; /* If "lower" hook not connected, let packet continue */ if (priv->lower == NULL) return; NG_SEND_DATA_ONLY(error, priv->lower, *mp); /* sets *mp = NULL */ } /* * Handle a packet that has come in on an interface, and which * does not match any of our known protocols (an ``orphan''). * * NOTE: this function will get called at splimp() */ static void ng_ether_input_orphan(struct ifnet *ifp, struct mbuf *m) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); int error; /* If "orphan" hook not connected, discard packet */ if (priv->orphan == NULL) { m_freem(m); return; } NG_SEND_DATA_ONLY(error, priv->orphan, m); } /* * Handle a packet that is going out on an interface. * The Ethernet header is already attached to the mbuf. */ static int ng_ether_output(struct ifnet *ifp, struct mbuf **mp) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); int error = 0; /* If "upper" hook not connected, let packet continue */ if (priv->upper == NULL) return (0); /* Send it out "upper" hook */ NG_SEND_DATA_ONLY(error, priv->upper, *mp); return (error); } /* * A new Ethernet interface has been attached. * Create a new node for it, etc. */ static void ng_ether_attach(struct ifnet *ifp) { priv_p priv; node_p node; /* Create node */ KASSERT(!IFP2NG(ifp), ("%s: node already exists?", __func__)); if (ng_make_node_common(&ng_ether_typestruct, &node) != 0) { log(LOG_ERR, "%s: can't %s for %s\n", __func__, "create node", ifp->if_xname); return; } /* Allocate private data */ MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); if (priv == NULL) { log(LOG_ERR, "%s: can't %s for %s\n", __func__, "allocate memory", ifp->if_xname); NG_NODE_UNREF(node); return; } NG_NODE_SET_PRIVATE(node, priv); priv->ifp = ifp; IFP2NG(ifp) = node; priv->autoSrcAddr = 1; priv->hwassist = ifp->if_hwassist; /* Try to give the node the same name as the interface */ if (ng_name_node(node, ifp->if_xname) != 0) { log(LOG_WARNING, "%s: can't name node %s\n", __func__, ifp->if_xname); } } /* * An Ethernet interface is being detached. * REALLY Destroy its node. */ static void ng_ether_detach(struct ifnet *ifp) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); if (node == NULL) /* no node (why not?), ignore */ return; NG_NODE_REALLY_DIE(node); /* Force real removal of node */ /* * We can't assume the ifnet is still around when we run shutdown * So zap it now. XXX We HOPE that anything running at this time * handles it (as it should in the non netgraph case). */ IFP2NG(ifp) = NULL; priv->ifp = NULL; /* XXX race if interrupted an output packet */ ng_rmnode_self(node); /* remove all netgraph parts */ } /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * It is not possible or allowable to create a node of this type. * Nodes get created when the interface is attached (or, when * this node type's KLD is loaded). */ static int ng_ether_constructor(node_p node) { return (EINVAL); } /* * Check for attaching a new hook. */ static int ng_ether_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); hook_p *hookptr; /* Divert hook is an alias for lower */ if (strcmp(name, NG_ETHER_HOOK_DIVERT) == 0) name = NG_ETHER_HOOK_LOWER; /* Which hook? */ if (strcmp(name, NG_ETHER_HOOK_UPPER) == 0) hookptr = &priv->upper; else if (strcmp(name, NG_ETHER_HOOK_LOWER) == 0) hookptr = &priv->lower; else if (strcmp(name, NG_ETHER_HOOK_ORPHAN) == 0) hookptr = &priv->orphan; else return (EINVAL); /* Check if already connected (shouldn't be, but doesn't hurt) */ if (*hookptr != NULL) return (EISCONN); /* Disable hardware checksums while 'upper' hook is connected */ if (hookptr == &priv->upper) priv->ifp->if_hwassist = 0; /* OK */ *hookptr = hook; return (0); } /* * Hooks are attached, adjust to force queueing. * We don't really care which hook it is. * they should all be queuing for outgoing data. */ static int ng_ether_connect(hook_p hook) { NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); return (0); } /* * Receive an incoming control message. */ static int ng_ether_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_ETHER_COOKIE: switch (msg->header.cmd) { case NGM_ETHER_GET_IFNAME: NG_MKRESPONSE(resp, msg, IFNAMSIZ + 1, M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } strlcpy(resp->data, priv->ifp->if_xname, IFNAMSIZ + 1); break; case NGM_ETHER_GET_IFINDEX: NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } *((u_int32_t *)resp->data) = priv->ifp->if_index; break; case NGM_ETHER_GET_ENADDR: NG_MKRESPONSE(resp, msg, ETHER_ADDR_LEN, M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bcopy((IFP2AC(priv->ifp))->ac_enaddr, resp->data, ETHER_ADDR_LEN); break; case NGM_ETHER_SET_ENADDR: { if (msg->header.arglen != ETHER_ADDR_LEN) { error = EINVAL; break; } error = if_setlladdr(priv->ifp, (u_char *)msg->data, ETHER_ADDR_LEN); break; } case NGM_ETHER_GET_PROMISC: NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } *((u_int32_t *)resp->data) = priv->promisc; break; case NGM_ETHER_SET_PROMISC: { u_char want; if (msg->header.arglen != sizeof(u_int32_t)) { error = EINVAL; break; } want = !!*((u_int32_t *)msg->data); if (want ^ priv->promisc) { if ((error = ifpromisc(priv->ifp, want)) != 0) break; priv->promisc = want; } break; } case NGM_ETHER_GET_AUTOSRC: NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } *((u_int32_t *)resp->data) = priv->autoSrcAddr; break; case NGM_ETHER_SET_AUTOSRC: if (msg->header.arglen != sizeof(u_int32_t)) { error = EINVAL; break; } priv->autoSrcAddr = !!*((u_int32_t *)msg->data); break; default: error = EINVAL; break; } break; default: error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook. */ static int ng_ether_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; - meta_p meta; NGI_GET_M(item, m); - NGI_GET_META(item, meta); NG_FREE_ITEM(item); + if (hook == priv->lower || hook == priv->orphan) - return ng_ether_rcv_lower(node, m, meta); + return ng_ether_rcv_lower(node, m); if (hook == priv->upper) - return ng_ether_rcv_upper(node, m, meta); + return ng_ether_rcv_upper(node, m); panic("%s: weird hook", __func__); #ifdef RESTARTABLE_PANICS /* so we don't get an error msg in LINT */ return NULL; #endif } /* * Handle an mbuf received on the "lower" or "orphan" hook. */ static int -ng_ether_rcv_lower(node_p node, struct mbuf *m, meta_p meta) +ng_ether_rcv_lower(node_p node, struct mbuf *m) { const priv_p priv = NG_NODE_PRIVATE(node); struct ifnet *const ifp = priv->ifp; - /* Discard meta info */ - NG_FREE_META(meta); - /* Check whether interface is ready for packets */ if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) { NG_FREE_M(m); return (ENETDOWN); } /* Make sure header is fully pulled up */ if (m->m_pkthdr.len < sizeof(struct ether_header)) { NG_FREE_M(m); return (EINVAL); } if (m->m_len < sizeof(struct ether_header) && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) return (ENOBUFS); /* Drop in the MAC address if desired */ if (priv->autoSrcAddr) { /* Make the mbuf writable if it's not already */ if (!M_WRITABLE(m) && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) return (ENOBUFS); /* Overwrite source MAC address */ bcopy((IFP2AC(ifp))->ac_enaddr, mtod(m, struct ether_header *)->ether_shost, ETHER_ADDR_LEN); } /* Send it on its way */ return ether_output_frame(ifp, m); } /* * Handle an mbuf received on the "upper" hook. */ static int -ng_ether_rcv_upper(node_p node, struct mbuf *m, meta_p meta) +ng_ether_rcv_upper(node_p node, struct mbuf *m) { const priv_p priv = NG_NODE_PRIVATE(node); - - /* Discard meta info */ - NG_FREE_META(meta); m->m_pkthdr.rcvif = priv->ifp; /* Route packet back in */ ether_demux(priv->ifp, m); return (0); } /* * Shutdown node. This resets the node but does not remove it * unless the REALLY_DIE flag is set. */ static int ng_ether_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); if (node->nd_flags & NG_REALLY_DIE) { /* * WE came here because the ethernet card is being unloaded, * so stop being persistant. * Actually undo all the things we did on creation. * Assume the ifp has already been freed. */ NG_NODE_SET_PRIVATE(node, NULL); FREE(priv, M_NETGRAPH); NG_NODE_UNREF(node); /* free node itself */ return (0); } if (priv->promisc) { /* disable promiscuous mode */ (void)ifpromisc(priv->ifp, 0); priv->promisc = 0; } priv->autoSrcAddr = 1; /* reset auto-src-addr flag */ node->nd_flags &= ~NG_INVALID; /* Signal ng_rmnode we are persisant */ return (0); } /* * Hook disconnection. */ static int ng_ether_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); if (hook == priv->upper) { priv->upper = NULL; if (priv->ifp != NULL) /* restore h/w csum */ priv->ifp->if_hwassist = priv->hwassist; } else if (hook == priv->lower) priv->lower = NULL; else if (hook == priv->orphan) priv->orphan = NULL; else panic("%s: weird hook", __func__); if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); /* reset node */ return (0); } /****************************************************************** INITIALIZATION ******************************************************************/ /* * Handle loading and unloading for this node type. */ static int ng_ether_mod_event(module_t mod, int event, void *data) { struct ifnet *ifp; int error = 0; int s; s = splnet(); switch (event) { case MOD_LOAD: /* Register function hooks */ if (ng_ether_attach_p != NULL) { error = EEXIST; break; } ng_ether_attach_p = ng_ether_attach; ng_ether_detach_p = ng_ether_detach; ng_ether_output_p = ng_ether_output; ng_ether_input_p = ng_ether_input; ng_ether_input_orphan_p = ng_ether_input_orphan; /* Create nodes for any already-existing Ethernet interfaces */ IFNET_RLOCK(); TAILQ_FOREACH(ifp, &ifnet, if_link) { if (ifp->if_type == IFT_ETHER || ifp->if_type == IFT_L2VLAN) ng_ether_attach(ifp); } IFNET_RUNLOCK(); break; case MOD_UNLOAD: /* * Note that the base code won't try to unload us until * all nodes have been removed, and that can't happen * until all Ethernet interfaces are removed. In any * case, we know there are no nodes left if the action * is MOD_UNLOAD, so there's no need to detach any nodes. */ /* Unregister function hooks */ ng_ether_attach_p = NULL; ng_ether_detach_p = NULL; ng_ether_output_p = NULL; ng_ether_input_p = NULL; ng_ether_input_orphan_p = NULL; break; default: error = EOPNOTSUPP; break; } splx(s); return (error); } Index: head/sys/netgraph/ng_gif.c =================================================================== --- head/sys/netgraph/ng_gif.c (revision 131154) +++ head/sys/netgraph/ng_gif.c (revision 131155) @@ -1,598 +1,594 @@ /* * ng_gif.c * * Copyright 2001 The Aerospace Corporation. 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. * 3. The name of The Aerospace Corporation may not be used to endorse or * promote products derived from this software. * * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION ``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 AEROSPACE CORPORATION 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. * * * Copyright (c) 1996-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. * * $FreeBSD$ */ /* * ng_gif(4) netgraph node type */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IFP2NG(ifp) ((struct ng_node *)((struct gif_softc *)(ifp))->gif_netgraph) /* Per-node private data */ struct private { struct ifnet *ifp; /* associated interface */ hook_p lower; /* lower OR orphan hook connection */ u_char lowerOrphan; /* whether lower is lower or orphan */ }; typedef struct private *priv_p; /* Functional hooks called from if_gif.c */ static void ng_gif_input(struct ifnet *ifp, struct mbuf **mp, int af); static void ng_gif_input_orphan(struct ifnet *ifp, struct mbuf *m, int af); static void ng_gif_attach(struct ifnet *ifp); static void ng_gif_detach(struct ifnet *ifp); /* Other functions */ static void ng_gif_input2(node_p node, struct mbuf **mp, int af); static int ng_gif_glue_af(struct mbuf **mp, int af); -static int ng_gif_rcv_lower(node_p node, struct mbuf *m, meta_p meta); +static int ng_gif_rcv_lower(node_p node, struct mbuf *m); /* Netgraph node methods */ static ng_constructor_t ng_gif_constructor; static ng_rcvmsg_t ng_gif_rcvmsg; static ng_shutdown_t ng_gif_shutdown; static ng_newhook_t ng_gif_newhook; static ng_connect_t ng_gif_connect; static ng_rcvdata_t ng_gif_rcvdata; static ng_disconnect_t ng_gif_disconnect; static int ng_gif_mod_event(module_t mod, int event, void *data); /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_gif_cmdlist[] = { { NGM_GIF_COOKIE, NGM_GIF_GET_IFNAME, "getifname", NULL, &ng_parse_string_type }, { NGM_GIF_COOKIE, NGM_GIF_GET_IFINDEX, "getifindex", NULL, &ng_parse_int32_type }, { 0 } }; static struct ng_type ng_gif_typestruct = { .version = NG_ABI_VERSION, .name = NG_GIF_NODE_TYPE, .mod_event = ng_gif_mod_event, .constructor = ng_gif_constructor, .rcvmsg = ng_gif_rcvmsg, .shutdown = ng_gif_shutdown, .newhook = ng_gif_newhook, .connect = ng_gif_connect, .rcvdata = ng_gif_rcvdata, .disconnect = ng_gif_disconnect, .cmdlist = ng_gif_cmdlist, }; MODULE_VERSION(ng_gif, 1); MODULE_DEPEND(ng_gif, if_gif, 1,1,1); NETGRAPH_INIT(gif, &ng_gif_typestruct); /****************************************************************** GIF FUNCTION HOOKS ******************************************************************/ /* * Handle a packet that has come in on an interface. We get to * look at it here before any upper layer protocols do. * * NOTE: this function will get called at splimp() */ static void ng_gif_input(struct ifnet *ifp, struct mbuf **mp, int af) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); /* If "lower" hook not connected, let packet continue */ if (priv->lower == NULL || priv->lowerOrphan) return; ng_gif_input2(node, mp, af); } /* * Handle a packet that has come in on an interface, and which * does not match any of our known protocols (an ``orphan''). * * NOTE: this function will get called at splimp() */ static void ng_gif_input_orphan(struct ifnet *ifp, struct mbuf *m, int af) { const node_p node = IFP2NG(ifp); const priv_p priv = NG_NODE_PRIVATE(node); /* If "orphan" hook not connected, let packet continue */ if (priv->lower == NULL || !priv->lowerOrphan) { m_freem(m); return; } ng_gif_input2(node, &m, af); if (m != NULL) m_freem(m); } /* * Handle a packet that has come in on a gif interface. * Attach the address family to the mbuf for later use. * * NOTE: this function will get called at splimp() */ static void ng_gif_input2(node_p node, struct mbuf **mp, int af) { const priv_p priv = NG_NODE_PRIVATE(node); int error; /* Glue address family on */ if ((error = ng_gif_glue_af(mp, af)) != 0) return; /* Send out lower/orphan hook */ NG_SEND_DATA_ONLY(error, priv->lower, *mp); *mp = NULL; } /* * A new gif interface has been attached. * Create a new node for it, etc. */ static void ng_gif_attach(struct ifnet *ifp) { priv_p priv; node_p node; /* Create node */ KASSERT(!IFP2NG(ifp), ("%s: node already exists?", __func__)); if (ng_make_node_common(&ng_gif_typestruct, &node) != 0) { log(LOG_ERR, "%s: can't %s for %s\n", __func__, "create node", ifp->if_xname); return; } /* Allocate private data */ MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); if (priv == NULL) { log(LOG_ERR, "%s: can't %s for %s\n", __func__, "allocate memory", ifp->if_xname); NG_NODE_UNREF(node); return; } NG_NODE_SET_PRIVATE(node, priv); priv->ifp = ifp; IFP2NG(ifp) = node; /* Try to give the node the same name as the interface */ if (ng_name_node(node, ifp->if_xname) != 0) { log(LOG_WARNING, "%s: can't name node %s\n", __func__, ifp->if_xname); } } /* * An interface is being detached. * REALLY Destroy its node. */ static void ng_gif_detach(struct ifnet *ifp) { const node_p node = IFP2NG(ifp); priv_p priv; if (node == NULL) /* no node (why not?), ignore */ return; priv = NG_NODE_PRIVATE(node); NG_NODE_REALLY_DIE(node); /* Force real removal of node */ /* * We can't assume the ifnet is still around when we run shutdown * So zap it now. XXX We HOPE that anything running at this time * handles it (as it should in the non netgraph case). */ IFP2NG(ifp) = NULL; priv->ifp = NULL; /* XXX race if interrupted an output packet */ ng_rmnode_self(node); /* remove all netgraph parts */ } /* * Optimization for gluing the address family onto * the front of an incoming packet. */ static int ng_gif_glue_af(struct mbuf **mp, int af) { struct mbuf *m = *mp; int error = 0; sa_family_t tmp_af; tmp_af = (sa_family_t) af; /* * XXX: should try to bring back some of the optimizations from * ng_ether.c */ /* * Doing anything more is likely to get more * expensive than it's worth.. * it's probable that everything else is in one * big lump. The next node will do an m_pullup() * for exactly the amount of data it needs and * hopefully everything after that will not * need one. So let's just use M_PREPEND. */ M_PREPEND(m, sizeof (tmp_af), M_DONTWAIT); if (m == NULL) { error = ENOBUFS; goto done; } #if 0 copy: #endif /* Copy header and return (possibly new) mbuf */ *mtod(m, sa_family_t *) = tmp_af; #if 0 bcopy((caddr_t)&tmp_af, mtod(m, sa_family_t *), sizeof(tmp_af)); #endif done: *mp = m; return error; } /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * It is not possible or allowable to create a node of this type. * Nodes get created when the interface is attached (or, when * this node type's KLD is loaded). */ static int ng_gif_constructor(node_p node) { return (EINVAL); } /* * Check for attaching a new hook. */ static int ng_gif_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); u_char orphan = priv->lowerOrphan; hook_p *hookptr; /* Divert hook is an alias for lower */ if (strcmp(name, NG_GIF_HOOK_DIVERT) == 0) name = NG_GIF_HOOK_LOWER; /* Which hook? */ if (strcmp(name, NG_GIF_HOOK_LOWER) == 0) { hookptr = &priv->lower; orphan = 0; } else if (strcmp(name, NG_GIF_HOOK_ORPHAN) == 0) { hookptr = &priv->lower; orphan = 1; } else return (EINVAL); /* Check if already connected (shouldn't be, but doesn't hurt) */ if (*hookptr != NULL) return (EISCONN); /* OK */ *hookptr = hook; priv->lowerOrphan = orphan; return (0); } /* * Hooks are attached, adjust to force queueing. * We don't really care which hook it is. * they should all be queuing for outgoing data. */ static int ng_gif_connect(hook_p hook) { NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); return (0); } /* * Receive an incoming control message. */ static int ng_gif_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_GIF_COOKIE: switch (msg->header.cmd) { case NGM_GIF_GET_IFNAME: NG_MKRESPONSE(resp, msg, IFNAMSIZ + 1, M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } strlcpy(resp->data, priv->ifp->if_xname, IFNAMSIZ + 1); break; case NGM_GIF_GET_IFINDEX: NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } *((u_int32_t *)resp->data) = priv->ifp->if_index; break; default: error = EINVAL; break; } break; default: error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook. */ static int ng_gif_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; - meta_p meta; NGI_GET_M(item, m); - NGI_GET_META(item, meta); NG_FREE_ITEM(item); + if (hook == priv->lower) - return ng_gif_rcv_lower(node, m, meta); + return ng_gif_rcv_lower(node, m); panic("%s: weird hook", __func__); } /* * Handle an mbuf received on the "lower" hook. */ static int -ng_gif_rcv_lower(node_p node, struct mbuf *m, meta_p meta) +ng_gif_rcv_lower(node_p node, struct mbuf *m) { struct sockaddr dst; const priv_p priv = NG_NODE_PRIVATE(node); bzero(&dst, sizeof(dst)); - - /* We don't process metadata. */ - NG_FREE_META(meta); /* Make sure header is fully pulled up */ if (m->m_pkthdr.len < sizeof(sa_family_t)) { NG_FREE_M(m); return (EINVAL); } if (m->m_len < sizeof(sa_family_t) && (m = m_pullup(m, sizeof(sa_family_t))) == NULL) { return (ENOBUFS); } dst.sa_family = *mtod(m, sa_family_t *); m_adj(m, sizeof(sa_family_t)); /* Send it on its way */ /* * XXX: gif_output only uses dst for the family and passes the * fourth argument (rt) to in{,6}_gif_output which ignore it. * If this changes ng_gif will probably break. */ return gif_output(priv->ifp, m, &dst, NULL); } /* * Shutdown node. This resets the node but does not remove it * unless the REALLY_DIE flag is set. */ static int ng_gif_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); if (node->nd_flags & NG_REALLY_DIE) { /* * WE came here because the gif interface is being destroyed, * so stop being persistant. * Actually undo all the things we did on creation. * Assume the ifp has already been freed. */ NG_NODE_SET_PRIVATE(node, NULL); FREE(priv, M_NETGRAPH); NG_NODE_UNREF(node); /* free node itself */ return (0); } node->nd_flags &= ~NG_INVALID; /* Signal ng_rmnode we are persisant */ return (0); } /* * Hook disconnection. */ static int ng_gif_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); if (hook == priv->lower) { priv->lower = NULL; priv->lowerOrphan = 0; } else panic("%s: weird hook", __func__); if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); /* reset node */ return (0); } /****************************************************************** INITIALIZATION ******************************************************************/ /* * Handle loading and unloading for this node type. */ static int ng_gif_mod_event(module_t mod, int event, void *data) { struct ifnet *ifp; int error = 0; int s; s = splnet(); switch (event) { case MOD_LOAD: /* Register function hooks */ if (ng_gif_attach_p != NULL) { error = EEXIST; break; } ng_gif_attach_p = ng_gif_attach; ng_gif_detach_p = ng_gif_detach; ng_gif_input_p = ng_gif_input; ng_gif_input_orphan_p = ng_gif_input_orphan; /* Create nodes for any already-existing gif interfaces */ IFNET_RLOCK(); TAILQ_FOREACH(ifp, &ifnet, if_link) { if (ifp->if_type == IFT_GIF) ng_gif_attach(ifp); } IFNET_RUNLOCK(); break; case MOD_UNLOAD: /* * Note that the base code won't try to unload us until * all nodes have been removed, and that can't happen * until all gif interfaces are destroyed. In any * case, we know there are no nodes left if the action * is MOD_UNLOAD, so there's no need to detach any nodes. * * XXX: what about manual unloads?!? */ /* Unregister function hooks */ ng_gif_attach_p = NULL; ng_gif_detach_p = NULL; ng_gif_input_p = NULL; ng_gif_input_orphan_p = NULL; break; default: error = EOPNOTSUPP; break; } splx(s); return (error); } Index: head/sys/netgraph/ng_hub.c =================================================================== --- head/sys/netgraph/ng_hub.c (revision 131154) +++ head/sys/netgraph/ng_hub.c (revision 131155) @@ -1,110 +1,100 @@ /*- * Copyright (c) 2004 Ruslan Ermilov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include static ng_constructor_t ng_hub_constructor; static ng_rcvdata_t ng_hub_rcvdata; static ng_disconnect_t ng_hub_disconnect; static struct ng_type ng_hub_typestruct = { .version = NG_ABI_VERSION, .name = NG_HUB_NODE_TYPE, .constructor = ng_hub_constructor, .rcvdata = ng_hub_rcvdata, .disconnect = ng_hub_disconnect, }; NETGRAPH_INIT(hub, &ng_hub_typestruct); static int ng_hub_constructor(node_p node) { return (0); } static int ng_hub_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); int error = 0; hook_p hook2; struct mbuf * const m = NGI_M(item), *m2; - meta_p const meta = NGI_META(item); - meta_p meta2; int nhooks; if ((nhooks = NG_NODE_NUMHOOKS(node)) == 1) { NG_FREE_ITEM(item); return (0); } LIST_FOREACH(hook2, &node->nd_hooks, hk_hooks) { if (hook2 == hook) continue; if (--nhooks == 1) NG_FWD_ITEM_HOOK(error, item, hook2); else { if ((m2 = m_dup(m, M_DONTWAIT)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } - if (meta != NULL) { - if ((meta2 = ng_copy_meta(meta)) == NULL) { - m_freem(m2); - NG_FREE_ITEM(item); - return (ENOMEM); - } - } else - meta2 = NULL; - NG_SEND_DATA(error, hook2, m2, meta2); + NG_SEND_DATA_ONLY(error, hook2, m2); if (error) continue; /* don't give up */ } } return (error); } static int ng_hub_disconnect(hook_p hook) { 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); } Index: head/sys/netgraph/ng_iface.c =================================================================== --- head/sys/netgraph/ng_iface.c (revision 131154) +++ head/sys/netgraph/ng_iface.c (revision 131155) @@ -1,831 +1,830 @@ /* * ng_iface.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 * * $FreeBSD$ * $Whistle: ng_iface.c,v 1.33 1999/11/01 09:24:51 julian Exp $ */ /* * This node is also a system networking interface. It has * a hook for each protocol (IP, AppleTalk, IPX, etc). Packets * are simply relayed between the interface and the hooks. * * Interfaces are named ng0, ng1, etc. New nodes take the * first available interface name. * * This node also includes Berkeley packet filter support. */ #include "opt_atalk.h" #include "opt_inet.h" #include "opt_inet6.h" #include "opt_ipx.h" #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 MALLOC_DEFINE(M_NETGRAPH_IFACE, "netgraph_iface", "netgraph iface node "); #else #define M_NETGRAPH_IFACE M_NETGRAPH #endif /* This struct describes one address family */ struct iffam { sa_family_t family; /* Address family */ const char *hookname; /* Name for hook */ }; typedef const struct iffam *iffam_p; /* List of address families supported by our interface */ const static struct iffam gFamilies[] = { { AF_INET, NG_IFACE_HOOK_INET }, { AF_INET6, NG_IFACE_HOOK_INET6 }, { AF_APPLETALK, NG_IFACE_HOOK_ATALK }, { AF_IPX, NG_IFACE_HOOK_IPX }, { AF_ATM, NG_IFACE_HOOK_ATM }, { AF_NATM, NG_IFACE_HOOK_NATM }, }; #define NUM_FAMILIES (sizeof(gFamilies) / sizeof(*gFamilies)) /* Node private data */ struct ng_iface_private { struct ifnet *ifp; /* Our interface */ int unit; /* Interface unit number */ node_p node; /* Our netgraph node */ hook_p hooks[NUM_FAMILIES]; /* Hook for each address family */ }; typedef struct ng_iface_private *priv_p; /* Interface methods */ static void ng_iface_start(struct ifnet *ifp); static int ng_iface_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data); static int ng_iface_output(struct ifnet *ifp, struct mbuf *m0, struct sockaddr *dst, struct rtentry *rt0); static void ng_iface_bpftap(struct ifnet *ifp, struct mbuf *m, sa_family_t family); #ifdef DEBUG static void ng_iface_print_ioctl(struct ifnet *ifp, int cmd, caddr_t data); #endif /* Netgraph methods */ static ng_constructor_t ng_iface_constructor; static ng_rcvmsg_t ng_iface_rcvmsg; static ng_shutdown_t ng_iface_shutdown; static ng_newhook_t ng_iface_newhook; static ng_rcvdata_t ng_iface_rcvdata; static ng_disconnect_t ng_iface_disconnect; /* Helper stuff */ static iffam_p get_iffam_from_af(sa_family_t family); static iffam_p get_iffam_from_hook(priv_p priv, hook_p hook); static iffam_p get_iffam_from_name(const char *name); static hook_p *get_hook_from_iffam(priv_p priv, iffam_p iffam); /* Parse type for struct ng_iface_ifname */ static const struct ng_parse_fixedstring_info ng_iface_ifname_info = { NG_IFACE_IFACE_NAME_MAX + 1 }; static const struct ng_parse_type ng_iface_ifname_type = { &ng_parse_fixedstring_type, &ng_iface_ifname_info }; /* Parse type for struct ng_cisco_ipaddr */ static const struct ng_parse_struct_field ng_cisco_ipaddr_type_fields[] = NG_CISCO_IPADDR_TYPE_INFO; static const struct ng_parse_type ng_cisco_ipaddr_type = { &ng_parse_struct_type, &ng_cisco_ipaddr_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_iface_cmds[] = { { NGM_IFACE_COOKIE, NGM_IFACE_GET_IFNAME, "getifname", NULL, &ng_iface_ifname_type }, { NGM_IFACE_COOKIE, NGM_IFACE_POINT2POINT, "point2point", NULL, NULL }, { NGM_IFACE_COOKIE, NGM_IFACE_BROADCAST, "broadcast", NULL, NULL }, { NGM_CISCO_COOKIE, NGM_CISCO_GET_IPADDR, "getipaddr", NULL, &ng_cisco_ipaddr_type }, { NGM_IFACE_COOKIE, NGM_IFACE_GET_IFINDEX, "getifindex", NULL, &ng_parse_uint32_type }, { 0 } }; /* Node type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_IFACE_NODE_TYPE, .constructor = ng_iface_constructor, .rcvmsg = ng_iface_rcvmsg, .shutdown = ng_iface_shutdown, .newhook = ng_iface_newhook, .rcvdata = ng_iface_rcvdata, .disconnect = ng_iface_disconnect, .cmdlist = ng_iface_cmds, }; NETGRAPH_INIT(iface, &typestruct); /* We keep a bitmap indicating which unit numbers are free. One means the unit number is free, zero means it's taken. */ static int *ng_iface_units = NULL; static int ng_iface_units_len = 0; static int ng_units_in_use = 0; #define UNITS_BITSPERWORD (sizeof(*ng_iface_units) * NBBY) /************************************************************************ HELPER STUFF ************************************************************************/ /* * Get the family descriptor from the family ID */ static __inline__ iffam_p get_iffam_from_af(sa_family_t family) { iffam_p iffam; int k; for (k = 0; k < NUM_FAMILIES; k++) { iffam = &gFamilies[k]; if (iffam->family == family) return (iffam); } return (NULL); } /* * Get the family descriptor from the hook */ static __inline__ iffam_p get_iffam_from_hook(priv_p priv, hook_p hook) { int k; for (k = 0; k < NUM_FAMILIES; k++) if (priv->hooks[k] == hook) return (&gFamilies[k]); return (NULL); } /* * Get the hook from the iffam descriptor */ static __inline__ hook_p * get_hook_from_iffam(priv_p priv, iffam_p iffam) { return (&priv->hooks[iffam - gFamilies]); } /* * Get the iffam descriptor from the name */ static __inline__ iffam_p get_iffam_from_name(const char *name) { iffam_p iffam; int k; for (k = 0; k < NUM_FAMILIES; k++) { iffam = &gFamilies[k]; if (!strcmp(iffam->hookname, name)) return (iffam); } return (NULL); } /* * Find the first free unit number for a new interface. * Increase the size of the unit bitmap as necessary. */ static __inline__ int ng_iface_get_unit(int *unit) { int index, bit; for (index = 0; index < ng_iface_units_len && ng_iface_units[index] == 0; index++); if (index == ng_iface_units_len) { /* extend array */ int i, *newarray, newlen; newlen = (2 * ng_iface_units_len) + 4; MALLOC(newarray, int *, newlen * sizeof(*ng_iface_units), M_NETGRAPH_IFACE, M_NOWAIT); if (newarray == NULL) return (ENOMEM); bcopy(ng_iface_units, newarray, ng_iface_units_len * sizeof(*ng_iface_units)); for (i = ng_iface_units_len; i < newlen; i++) newarray[i] = ~0; if (ng_iface_units != NULL) FREE(ng_iface_units, M_NETGRAPH_IFACE); ng_iface_units = newarray; ng_iface_units_len = newlen; } bit = ffs(ng_iface_units[index]) - 1; KASSERT(bit >= 0 && bit <= UNITS_BITSPERWORD - 1, ("%s: word=%d bit=%d", __func__, ng_iface_units[index], bit)); ng_iface_units[index] &= ~(1 << bit); *unit = (index * UNITS_BITSPERWORD) + bit; ng_units_in_use++; return (0); } /* * Free a no longer needed unit number. */ static __inline__ void ng_iface_free_unit(int unit) { int index, bit; index = unit / UNITS_BITSPERWORD; bit = unit % UNITS_BITSPERWORD; KASSERT(index < ng_iface_units_len, ("%s: unit=%d len=%d", __func__, unit, ng_iface_units_len)); KASSERT((ng_iface_units[index] & (1 << bit)) == 0, ("%s: unit=%d is free", __func__, unit)); ng_iface_units[index] |= (1 << bit); /* * XXX We could think about reducing the size of ng_iface_units[] * XXX here if the last portion is all ones * XXX At least free it if no more units. * Needed if we are to eventually be able to unload. */ ng_units_in_use--; if (ng_units_in_use == 0) { /* XXX make SMP safe */ FREE(ng_iface_units, M_NETGRAPH_IFACE); ng_iface_units_len = 0; ng_iface_units = NULL; } } /************************************************************************ INTERFACE STUFF ************************************************************************/ /* * Process an ioctl for the virtual interface */ static int ng_iface_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct ifreq *const ifr = (struct ifreq *) data; int s, error = 0; #ifdef DEBUG ng_iface_print_ioctl(ifp, command, data); #endif s = splimp(); switch (command) { /* These two are mostly handled at a higher layer */ case SIOCSIFADDR: ifp->if_flags |= (IFF_UP | IFF_RUNNING); ifp->if_flags &= ~(IFF_OACTIVE); break; case SIOCGIFADDR: break; /* Set flags */ case SIOCSIFFLAGS: /* * If the interface is marked up and stopped, then start it. * If it is marked down and running, then stop it. */ if (ifr->ifr_flags & IFF_UP) { if (!(ifp->if_flags & IFF_RUNNING)) { ifp->if_flags &= ~(IFF_OACTIVE); ifp->if_flags |= IFF_RUNNING; } } else { if (ifp->if_flags & IFF_RUNNING) ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); } break; /* Set the interface MTU */ case SIOCSIFMTU: if (ifr->ifr_mtu > NG_IFACE_MTU_MAX || ifr->ifr_mtu < NG_IFACE_MTU_MIN) error = EINVAL; else ifp->if_mtu = ifr->ifr_mtu; break; /* Stuff that's not supported */ case SIOCADDMULTI: case SIOCDELMULTI: error = 0; break; case SIOCSIFPHYS: error = EOPNOTSUPP; break; default: error = EINVAL; break; } (void) splx(s); return (error); } /* * This routine is called to deliver a packet out the interface. * We simply look at the address family and relay the packet to * the corresponding hook, if it exists and is connected. */ static int ng_iface_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, struct rtentry *rt0) { const priv_p priv = (priv_p) ifp->if_softc; const iffam_p iffam = get_iffam_from_af(dst->sa_family); - meta_p meta = NULL; int len, error = 0; /* Check interface flags */ if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) { m_freem(m); return (ENETDOWN); } /* BPF writes need to be handled specially */ if (dst->sa_family == AF_UNSPEC) { if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) return (ENOBUFS); dst->sa_family = (sa_family_t)*mtod(m, int32_t *); m->m_data += 4; m->m_len -= 4; m->m_pkthdr.len -= 4; } /* Berkeley packet filter */ ng_iface_bpftap(ifp, m, dst->sa_family); /* Check address family to determine hook (if known) */ if (iffam == NULL) { m_freem(m); log(LOG_WARNING, "%s: can't handle af%d\n", ifp->if_xname, (int)dst->sa_family); return (EAFNOSUPPORT); } /* Copy length before the mbuf gets invalidated */ len = m->m_pkthdr.len; /* Send packet; if hook is not connected, mbuf will get freed. */ - NG_SEND_DATA(error, *get_hook_from_iffam(priv, iffam), m, meta); + NG_SEND_DATA_ONLY(error, *get_hook_from_iffam(priv, iffam), m); /* Update stats */ if (error == 0) { ifp->if_obytes += len; ifp->if_opackets++; } return (error); } /* * This routine should never be called */ static void ng_iface_start(struct ifnet *ifp) { if_printf(ifp, "%s called?", __func__); } /* * Flash a packet by the BPF (requires prepending 4 byte AF header) * Note the phoney mbuf; this is OK because BPF treats it read-only. */ static void ng_iface_bpftap(struct ifnet *ifp, struct mbuf *m, sa_family_t family) { KASSERT(family != AF_UNSPEC, ("%s: family=AF_UNSPEC", __func__)); if (ifp->if_bpf != NULL) { int32_t family4 = (int32_t)family; bpf_mtap2(ifp->if_bpf, &family4, sizeof(family4), m); } } #ifdef DEBUG /* * Display an ioctl to the virtual interface */ static void ng_iface_print_ioctl(struct ifnet *ifp, int command, caddr_t data) { char *str; switch (command & IOC_DIRMASK) { case IOC_VOID: str = "IO"; break; case IOC_OUT: str = "IOR"; break; case IOC_IN: str = "IOW"; break; case IOC_INOUT: str = "IORW"; break; default: str = "IO??"; } log(LOG_DEBUG, "%s: %s('%c', %d, char[%d])\n", ifp->if_xname, str, IOCGROUP(command), command & 0xff, IOCPARM_LEN(command)); } #endif /* DEBUG */ /************************************************************************ NETGRAPH NODE STUFF ************************************************************************/ /* * Constructor for a node */ static int ng_iface_constructor(node_p node) { char ifname[NG_IFACE_IFACE_NAME_MAX + 1]; struct ifnet *ifp; priv_p priv; int error = 0; /* Allocate node and interface private structures */ MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH_IFACE, M_NOWAIT|M_ZERO); if (priv == NULL) return (ENOMEM); MALLOC(ifp, struct ifnet *, sizeof(*ifp), M_NETGRAPH_IFACE, M_NOWAIT|M_ZERO); if (ifp == NULL) { FREE(priv, M_NETGRAPH_IFACE); return (ENOMEM); } /* Link them together */ ifp->if_softc = priv; priv->ifp = ifp; /* Get an interface unit number */ if ((error = ng_iface_get_unit(&priv->unit)) != 0) { FREE(ifp, M_NETGRAPH_IFACE); FREE(priv, M_NETGRAPH_IFACE); return (error); } /* Link together node and private info */ NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* Initialize interface structure */ if_initname(ifp, NG_IFACE_IFACE_NAME, priv->unit); ifp->if_output = ng_iface_output; ifp->if_start = ng_iface_start; ifp->if_ioctl = ng_iface_ioctl; ifp->if_watchdog = NULL; ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; ifp->if_mtu = NG_IFACE_MTU_DEFAULT; ifp->if_flags = (IFF_SIMPLEX|IFF_POINTOPOINT|IFF_NOARP|IFF_MULTICAST); ifp->if_type = IFT_PROPVIRTUAL; /* XXX */ ifp->if_addrlen = 0; /* XXX */ ifp->if_hdrlen = 0; /* XXX */ ifp->if_baudrate = 64000; /* XXX */ TAILQ_INIT(&ifp->if_addrhead); /* Give this node the same name as the interface (if possible) */ bzero(ifname, sizeof(ifname)); strlcpy(ifname, ifp->if_xname, sizeof(ifname)); if (ng_name_node(node, ifname) != 0) log(LOG_WARNING, "%s: can't acquire netgraph name\n", ifname); /* Attach the interface */ if_attach(ifp); bpfattach(ifp, DLT_NULL, sizeof(u_int)); /* Done */ return (0); } /* * Give our ok for a hook to be added */ static int ng_iface_newhook(node_p node, hook_p hook, const char *name) { const iffam_p iffam = get_iffam_from_name(name); hook_p *hookptr; if (iffam == NULL) return (EPFNOSUPPORT); hookptr = get_hook_from_iffam(NG_NODE_PRIVATE(node), iffam); if (*hookptr != NULL) return (EISCONN); *hookptr = hook; return (0); } /* * Receive a control message */ static int ng_iface_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ifnet *const ifp = priv->ifp; struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_IFACE_COOKIE: switch (msg->header.cmd) { case NGM_IFACE_GET_IFNAME: { struct ng_iface_ifname *arg; NG_MKRESPONSE(resp, msg, sizeof(*arg), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } arg = (struct ng_iface_ifname *)resp->data; strlcpy(arg->ngif_name, ifp->if_xname, sizeof(arg->ngif_name)); break; } case NGM_IFACE_POINT2POINT: case NGM_IFACE_BROADCAST: { /* Deny request if interface is UP */ if ((ifp->if_flags & IFF_UP) != 0) return (EBUSY); /* Change flags */ switch (msg->header.cmd) { case NGM_IFACE_POINT2POINT: ifp->if_flags |= IFF_POINTOPOINT; ifp->if_flags &= ~IFF_BROADCAST; break; case NGM_IFACE_BROADCAST: ifp->if_flags &= ~IFF_POINTOPOINT; ifp->if_flags |= IFF_BROADCAST; break; } break; } case NGM_IFACE_GET_IFINDEX: NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } *((uint32_t *)resp->data) = priv->ifp->if_index; break; default: error = EINVAL; break; } break; case NGM_CISCO_COOKIE: switch (msg->header.cmd) { case NGM_CISCO_GET_IPADDR: /* we understand this too */ { struct ifaddr *ifa; /* Return the first configured IP address */ TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { struct ng_cisco_ipaddr *ips; if (ifa->ifa_addr->sa_family != AF_INET) continue; NG_MKRESPONSE(resp, msg, sizeof(ips), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } ips = (struct ng_cisco_ipaddr *)resp->data; ips->ipaddr = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr; ips->netmask = ((struct sockaddr_in *) ifa->ifa_netmask)->sin_addr; break; } /* No IP addresses on this interface? */ if (ifa == NULL) error = EADDRNOTAVAIL; break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Recive data from a hook. Pass the packet to the correct input routine. */ static int ng_iface_rcvdata(hook_p hook, item_p item) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); const iffam_p iffam = get_iffam_from_hook(priv, hook); struct ifnet *const ifp = priv->ifp; struct mbuf *m; int isr; NGI_GET_M(item, m); NG_FREE_ITEM(item); /* Sanity checks */ KASSERT(iffam != NULL, ("%s: iffam", __func__)); M_ASSERTPKTHDR(m); if ((ifp->if_flags & IFF_UP) == 0) { NG_FREE_M(m); return (ENETDOWN); } /* Update interface stats */ ifp->if_ipackets++; ifp->if_ibytes += m->m_pkthdr.len; /* Note receiving interface */ m->m_pkthdr.rcvif = ifp; /* Berkeley packet filter */ ng_iface_bpftap(ifp, m, iffam->family); /* Send packet */ switch (iffam->family) { #ifdef INET case AF_INET: isr = NETISR_IP; break; #endif #ifdef INET6 case AF_INET6: isr = NETISR_IPV6; break; #endif #ifdef IPX case AF_IPX: isr = NETISR_IPX; break; #endif #ifdef NETATALK case AF_APPLETALK: isr = NETISR_ATALK2; break; #endif default: m_freem(m); return (EAFNOSUPPORT); } /* First chunk of an mbuf contains good junk */ if (harvest.point_to_point) random_harvest(m, 16, 3, 0, RANDOM_NET); netisr_dispatch(isr, m); return (0); } /* * Shutdown and remove the node and its associated interface. */ static int ng_iface_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); bpfdetach(priv->ifp); if_detach(priv->ifp); FREE(priv->ifp, M_NETGRAPH_IFACE); priv->ifp = NULL; ng_iface_free_unit(priv->unit); FREE(priv, M_NETGRAPH_IFACE); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); return (0); } /* * Hook disconnection. Note that we do *not* shutdown when all * hooks have been disconnected. */ static int ng_iface_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); const iffam_p iffam = get_iffam_from_hook(priv, hook); if (iffam == NULL) panic(__func__); *get_hook_from_iffam(priv, iffam) = NULL; return (0); } Index: head/sys/netgraph/ng_l2tp.c =================================================================== --- head/sys/netgraph/ng_l2tp.c (revision 131154) +++ head/sys/netgraph/ng_l2tp.c (revision 131155) @@ -1,1476 +1,1475 @@ /* * Copyright (c) 2001-2002 Packet Design, LLC. * 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 * Packet Design; provided, however, that: * * (i) Any and all reproductions of the source or object code * must include the copyright notice above and the following * disclaimer of warranties; and * (ii) No rights are granted, in any manner or form, to use * Packet Design trademarks, including the mark "PACKET DESIGN" * on advertising, endorsements, or otherwise except as such * appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN 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. PACKET DESIGN 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 PACKET DESIGN 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 PACKET DESIGN IS ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. * * Author: Archie Cobbs * * $FreeBSD$ */ /* * L2TP netgraph node type. * * This node type implements the lower layer of the * L2TP protocol as specified in RFC 2661. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_L2TP, "netgraph_l2tp", "netgraph l2tp node"); #else #define M_NETGRAPH_L2TP M_NETGRAPH #endif /* L2TP header format (first 2 bytes only) */ #define L2TP_HDR_CTRL 0x8000 /* control packet */ #define L2TP_HDR_LEN 0x4000 /* has length field */ #define L2TP_HDR_SEQ 0x0800 /* has ns, nr fields */ #define L2TP_HDR_OFF 0x0200 /* has offset field */ #define L2TP_HDR_PRIO 0x0100 /* give priority */ #define L2TP_HDR_VERS_MASK 0x000f /* version field mask */ #define L2TP_HDR_VERSION 0x0002 /* version field */ /* Bits that must be zero or one in first two bytes of header */ #define L2TP_CTRL_0BITS 0x030d /* ctrl: must be 0 */ #define L2TP_CTRL_1BITS 0xc802 /* ctrl: must be 1 */ #define L2TP_DATA_0BITS 0x800d /* data: must be 0 */ #define L2TP_DATA_1BITS 0x0002 /* data: must be 1 */ /* Standard xmit ctrl and data header bits */ #define L2TP_CTRL_HDR (L2TP_HDR_CTRL | L2TP_HDR_LEN \ | L2TP_HDR_SEQ | L2TP_HDR_VERSION) #define L2TP_DATA_HDR (L2TP_HDR_VERSION) /* optional: len, seq */ /* Some hard coded values */ #define L2TP_MAX_XWIN 16 /* my max xmit window */ #define L2TP_MAX_REXMIT 5 /* default max rexmit */ #define L2TP_MAX_REXMIT_TO 30 /* default rexmit to */ #define L2TP_DELAYED_ACK ((hz + 19) / 20) /* delayed ack: 50 ms */ /* Default data sequence number configuration for new sessions */ #define L2TP_CONTROL_DSEQ 1 /* we are the lns */ #define L2TP_ENABLE_DSEQ 1 /* enable data seq # */ /* Compare sequence numbers using circular math */ #define L2TP_SEQ_DIFF(x, y) ((int)((int16_t)(x) - (int16_t)(y))) /* * Sequence number state * * Invariants: * - If cwnd < ssth, we're doing slow start, otherwise congestion avoidance * - The number of unacknowledged xmit packets is (ns - rack) <= seq->wmax * - The first (ns - rack) mbuf's in xwin[] array are copies of these * unacknowledged packets; the remainder of xwin[] consists first of * zero or more further untransmitted packets in the transmit queue * - We try to keep the peer's receive window as full as possible. * Therefore, (i < cwnd && xwin[i] != NULL) implies (ns - rack) > i. * - rack_timer is running iff (ns - rack) > 0 (unack'd xmit'd pkts) * - If xack != nr, there are unacknowledged recv packet(s) (delayed ack) * - xack_timer is running iff xack != nr (unack'd rec'd pkts) */ struct l2tp_seq { u_int16_t ns; /* next xmit seq we send */ u_int16_t nr; /* next recv seq we expect */ u_int16_t rack; /* last 'nr' we rec'd */ u_int16_t xack; /* last 'nr' we sent */ u_int16_t wmax; /* peer's max recv window */ u_int16_t cwnd; /* current congestion window */ u_int16_t ssth; /* slow start threshold */ u_int16_t acks; /* # consecutive acks rec'd */ u_int16_t rexmits; /* # retransmits sent */ u_int16_t max_rexmits; /* max # retransmits sent */ u_int16_t max_rexmit_to; /* max retransmit timeout */ struct callout rack_timer; /* retransmit timer */ struct callout xack_timer; /* delayed ack timer */ u_char rack_timer_running; /* xmit timer running */ u_char xack_timer_running; /* ack timer running */ struct mbuf *xwin[L2TP_MAX_XWIN]; /* transmit window */ }; /* Node private data */ struct ng_l2tp_private { node_p node; /* back pointer to node */ hook_p ctrl; /* hook to upper layers */ hook_p lower; /* hook to lower layers */ struct ng_l2tp_config conf; /* node configuration */ struct ng_l2tp_stats stats; /* node statistics */ struct l2tp_seq seq; /* ctrl sequence number state */ ng_ID_t ftarget; /* failure message target */ }; typedef struct ng_l2tp_private *priv_p; /* Hook private data (data session hooks only) */ struct ng_l2tp_hook_private { struct ng_l2tp_sess_config conf; /* hook/session config */ u_int16_t ns; /* data ns sequence number */ u_int16_t nr; /* data nr sequence number */ }; typedef struct ng_l2tp_hook_private *hookpriv_p; /* Netgraph node methods */ static ng_constructor_t ng_l2tp_constructor; static ng_rcvmsg_t ng_l2tp_rcvmsg; static ng_shutdown_t ng_l2tp_shutdown; static ng_newhook_t ng_l2tp_newhook; static ng_rcvdata_t ng_l2tp_rcvdata; static ng_disconnect_t ng_l2tp_disconnect; /* Internal functions */ static int ng_l2tp_recv_lower(node_p node, item_p item); static int ng_l2tp_recv_ctrl(node_p node, item_p item); static int ng_l2tp_recv_data(node_p node, item_p item, hookpriv_p hpriv); static int ng_l2tp_xmit_ctrl(priv_p priv, struct mbuf *m, u_int16_t ns); static void ng_l2tp_seq_init(priv_p priv); static int ng_l2tp_seq_adjust(priv_p priv, const struct ng_l2tp_config *conf); static void ng_l2tp_seq_reset(priv_p priv); static void ng_l2tp_seq_failure(priv_p priv); static void ng_l2tp_seq_recv_nr(priv_p priv, u_int16_t nr); static int ng_l2tp_seq_recv_ns(priv_p priv, u_int16_t ns); static void ng_l2tp_seq_xack_timeout(void *arg); static void ng_l2tp_seq_rack_timeout(void *arg); static ng_fn_eachhook ng_l2tp_find_session; static ng_fn_eachhook ng_l2tp_reset_session; #ifdef INVARIANTS static void ng_l2tp_seq_check(struct l2tp_seq *seq); #endif /* Parse type for struct ng_l2tp_config */ static const struct ng_parse_struct_field ng_l2tp_config_type_fields[] = NG_L2TP_CONFIG_TYPE_INFO; static const struct ng_parse_type ng_l2tp_config_type = { &ng_parse_struct_type, &ng_l2tp_config_type_fields, }; /* Parse type for struct ng_l2tp_sess_config */ static const struct ng_parse_struct_field ng_l2tp_sess_config_type_fields[] = NG_L2TP_SESS_CONFIG_TYPE_INFO; static const struct ng_parse_type ng_l2tp_sess_config_type = { &ng_parse_struct_type, &ng_l2tp_sess_config_type_fields, }; /* Parse type for struct ng_l2tp_stats */ static const struct ng_parse_struct_field ng_l2tp_stats_type_fields[] = NG_L2TP_STATS_TYPE_INFO; static const struct ng_parse_type ng_l2tp_stats_type = { &ng_parse_struct_type, &ng_l2tp_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_l2tp_cmdlist[] = { { NGM_L2TP_COOKIE, NGM_L2TP_SET_CONFIG, "setconfig", &ng_l2tp_config_type, NULL }, { NGM_L2TP_COOKIE, NGM_L2TP_GET_CONFIG, "getconfig", NULL, &ng_l2tp_config_type }, { NGM_L2TP_COOKIE, NGM_L2TP_SET_SESS_CONFIG, "setsessconfig", &ng_l2tp_sess_config_type, NULL }, { NGM_L2TP_COOKIE, NGM_L2TP_GET_SESS_CONFIG, "getsessconfig", &ng_parse_hint16_type, &ng_l2tp_sess_config_type }, { NGM_L2TP_COOKIE, NGM_L2TP_GET_STATS, "getstats", NULL, &ng_l2tp_stats_type }, { NGM_L2TP_COOKIE, NGM_L2TP_CLR_STATS, "clrstats", NULL, NULL }, { NGM_L2TP_COOKIE, NGM_L2TP_GETCLR_STATS, "getclrstats", NULL, &ng_l2tp_stats_type }, { NGM_L2TP_COOKIE, NGM_L2TP_ACK_FAILURE, "ackfailure", NULL, NULL }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_l2tp_typestruct = { .version = NG_ABI_VERSION, .name = NG_L2TP_NODE_TYPE, .constructor = ng_l2tp_constructor, .rcvmsg = ng_l2tp_rcvmsg, .shutdown = ng_l2tp_shutdown, .newhook = ng_l2tp_newhook, .rcvdata = ng_l2tp_rcvdata, .disconnect = ng_l2tp_disconnect, .cmdlist = ng_l2tp_cmdlist, }; NETGRAPH_INIT(l2tp, &ng_l2tp_typestruct); /* Sequence number state sanity checking */ #ifdef INVARIANTS #define L2TP_SEQ_CHECK(seq) ng_l2tp_seq_check(seq) #else #define L2TP_SEQ_CHECK(x) do { } while (0) #endif /* memmove macro */ #define memmove(d, s, l) bcopy(s, d, l) /* Whether to use m_copypacket() or m_dup() */ #define L2TP_COPY_MBUF m_copypacket /************************************************************************ NETGRAPH NODE STUFF ************************************************************************/ /* * Node type constructor */ static int ng_l2tp_constructor(node_p node) { priv_p priv; /* Allocate private structure */ MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH_L2TP, M_NOWAIT | M_ZERO); if (priv == NULL) return (ENOMEM); NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* Apply a semi-reasonable default configuration */ priv->conf.peer_win = 1; priv->conf.rexmit_max = L2TP_MAX_REXMIT; priv->conf.rexmit_max_to = L2TP_MAX_REXMIT_TO; /* Initialize sequence number state */ ng_l2tp_seq_init(priv); /* Done */ return (0); } /* * Give our OK for a hook to be added. */ static int ng_l2tp_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_L2TP_HOOK_CTRL) == 0) { if (priv->ctrl != NULL) return (EISCONN); priv->ctrl = hook; } else if (strcmp(name, NG_L2TP_HOOK_LOWER) == 0) { if (priv->lower != NULL) return (EISCONN); priv->lower = hook; } else { static const char hexdig[16] = "0123456789abcdef"; u_int16_t session_id; hookpriv_p hpriv; const char *hex; int i; int j; /* Parse hook name to get session ID */ if (strncmp(name, NG_L2TP_HOOK_SESSION_P, sizeof(NG_L2TP_HOOK_SESSION_P) - 1) != 0) return (EINVAL); hex = name + sizeof(NG_L2TP_HOOK_SESSION_P) - 1; for (session_id = i = 0; i < 4; i++) { for (j = 0; j < 16 && hex[i] != hexdig[j]; j++); if (j == 16) return (EINVAL); session_id = (session_id << 4) | j; } if (hex[i] != '\0') return (EINVAL); /* Create hook private structure */ MALLOC(hpriv, hookpriv_p, sizeof(*hpriv), M_NETGRAPH_L2TP, M_NOWAIT | M_ZERO); if (hpriv == NULL) return (ENOMEM); hpriv->conf.session_id = htons(session_id); hpriv->conf.control_dseq = L2TP_CONTROL_DSEQ; hpriv->conf.enable_dseq = L2TP_ENABLE_DSEQ; NG_HOOK_SET_PRIVATE(hook, hpriv); } /* Done */ return (0); } /* * Receive a control message. */ static int ng_l2tp_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_L2TP_COOKIE: switch (msg->header.cmd) { case NGM_L2TP_SET_CONFIG: { struct ng_l2tp_config *const conf = (struct ng_l2tp_config *)msg->data; /* Check for invalid or illegal config */ if (msg->header.arglen != sizeof(*conf)) { error = EINVAL; break; } conf->enabled = !!conf->enabled; conf->match_id = !!conf->match_id; conf->tunnel_id = htons(conf->tunnel_id); conf->peer_id = htons(conf->peer_id); if (priv->conf.enabled && ((priv->conf.tunnel_id != 0 && conf->tunnel_id != priv->conf.tunnel_id) || ((priv->conf.peer_id != 0 && conf->peer_id != priv->conf.peer_id)))) { error = EBUSY; break; } /* Save calling node as failure target */ priv->ftarget = NGI_RETADDR(item); /* Adjust sequence number state */ if ((error = ng_l2tp_seq_adjust(priv, conf)) != 0) break; /* Update node's config */ priv->conf = *conf; break; } case NGM_L2TP_GET_CONFIG: { struct ng_l2tp_config *conf; NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_l2tp_config *)resp->data; *conf = priv->conf; /* Put ID's in host order */ conf->tunnel_id = ntohs(conf->tunnel_id); conf->peer_id = ntohs(conf->peer_id); break; } case NGM_L2TP_SET_SESS_CONFIG: { struct ng_l2tp_sess_config *const conf = (struct ng_l2tp_sess_config *)msg->data; hookpriv_p hpriv; hook_p hook; /* Check for invalid or illegal config */ if (msg->header.arglen != sizeof(*conf)) { error = EINVAL; break; } /* Put ID's in network order */ conf->session_id = htons(conf->session_id); conf->peer_id = htons(conf->peer_id); /* Find matching hook */ NG_NODE_FOREACH_HOOK(node, ng_l2tp_find_session, (void *)(uintptr_t)conf->session_id, hook); if (hook == NULL) { error = ENOENT; break; } hpriv = NG_HOOK_PRIVATE(hook); /* Update hook's config */ hpriv->conf = *conf; break; } case NGM_L2TP_GET_SESS_CONFIG: { struct ng_l2tp_sess_config *conf; u_int16_t session_id; hookpriv_p hpriv; hook_p hook; /* Get session ID */ if (msg->header.arglen != sizeof(session_id)) { error = EINVAL; break; } memcpy(&session_id, msg->data, 2); session_id = htons(session_id); /* Find matching hook */ NG_NODE_FOREACH_HOOK(node, ng_l2tp_find_session, (void *)(uintptr_t)session_id, hook); if (hook == NULL) { error = ENOENT; break; } hpriv = NG_HOOK_PRIVATE(hook); /* Send response */ NG_MKRESPONSE(resp, msg, sizeof(hpriv->conf), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_l2tp_sess_config *)resp->data; *conf = hpriv->conf; /* Put ID's in host order */ conf->session_id = ntohs(conf->session_id); conf->peer_id = ntohs(conf->peer_id); break; } case NGM_L2TP_GET_STATS: case NGM_L2TP_CLR_STATS: case NGM_L2TP_GETCLR_STATS: { if (msg->header.cmd != NGM_L2TP_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(priv->stats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } memcpy(resp->data, &priv->stats, sizeof(priv->stats)); } if (msg->header.cmd != NGM_L2TP_GET_STATS) memset(&priv->stats, 0, 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_l2tp_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); int error; /* Sanity check */ L2TP_SEQ_CHECK(&priv->seq); /* If not configured, reject */ if (!priv->conf.enabled) { NG_FREE_ITEM(item); return (ENXIO); } /* Handle incoming frame from below */ if (hook == priv->lower) { error = ng_l2tp_recv_lower(node, item); goto done; } /* Handle outgoing control frame */ if (hook == priv->ctrl) { error = ng_l2tp_recv_ctrl(node, item); goto done; } /* Handle outgoing data frame */ error = ng_l2tp_recv_data(node, item, NG_HOOK_PRIVATE(hook)); done: /* Done */ L2TP_SEQ_CHECK(&priv->seq); return (error); } /* * Destroy node */ static int ng_l2tp_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); struct l2tp_seq *const seq = &priv->seq; /* Sanity check */ L2TP_SEQ_CHECK(seq); /* Reset sequence number state */ ng_l2tp_seq_reset(priv); /* Free private data if neither timer is running */ if (!seq->rack_timer_running && !seq->xack_timer_running) { FREE(priv, M_NETGRAPH_L2TP); NG_NODE_SET_PRIVATE(node, NULL); } /* Unref node */ NG_NODE_UNREF(node); return (0); } /* * Hook disconnection */ static int ng_l2tp_disconnect(hook_p hook) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); /* Zero out hook pointer */ if (hook == priv->ctrl) priv->ctrl = NULL; else if (hook == priv->lower) priv->lower = NULL; else { FREE(NG_HOOK_PRIVATE(hook), M_NETGRAPH_L2TP); NG_HOOK_SET_PRIVATE(hook, NULL); } /* 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); } /************************************************************************* INTERNAL FUNCTIONS *************************************************************************/ /* * Find the hook with a given session ID. */ static int ng_l2tp_find_session(hook_p hook, void *arg) { const hookpriv_p hpriv = NG_HOOK_PRIVATE(hook); const u_int16_t sid = (u_int16_t)(uintptr_t)arg; if (hpriv == NULL || hpriv->conf.session_id != sid) return (-1); return (0); } /* * Reset a hook's session state. */ static int ng_l2tp_reset_session(hook_p hook, void *arg) { const hookpriv_p hpriv = NG_HOOK_PRIVATE(hook); if (hpriv != NULL) { hpriv->conf.control_dseq = 0; hpriv->conf.enable_dseq = 0; hpriv->nr = 0; hpriv->ns = 0; } return (-1); } /* * Handle an incoming frame from below. */ static int ng_l2tp_recv_lower(node_p node, item_p item) { static const u_int16_t req_bits[2][2] = { { L2TP_DATA_0BITS, L2TP_DATA_1BITS }, { L2TP_CTRL_0BITS, L2TP_CTRL_1BITS }, }; const priv_p priv = NG_NODE_PRIVATE(node); hookpriv_p hpriv = NULL; hook_p hook = NULL; u_int16_t ids[2]; struct mbuf *m; u_int16_t hdr; u_int16_t ns; u_int16_t nr; int is_ctrl; int error; int len; /* Grab mbuf */ NGI_GET_M(item, m); /* Update stats */ priv->stats.recvPackets++; priv->stats.recvOctets += m->m_pkthdr.len; /* Get initial header */ if (m->m_pkthdr.len < 6) { priv->stats.recvRunts++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EINVAL); } if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); return (EINVAL); } hdr = ntohs(*mtod(m, u_int16_t *)); m_adj(m, 2); /* Check required header bits and minimum length */ is_ctrl = (hdr & L2TP_HDR_CTRL) != 0; if ((hdr & req_bits[is_ctrl][0]) != 0 || (~hdr & req_bits[is_ctrl][1]) != 0) { priv->stats.recvInvalid++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EINVAL); } if (m->m_pkthdr.len < 4 /* tunnel, session id */ + (2 * ((hdr & L2TP_HDR_LEN) != 0)) /* length field */ + (4 * ((hdr & L2TP_HDR_SEQ) != 0)) /* seq # fields */ + (2 * ((hdr & L2TP_HDR_OFF) != 0))) { /* offset field */ priv->stats.recvRunts++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EINVAL); } /* Get and validate length field if present */ if ((hdr & L2TP_HDR_LEN) != 0) { if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); return (EINVAL); } len = (u_int16_t)ntohs(*mtod(m, u_int16_t *)) - 4; m_adj(m, 2); if (len < 0 || len > m->m_pkthdr.len) { priv->stats.recvInvalid++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EINVAL); } if (len < m->m_pkthdr.len) /* trim extra bytes */ m_adj(m, -(m->m_pkthdr.len - len)); } /* Get tunnel ID and session ID */ if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); return (EINVAL); } memcpy(ids, mtod(m, u_int16_t *), 4); m_adj(m, 4); /* Check tunnel ID */ if (ids[0] != priv->conf.tunnel_id && (priv->conf.match_id || ids[0] != 0)) { priv->stats.recvWrongTunnel++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EADDRNOTAVAIL); } /* Check session ID (for data packets only) */ if ((hdr & L2TP_HDR_CTRL) == 0) { NG_NODE_FOREACH_HOOK(node, ng_l2tp_find_session, (void *)(uintptr_t)ids[1], hook); if (hook == NULL) { priv->stats.recvUnknownSID++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ENOTCONN); } hpriv = NG_HOOK_PRIVATE(hook); } /* Get Ns, Nr fields if present */ if ((hdr & L2TP_HDR_SEQ) != 0) { if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); return (EINVAL); } memcpy(&ns, &mtod(m, u_int16_t *)[0], 2); ns = ntohs(ns); memcpy(&nr, &mtod(m, u_int16_t *)[1], 2); nr = ntohs(nr); m_adj(m, 4); } /* Strip offset padding if present */ if ((hdr & L2TP_HDR_OFF) != 0) { u_int16_t offset; /* Get length of offset padding */ if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); return (EINVAL); } memcpy(&offset, mtod(m, u_int16_t *), 2); offset = ntohs(offset); /* Trim offset padding */ if (offset <= 2 || offset > m->m_pkthdr.len) { priv->stats.recvInvalid++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EINVAL); } m_adj(m, offset); } /* Handle control packets */ if ((hdr & L2TP_HDR_CTRL) != 0) { /* Handle receive ack sequence number Nr */ ng_l2tp_seq_recv_nr(priv, nr); /* Discard ZLB packets */ if (m->m_pkthdr.len == 0) { priv->stats.recvZLBs++; NG_FREE_ITEM(item); NG_FREE_M(m); return (0); } /* * Prepend session ID to packet here: we don't want to accept * the send sequence number Ns if we have to drop the packet * later because of a memory error, because then the upper * layer would never get the packet. */ M_PREPEND(m, 2, M_DONTWAIT); if (m == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); return (ENOBUFS); } memcpy(mtod(m, u_int16_t *), &ids[1], 2); /* Now handle send sequence number */ if (ng_l2tp_seq_recv_ns(priv, ns) == -1) { NG_FREE_ITEM(item); NG_FREE_M(m); return (0); } /* Deliver packet to upper layers */ NG_FWD_NEW_DATA(error, item, priv->ctrl, m); return (error); } /* Follow peer's lead in data sequencing, if configured to do so */ if (!hpriv->conf.control_dseq) hpriv->conf.enable_dseq = ((hdr & L2TP_HDR_SEQ) != 0); /* Handle data sequence numbers if present and enabled */ if ((hdr & L2TP_HDR_SEQ) != 0) { if (hpriv->conf.enable_dseq && L2TP_SEQ_DIFF(ns, hpriv->nr) < 0) { NG_FREE_ITEM(item); /* duplicate or out of order */ NG_FREE_M(m); priv->stats.recvDataDrops++; return (0); } hpriv->nr = ns + 1; } /* Drop empty data packets */ if (m->m_pkthdr.len == 0) { NG_FREE_ITEM(item); NG_FREE_M(m); return (0); } /* Deliver data */ NG_FWD_NEW_DATA(error, item, hook, m); return (error); } /* * Handle an outgoing control frame. */ static int ng_l2tp_recv_ctrl(node_p node, item_p item) { const priv_p priv = NG_NODE_PRIVATE(node); struct l2tp_seq *const seq = &priv->seq; struct mbuf *m; int i; /* Grab mbuf and discard other stuff XXX */ NGI_GET_M(item, m); NG_FREE_ITEM(item); /* Packet should have session ID prepended */ if (m->m_pkthdr.len < 2) { priv->stats.xmitInvalid++; m_freem(m); return (EINVAL); } /* Check max length */ if (m->m_pkthdr.len >= 0x10000 - 14) { priv->stats.xmitTooBig++; m_freem(m); return (EOVERFLOW); } /* Find next empty slot in transmit queue */ for (i = 0; i < L2TP_MAX_XWIN && seq->xwin[i] != NULL; i++); if (i == L2TP_MAX_XWIN) { priv->stats.xmitDrops++; m_freem(m); return (ENOBUFS); } seq->xwin[i] = m; /* Sanity check receive ack timer state */ KASSERT((i == 0) ^ seq->rack_timer_running, ("%s: xwin %d full but rack timer %srunning", __FUNCTION__, i, seq->rack_timer_running ? "" : "not ")); /* If peer's receive window is already full, nothing else to do */ if (i >= seq->cwnd) return (0); /* Start retransmit timer if not already running */ if (!seq->rack_timer_running) { callout_reset(&seq->rack_timer, hz, ng_l2tp_seq_rack_timeout, node); seq->rack_timer_running = 1; NG_NODE_REF(node); } /* Copy packet */ if ((m = L2TP_COPY_MBUF(seq->xwin[i], M_DONTWAIT)) == NULL) { priv->stats.memoryFailures++; return (ENOBUFS); } /* Send packet and increment xmit sequence number */ return (ng_l2tp_xmit_ctrl(priv, m, seq->ns++)); } /* * Handle an outgoing data frame. */ static int ng_l2tp_recv_data(node_p node, item_p item, hookpriv_p hpriv) { const priv_p priv = NG_NODE_PRIVATE(node); struct mbuf *m; u_int16_t hdr; int error; int i = 1; /* Get mbuf */ NGI_GET_M(item, m); /* Check max length */ if (m->m_pkthdr.len >= 0x10000 - 12) { priv->stats.xmitDataTooBig++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EOVERFLOW); } /* Prepend L2TP header */ M_PREPEND(m, 6 + (2 * (hpriv->conf.include_length != 0)) + (4 * (hpriv->conf.enable_dseq != 0)), M_DONTWAIT); if (m == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); return (ENOBUFS); } hdr = L2TP_DATA_HDR; if (hpriv->conf.include_length) { hdr |= L2TP_HDR_LEN; mtod(m, u_int16_t *)[i++] = htons(m->m_pkthdr.len); } mtod(m, u_int16_t *)[i++] = priv->conf.peer_id; mtod(m, u_int16_t *)[i++] = hpriv->conf.peer_id; if (hpriv->conf.enable_dseq) { hdr |= L2TP_HDR_SEQ; mtod(m, u_int16_t *)[i++] = htons(hpriv->ns); mtod(m, u_int16_t *)[i++] = htons(hpriv->nr); hpriv->ns++; } mtod(m, u_int16_t *)[0] = htons(hdr); /* Send packet */ NG_FWD_NEW_DATA(error, item, priv->lower, m); return (error); } /* * Send a message to our controlling node that we've failed. */ static void ng_l2tp_seq_failure(priv_p priv) { struct ng_mesg *msg; int error; NG_MKMESSAGE(msg, NGM_L2TP_COOKIE, NGM_L2TP_ACK_FAILURE, 0, M_NOWAIT); if (msg == NULL) return; NG_SEND_MSG_ID(error, priv->node, msg, priv->ftarget, 0); } /************************************************************************ SEQUENCE NUMBER HANDLING ************************************************************************/ /* * Initialize sequence number state. */ static void ng_l2tp_seq_init(priv_p priv) { struct l2tp_seq *const seq = &priv->seq; KASSERT(priv->conf.peer_win >= 1, ("%s: peer_win is zero", __FUNCTION__)); memset(seq, 0, sizeof(*seq)); seq->cwnd = 1; seq->wmax = priv->conf.peer_win; if (seq->wmax > L2TP_MAX_XWIN) seq->wmax = L2TP_MAX_XWIN; seq->ssth = seq->wmax; seq->max_rexmits = priv->conf.rexmit_max; seq->max_rexmit_to = priv->conf.rexmit_max_to; callout_init(&seq->rack_timer, 0); callout_init(&seq->xack_timer, 0); L2TP_SEQ_CHECK(seq); } /* * Adjust sequence number state accordingly after reconfiguration. */ static int ng_l2tp_seq_adjust(priv_p priv, const struct ng_l2tp_config *conf) { struct l2tp_seq *const seq = &priv->seq; u_int16_t new_wmax; /* If disabling node, reset state sequence number */ if (!conf->enabled) { ng_l2tp_seq_reset(priv); return (0); } /* Adjust peer's max recv window; it can only increase */ new_wmax = conf->peer_win; if (new_wmax > L2TP_MAX_XWIN) new_wmax = L2TP_MAX_XWIN; if (new_wmax == 0) return (EINVAL); if (new_wmax < seq->wmax) return (EBUSY); seq->wmax = new_wmax; /* Update retransmit parameters */ seq->max_rexmits = conf->rexmit_max; seq->max_rexmit_to = conf->rexmit_max_to; /* Done */ return (0); } /* * Reset sequence number state. */ static void ng_l2tp_seq_reset(priv_p priv) { struct l2tp_seq *const seq = &priv->seq; hook_p hook; int i; /* Sanity check */ L2TP_SEQ_CHECK(seq); /* Stop timers */ if (seq->rack_timer_running && callout_stop(&seq->rack_timer) == 1) { seq->rack_timer_running = 0; NG_NODE_UNREF(priv->node); } if (seq->xack_timer_running && callout_stop(&seq->xack_timer) == 1) { seq->xack_timer_running = 0; NG_NODE_UNREF(priv->node); } /* Free retransmit queue */ for (i = 0; i < L2TP_MAX_XWIN; i++) { if (seq->xwin[i] == NULL) break; m_freem(seq->xwin[i]); } /* Reset session hooks' sequence number states */ NG_NODE_FOREACH_HOOK(priv->node, ng_l2tp_reset_session, NULL, hook); /* Reset node's sequence number state */ memset(seq, 0, sizeof(*seq)); seq->cwnd = 1; seq->wmax = L2TP_MAX_XWIN; seq->ssth = seq->wmax; /* Done */ L2TP_SEQ_CHECK(seq); } /* * Handle receipt of an acknowledgement value (Nr) from peer. */ static void ng_l2tp_seq_recv_nr(priv_p priv, u_int16_t nr) { struct l2tp_seq *const seq = &priv->seq; struct mbuf *m; int nack; int i; /* Verify peer's ACK is in range */ if ((nack = L2TP_SEQ_DIFF(nr, seq->rack)) <= 0) return; /* duplicate ack */ if (L2TP_SEQ_DIFF(nr, seq->ns) > 0) { priv->stats.recvBadAcks++; /* ack for packet not sent */ return; } KASSERT(nack <= L2TP_MAX_XWIN, ("%s: nack=%d > %d", __FUNCTION__, nack, L2TP_MAX_XWIN)); /* Update receive ack stats */ seq->rack = nr; seq->rexmits = 0; /* Free acknowledged packets and shift up packets in the xmit queue */ for (i = 0; i < nack; i++) m_freem(seq->xwin[i]); memmove(seq->xwin, seq->xwin + nack, (L2TP_MAX_XWIN - nack) * sizeof(*seq->xwin)); memset(seq->xwin + (L2TP_MAX_XWIN - nack), 0, nack * sizeof(*seq->xwin)); /* * Do slow-start/congestion avoidance windowing algorithm described * in RFC 2661, Appendix A. Here we handle a multiple ACK as if each * ACK had arrived separately. */ if (seq->cwnd < seq->wmax) { /* Handle slow start phase */ if (seq->cwnd < seq->ssth) { seq->cwnd += nack; nack = 0; if (seq->cwnd > seq->ssth) { /* into cg.av. phase */ nack = seq->cwnd - seq->ssth; seq->cwnd = seq->ssth; } } /* Handle congestion avoidance phase */ if (seq->cwnd >= seq->ssth) { seq->acks += nack; while (seq->acks >= seq->cwnd) { seq->acks -= seq->cwnd; if (seq->cwnd < seq->wmax) seq->cwnd++; } } } /* Stop xmit timer */ if (seq->rack_timer_running && callout_stop(&seq->rack_timer) == 1) { seq->rack_timer_running = 0; NG_NODE_UNREF(priv->node); } /* If transmit queue is empty, we're done for now */ if (seq->xwin[0] == NULL) return; /* Start restransmit timer again */ callout_reset(&seq->rack_timer, hz, ng_l2tp_seq_rack_timeout, priv->node); seq->rack_timer_running = 1; NG_NODE_REF(priv->node); /* * Send more packets, trying to keep peer's receive window full. * If there is a memory error, pretend packet was sent, as it * will get retransmitted later anyway. */ while ((i = L2TP_SEQ_DIFF(seq->ns, seq->rack)) < seq->cwnd && seq->xwin[i] != NULL) { if ((m = L2TP_COPY_MBUF(seq->xwin[i], M_DONTWAIT)) == NULL) priv->stats.memoryFailures++; else ng_l2tp_xmit_ctrl(priv, m, seq->ns); seq->ns++; } } /* * Handle receipt of a sequence number value (Ns) from peer. * We make no attempt to re-order out of order packets. * * This function should only be called for non-ZLB packets. * * Returns: * 0 Accept packet * -1 Drop packet */ static int ng_l2tp_seq_recv_ns(priv_p priv, u_int16_t ns) { struct l2tp_seq *const seq = &priv->seq; /* If not what we expect, drop packet and send an immediate ZLB ack */ if (ns != seq->nr) { if (L2TP_SEQ_DIFF(ns, seq->nr) < 0) priv->stats.recvDuplicates++; else priv->stats.recvOutOfOrder++; ng_l2tp_xmit_ctrl(priv, NULL, seq->ns); return (-1); } /* Update recv sequence number */ seq->nr++; /* Start receive ack timer, if not already running */ if (!seq->xack_timer_running) { callout_reset(&seq->xack_timer, L2TP_DELAYED_ACK, ng_l2tp_seq_xack_timeout, priv->node); seq->xack_timer_running = 1; NG_NODE_REF(priv->node); } /* Accept packet */ return (0); } /* * Handle an ack timeout. We have an outstanding ack that we * were hoping to piggy-back, but haven't, so send a ZLB. */ static void ng_l2tp_seq_xack_timeout(void *arg) { const node_p node = arg; const priv_p priv = NG_NODE_PRIVATE(node); struct l2tp_seq *const seq = &priv->seq; int s; /* Check if node is going away */ s = splnet(); if (NG_NODE_NOT_VALID(node)) { seq->xack_timer_running = 0; if (!seq->rack_timer_running) { FREE(priv, M_NETGRAPH_L2TP); NG_NODE_SET_PRIVATE(node, NULL); } NG_NODE_UNREF(node); splx(s); return; } /* Sanity check */ L2TP_SEQ_CHECK(seq); /* If ack is still outstanding, send a ZLB */ if (seq->xack != seq->nr) ng_l2tp_xmit_ctrl(priv, NULL, seq->ns); /* Done */ seq->xack_timer_running = 0; NG_NODE_UNREF(node); L2TP_SEQ_CHECK(seq); splx(s); } /* * Handle a transmit timeout. The peer has failed to respond * with an ack for our packet, so retransmit it. */ static void ng_l2tp_seq_rack_timeout(void *arg) { const node_p node = arg; const priv_p priv = NG_NODE_PRIVATE(node); struct l2tp_seq *const seq = &priv->seq; struct mbuf *m; u_int delay; int s; /* Check if node is going away */ s = splnet(); if (NG_NODE_NOT_VALID(node)) { seq->rack_timer_running = 0; if (!seq->xack_timer_running) { FREE(priv, M_NETGRAPH_L2TP); NG_NODE_SET_PRIVATE(node, NULL); } NG_NODE_UNREF(node); splx(s); return; } /* Sanity check */ L2TP_SEQ_CHECK(seq); /* Make sure peer's ack is still outstanding before doing anything */ if (seq->rack == seq->ns) { seq->rack_timer_running = 0; NG_NODE_UNREF(node); goto done; } priv->stats.xmitRetransmits++; /* Have we reached the retransmit limit? If so, notify owner. */ if (seq->rexmits++ >= seq->max_rexmits) ng_l2tp_seq_failure(priv); /* Restart timer, this time with an increased delay */ delay = (seq->rexmits > 12) ? (1 << 12) : (1 << seq->rexmits); if (delay > seq->max_rexmit_to) delay = seq->max_rexmit_to; callout_reset(&seq->rack_timer, hz * delay, ng_l2tp_seq_rack_timeout, node); /* Do slow-start/congestion algorithm windowing algorithm */ seq->ssth = (seq->cwnd + 1) / 2; seq->cwnd = 1; seq->acks = 0; /* Retransmit oldest unack'd packet */ if ((m = L2TP_COPY_MBUF(seq->xwin[0], M_DONTWAIT)) == NULL) priv->stats.memoryFailures++; else ng_l2tp_xmit_ctrl(priv, m, seq->rack); done: /* Done */ L2TP_SEQ_CHECK(seq); splx(s); } /* * Transmit a control stream packet, payload optional. * The transmit sequence number is not incremented. */ static int ng_l2tp_xmit_ctrl(priv_p priv, struct mbuf *m, u_int16_t ns) { struct l2tp_seq *const seq = &priv->seq; u_int16_t session_id = 0; - meta_p meta = NULL; int error; /* If no mbuf passed, send an empty packet (ZLB) */ if (m == NULL) { /* Create a new mbuf for ZLB packet */ MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) { priv->stats.memoryFailures++; return (ENOBUFS); } m->m_len = m->m_pkthdr.len = 12; m->m_pkthdr.rcvif = NULL; priv->stats.xmitZLBs++; } else { /* Strip off session ID */ if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { priv->stats.memoryFailures++; return (ENOBUFS); } memcpy(&session_id, mtod(m, u_int16_t *), 2); m_adj(m, 2); /* Make room for L2TP header */ M_PREPEND(m, 12, M_DONTWAIT); if (m == NULL) { priv->stats.memoryFailures++; return (ENOBUFS); } } /* Fill in L2TP header */ mtod(m, u_int16_t *)[0] = htons(L2TP_CTRL_HDR); mtod(m, u_int16_t *)[1] = htons(m->m_pkthdr.len); mtod(m, u_int16_t *)[2] = priv->conf.peer_id; mtod(m, u_int16_t *)[3] = session_id; mtod(m, u_int16_t *)[4] = htons(ns); mtod(m, u_int16_t *)[5] = htons(seq->nr); /* Update sequence number info and stats */ priv->stats.xmitPackets++; priv->stats.xmitOctets += m->m_pkthdr.len; /* Stop ack timer: we're sending an ack with this packet */ if (seq->xack_timer_running && callout_stop(&seq->xack_timer) == 1) { seq->xack_timer_running = 0; NG_NODE_UNREF(priv->node); } seq->xack = seq->nr; /* Send packet */ - NG_SEND_DATA(error, priv->lower, m, meta); + NG_SEND_DATA_ONLY(error, priv->lower, m); return (error); } #ifdef INVARIANTS /* * Sanity check sequence number state. */ static void ng_l2tp_seq_check(struct l2tp_seq *seq) { const int self_unack = L2TP_SEQ_DIFF(seq->nr, seq->xack); const int peer_unack = L2TP_SEQ_DIFF(seq->ns, seq->rack); int i; #define CHECK(p) KASSERT((p), ("%s: not: %s", __FUNCTION__, #p)) CHECK(seq->wmax <= L2TP_MAX_XWIN); CHECK(seq->cwnd >= 1); CHECK(seq->cwnd <= seq->wmax); CHECK(seq->ssth >= 1); CHECK(seq->ssth <= seq->wmax); if (seq->cwnd < seq->ssth) CHECK(seq->acks == 0); else CHECK(seq->acks <= seq->cwnd); CHECK(self_unack >= 0); CHECK(peer_unack >= 0); CHECK(peer_unack <= seq->wmax); CHECK((self_unack == 0) ^ seq->xack_timer_running); CHECK((peer_unack == 0) ^ seq->rack_timer_running); CHECK(seq->rack_timer_running || !callout_pending(&seq->rack_timer)); CHECK(seq->xack_timer_running || !callout_pending(&seq->xack_timer)); for (i = 0; i < peer_unack; i++) CHECK(seq->xwin[i] != NULL); for ( ; i < seq->cwnd; i++) /* verify peer's recv window full */ CHECK(seq->xwin[i] == NULL); #undef CHECK } #endif /* INVARIANTS */ Index: head/sys/netgraph/ng_one2many.c =================================================================== --- head/sys/netgraph/ng_one2many.c (revision 131154) +++ head/sys/netgraph/ng_one2many.c (revision 131155) @@ -1,558 +1,547 @@ /* * ng_one2many.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_one2many(4) netgraph node type * * Packets received on the "one" hook are sent out each of the * "many" hooks accoring to an algorithm. Packets received on any * "many" hook are always delivered to the "one" hook. */ #include #include #include #include #include #include #include #include #include #include #include /* Per-link private data */ struct ng_one2many_link { hook_p hook; /* netgraph hook */ struct ng_one2many_link_stats stats; /* link stats */ }; /* Per-node private data */ struct ng_one2many_private { struct ng_one2many_config conf; /* node configuration */ struct ng_one2many_link one; /* "one" hook */ struct ng_one2many_link many[NG_ONE2MANY_MAX_LINKS]; u_int16_t nextMany; /* next round-robin */ u_int16_t numActiveMany; /* # active "many" */ u_int16_t activeMany[NG_ONE2MANY_MAX_LINKS]; }; typedef struct ng_one2many_private *priv_p; /* Netgraph node methods */ static ng_constructor_t ng_one2many_constructor; static ng_rcvmsg_t ng_one2many_rcvmsg; static ng_shutdown_t ng_one2many_shutdown; static ng_newhook_t ng_one2many_newhook; static ng_rcvdata_t ng_one2many_rcvdata; static ng_disconnect_t ng_one2many_disconnect; /* Other functions */ static void ng_one2many_update_many(priv_p priv); /* Store each hook's link number in the private field */ #define LINK_NUM(hook) (*(int16_t *)(&(hook)->private)) /****************************************************************** NETGRAPH PARSE TYPES ******************************************************************/ /* Parse type for struct ng_one2many_config */ static const struct ng_parse_fixedarray_info ng_one2many_enableLinks_array_type_info = { &ng_parse_uint8_type, NG_ONE2MANY_MAX_LINKS }; static const struct ng_parse_type ng_one2many_enableLinks_array_type = { &ng_parse_fixedarray_type, &ng_one2many_enableLinks_array_type_info, }; static const struct ng_parse_struct_field ng_one2many_config_type_fields[] = NG_ONE2MANY_CONFIG_TYPE_INFO(&ng_one2many_enableLinks_array_type); static const struct ng_parse_type ng_one2many_config_type = { &ng_parse_struct_type, &ng_one2many_config_type_fields }; /* Parse type for struct ng_one2many_link_stats */ static const struct ng_parse_struct_field ng_one2many_link_stats_type_fields[] = NG_ONE2MANY_LINK_STATS_TYPE_INFO; static const struct ng_parse_type ng_one2many_link_stats_type = { &ng_parse_struct_type, &ng_one2many_link_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_one2many_cmdlist[] = { { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_SET_CONFIG, "setconfig", &ng_one2many_config_type, NULL }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_GET_CONFIG, "getconfig", NULL, &ng_one2many_config_type }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_GET_STATS, "getstats", &ng_parse_int32_type, &ng_one2many_link_stats_type }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_CLR_STATS, "clrstats", &ng_parse_int32_type, NULL, }, { NGM_ONE2MANY_COOKIE, NGM_ONE2MANY_GETCLR_STATS, "getclrstats", &ng_parse_int32_type, &ng_one2many_link_stats_type }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_one2many_typestruct = { .version = NG_ABI_VERSION, .name = NG_ONE2MANY_NODE_TYPE, .constructor = ng_one2many_constructor, .rcvmsg = ng_one2many_rcvmsg, .shutdown = ng_one2many_shutdown, .newhook = ng_one2many_newhook, .rcvdata = ng_one2many_rcvdata, .disconnect = ng_one2many_disconnect, .cmdlist = ng_one2many_cmdlist, }; NETGRAPH_INIT(one2many, &ng_one2many_typestruct); /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * Node constructor */ static int ng_one2many_constructor(node_p node) { priv_p priv; /* Allocate and initialize private info */ MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); if (priv == NULL) return (ENOMEM); priv->conf.xmitAlg = NG_ONE2MANY_XMIT_ROUNDROBIN; priv->conf.failAlg = NG_ONE2MANY_FAIL_MANUAL; NG_NODE_SET_PRIVATE(node, priv); /* Done */ return (0); } /* * Method for attaching a new hook */ static int ng_one2many_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_one2many_link *link; int linkNum; u_long i; /* Which hook? */ if (strncmp(name, NG_ONE2MANY_HOOK_MANY_PREFIX, strlen(NG_ONE2MANY_HOOK_MANY_PREFIX)) == 0) { const char *cp; char *eptr; cp = name + strlen(NG_ONE2MANY_HOOK_MANY_PREFIX); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); i = strtoul(cp, &eptr, 10); if (*eptr != '\0' || i < 0 || i >= NG_ONE2MANY_MAX_LINKS) return (EINVAL); linkNum = (int)i; link = &priv->many[linkNum]; } else if (strcmp(name, NG_ONE2MANY_HOOK_ONE) == 0) { linkNum = NG_ONE2MANY_ONE_LINKNUM; link = &priv->one; } else return (EINVAL); /* Is hook already connected? (should never happen) */ if (link->hook != NULL) return (EISCONN); /* Setup private info for this link */ NG_HOOK_SET_PRIVATE(hook, (void *)(intptr_t)linkNum); link->hook = hook; bzero(&link->stats, sizeof(link->stats)); if (linkNum != NG_ONE2MANY_ONE_LINKNUM) { priv->conf.enabledLinks[linkNum] = 1; /* auto-enable link */ ng_one2many_update_many(priv); } /* Done */ return (0); } /* * Receive a control message */ static int ng_one2many_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_ONE2MANY_COOKIE: switch (msg->header.cmd) { case NGM_ONE2MANY_SET_CONFIG: { struct ng_one2many_config *conf; int i; /* Check that new configuration is valid */ if (msg->header.arglen != sizeof(*conf)) { error = EINVAL; break; } conf = (struct ng_one2many_config *)msg->data; switch (conf->xmitAlg) { case NG_ONE2MANY_XMIT_ROUNDROBIN: case NG_ONE2MANY_XMIT_ALL: break; default: error = EINVAL; break; } switch (conf->failAlg) { case NG_ONE2MANY_FAIL_MANUAL: break; default: error = EINVAL; break; } if (error != 0) break; /* Normalized many link enabled bits */ for (i = 0; i < NG_ONE2MANY_MAX_LINKS; i++) conf->enabledLinks[i] = !!conf->enabledLinks[i]; /* Copy config and reset */ bcopy(conf, &priv->conf, sizeof(*conf)); ng_one2many_update_many(priv); break; } case NGM_ONE2MANY_GET_CONFIG: { struct ng_one2many_config *conf; NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_one2many_config *)resp->data; bcopy(&priv->conf, conf, sizeof(priv->conf)); break; } case NGM_ONE2MANY_GET_STATS: case NGM_ONE2MANY_CLR_STATS: case NGM_ONE2MANY_GETCLR_STATS: { struct ng_one2many_link *link; int linkNum; /* Get link */ if (msg->header.arglen != sizeof(int32_t)) { error = EINVAL; break; } linkNum = *((int32_t *)msg->data); if (linkNum == NG_ONE2MANY_ONE_LINKNUM) link = &priv->one; else if (linkNum == 0 && linkNum < NG_ONE2MANY_MAX_LINKS) { link = &priv->many[linkNum]; } else { error = EINVAL; break; } /* Get/clear stats */ if (msg->header.cmd != NGM_ONE2MANY_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_ONE2MANY_GET_STATS) bzero(&link->stats, sizeof(link->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 data on a hook */ static int ng_one2many_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_one2many_link *src; struct ng_one2many_link *dst = NULL; int error = 0; int linkNum; int i; struct mbuf *m; - meta_p meta; m = NGI_M(item); /* just peaking, mbuf still owned by item */ /* Get link number */ linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); KASSERT(linkNum == NG_ONE2MANY_ONE_LINKNUM || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), ("%s: linkNum=%d", __func__, linkNum)); /* Figure out source link */ src = (linkNum == NG_ONE2MANY_ONE_LINKNUM) ? &priv->one : &priv->many[linkNum]; KASSERT(src->hook != NULL, ("%s: no src%d", __func__, linkNum)); /* Update receive stats */ src->stats.recvPackets++; src->stats.recvOctets += m->m_pkthdr.len; /* Figure out destination link */ if (linkNum == NG_ONE2MANY_ONE_LINKNUM) { if (priv->numActiveMany == 0) { NG_FREE_ITEM(item); return (ENOTCONN); } switch(priv->conf.xmitAlg) { case NG_ONE2MANY_XMIT_ROUNDROBIN: dst = &priv->many[priv->activeMany[priv->nextMany]]; priv->nextMany = (priv->nextMany + 1) % priv->numActiveMany; break; case NG_ONE2MANY_XMIT_ALL: - meta = NGI_META(item); /* peek.. */ /* no need to copy data for the 1st one */ dst = &priv->many[priv->activeMany[0]]; /* make copies of data and send for all links * except the first one, which we'll do last */ for (i = 1; i < priv->numActiveMany; i++) { - meta_p meta2 = NULL; struct mbuf *m2; struct ng_one2many_link *mdst; mdst = &priv->many[priv->activeMany[i]]; m2 = m_dup(m, M_DONTWAIT); /* XXX m_copypacket() */ if (m2 == NULL) { mdst->stats.memoryFailures++; NG_FREE_ITEM(item); NG_FREE_M(m); return (ENOBUFS); } - if (meta != NULL - && (meta2 = ng_copy_meta(meta)) == NULL) { - mdst->stats.memoryFailures++; - m_freem(m2); - NG_FREE_ITEM(item); - NG_FREE_M(m); - return (ENOMEM); - } /* Update transmit stats */ mdst->stats.xmitPackets++; mdst->stats.xmitOctets += m->m_pkthdr.len; - NG_SEND_DATA(error, mdst->hook, m2, meta2); + NG_SEND_DATA_ONLY(error, mdst->hook, m2); } break; #ifdef INVARIANTS default: panic("%s: invalid xmitAlg", __func__); #endif } } else { dst = &priv->one; } /* Update transmit stats */ dst->stats.xmitPackets++; dst->stats.xmitOctets += m->m_pkthdr.len; /* Deliver packet */ NG_FWD_ITEM_HOOK(error, item, dst->hook); return (error); } /* * Shutdown node */ static int ng_one2many_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); KASSERT(priv->numActiveMany == 0, ("%s: numActiveMany=%d", __func__, priv->numActiveMany)); FREE(priv, M_NETGRAPH); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); return (0); } /* * Hook disconnection. */ static int ng_one2many_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 == NG_ONE2MANY_ONE_LINKNUM || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), ("%s: linkNum=%d", __func__, linkNum)); /* Nuke the link */ if (linkNum == NG_ONE2MANY_ONE_LINKNUM) priv->one.hook = NULL; else { priv->many[linkNum].hook = NULL; priv->conf.enabledLinks[linkNum] = 0; ng_one2many_update_many(priv); } /* If no hooks left, go away */ 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); } /****************************************************************** OTHER FUNCTIONS ******************************************************************/ /* * Update internal state after the addition or removal of a "many" link */ static void ng_one2many_update_many(priv_p priv) { int linkNum; /* Update list of which "many" links are up */ priv->numActiveMany = 0; for (linkNum = 0; linkNum < NG_ONE2MANY_MAX_LINKS; linkNum++) { switch (priv->conf.failAlg) { case NG_ONE2MANY_FAIL_MANUAL: if (priv->many[linkNum].hook != NULL && priv->conf.enabledLinks[linkNum]) { priv->activeMany[priv->numActiveMany] = linkNum; priv->numActiveMany++; } break; #ifdef INVARIANTS default: panic("%s: invalid failAlg", __func__); #endif } } /* Update transmit algorithm state */ switch (priv->conf.xmitAlg) { case NG_ONE2MANY_XMIT_ROUNDROBIN: if (priv->numActiveMany > 0) priv->nextMany %= priv->numActiveMany; break; case NG_ONE2MANY_XMIT_ALL: break; #ifdef INVARIANTS default: panic("%s: invalid xmitAlg", __func__); #endif } } Index: head/sys/netgraph/ng_ppp.c =================================================================== --- head/sys/netgraph/ng_ppp.c (revision 131154) +++ head/sys/netgraph/ng_ppp.c (revision 131155) @@ -1,2098 +1,2067 @@ /* * ng_ppp.c * * Copyright (c) 1996-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$ * $Whistle: ng_ppp.c,v 1.24 1999/11/01 09:24:52 julian Exp $ */ /* * PPP node type. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_PPP, "netgraph_ppp", "netgraph ppp node"); #else #define M_NETGRAPH_PPP M_NETGRAPH #endif #define PROT_VALID(p) (((p) & 0x0101) == 0x0001) #define PROT_COMPRESSABLE(p) (((p) & 0xff00) == 0x0000) /* Some PPP protocol numbers we're interested in */ #define PROT_APPLETALK 0x0029 #define PROT_COMPD 0x00fd #define PROT_CRYPTD 0x0053 #define PROT_IP 0x0021 #define PROT_IPV6 0x0057 #define PROT_IPX 0x002b #define PROT_LCP 0xc021 #define PROT_MP 0x003d #define PROT_VJCOMP 0x002d #define PROT_VJUNCOMP 0x002f /* Multilink PPP definitions */ #define MP_MIN_MRRU 1500 /* per RFC 1990 */ #define MP_INITIAL_SEQ 0 /* per RFC 1990 */ #define MP_MIN_LINK_MRU 32 #define MP_SHORT_SEQ_MASK 0x00000fff /* short seq # mask */ #define MP_SHORT_SEQ_HIBIT 0x00000800 /* short seq # high bit */ #define MP_SHORT_FIRST_FLAG 0x00008000 /* first fragment in frame */ #define MP_SHORT_LAST_FLAG 0x00004000 /* last fragment in frame */ #define MP_LONG_SEQ_MASK 0x00ffffff /* long seq # mask */ #define MP_LONG_SEQ_HIBIT 0x00800000 /* long seq # high bit */ #define MP_LONG_FIRST_FLAG 0x80000000 /* first fragment in frame */ #define MP_LONG_LAST_FLAG 0x40000000 /* last fragment in frame */ #define MP_NOSEQ 0x7fffffff /* impossible sequence number */ /* Sign extension of MP sequence numbers */ #define MP_SHORT_EXTEND(s) (((s) & MP_SHORT_SEQ_HIBIT) ? \ ((s) | ~MP_SHORT_SEQ_MASK) \ : ((s) & MP_SHORT_SEQ_MASK)) #define MP_LONG_EXTEND(s) (((s) & MP_LONG_SEQ_HIBIT) ? \ ((s) | ~MP_LONG_SEQ_MASK) \ : ((s) & MP_LONG_SEQ_MASK)) /* Comparision of MP sequence numbers. Note: all sequence numbers except priv->xseq are stored with the sign bit extended. */ #define MP_SHORT_SEQ_DIFF(x,y) MP_SHORT_EXTEND((x) - (y)) #define MP_LONG_SEQ_DIFF(x,y) MP_LONG_EXTEND((x) - (y)) #define MP_RECV_SEQ_DIFF(priv,x,y) \ ((priv)->conf.recvShortSeq ? \ MP_SHORT_SEQ_DIFF((x), (y)) : \ MP_LONG_SEQ_DIFF((x), (y))) /* Increment receive sequence number */ #define MP_NEXT_RECV_SEQ(priv,seq) \ ((priv)->conf.recvShortSeq ? \ MP_SHORT_EXTEND((seq) + 1) : \ MP_LONG_EXTEND((seq) + 1)) /* Don't fragment transmitted packets smaller than this */ #define MP_MIN_FRAG_LEN 6 /* Maximum fragment reasssembly queue length */ #define MP_MAX_QUEUE_LEN 128 /* Fragment queue scanner period */ #define MP_FRAGTIMER_INTERVAL (hz/2) /* We store incoming fragments this way */ struct ng_ppp_frag { int seq; /* fragment seq# */ u_char first; /* First in packet? */ u_char last; /* Last in packet? */ struct timeval timestamp; /* time of reception */ struct mbuf *data; /* Fragment data */ - meta_p meta; /* Fragment meta */ TAILQ_ENTRY(ng_ppp_frag) f_qent; /* Fragment queue */ }; /* We use integer indicies to refer to the non-link hooks */ static const char *const ng_ppp_hook_names[] = { NG_PPP_HOOK_ATALK, #define HOOK_INDEX_ATALK 0 NG_PPP_HOOK_BYPASS, #define HOOK_INDEX_BYPASS 1 NG_PPP_HOOK_COMPRESS, #define HOOK_INDEX_COMPRESS 2 NG_PPP_HOOK_ENCRYPT, #define HOOK_INDEX_ENCRYPT 3 NG_PPP_HOOK_DECOMPRESS, #define HOOK_INDEX_DECOMPRESS 4 NG_PPP_HOOK_DECRYPT, #define HOOK_INDEX_DECRYPT 5 NG_PPP_HOOK_INET, #define HOOK_INDEX_INET 6 NG_PPP_HOOK_IPX, #define HOOK_INDEX_IPX 7 NG_PPP_HOOK_VJC_COMP, #define HOOK_INDEX_VJC_COMP 8 NG_PPP_HOOK_VJC_IP, #define HOOK_INDEX_VJC_IP 9 NG_PPP_HOOK_VJC_UNCOMP, #define HOOK_INDEX_VJC_UNCOMP 10 NG_PPP_HOOK_VJC_VJIP, #define HOOK_INDEX_VJC_VJIP 11 NG_PPP_HOOK_IPV6, #define HOOK_INDEX_IPV6 12 NULL #define HOOK_INDEX_MAX 13 }; /* We store index numbers in the hook private pointer. The HOOK_INDEX() for a hook is either the index (above) for normal hooks, or the ones complement of the link number for link hooks. XXX Not any more.. (what a hack) #define HOOK_INDEX(hook) (*((int16_t *) &(hook)->private)) */ /* Per-link private information */ struct ng_ppp_link { struct ng_ppp_link_conf conf; /* link configuration */ hook_p hook; /* connection to link data */ int32_t seq; /* highest rec'd seq# - MSEQ */ struct timeval lastWrite; /* time of last write */ int bytesInQueue; /* bytes in the output queue */ struct ng_ppp_link_stat stats; /* Link stats */ }; /* Total per-node private information */ struct ng_ppp_private { struct ng_ppp_bund_conf conf; /* bundle config */ struct ng_ppp_link_stat bundleStats; /* bundle stats */ struct ng_ppp_link links[NG_PPP_MAX_LINKS];/* per-link info */ int32_t xseq; /* next out MP seq # */ int32_t mseq; /* min links[i].seq */ u_char vjCompHooked; /* VJ comp hooked up? */ u_char allLinksEqual; /* all xmit the same? */ u_char timerActive; /* frag timer active? */ u_int numActiveLinks; /* how many links up */ int activeLinks[NG_PPP_MAX_LINKS]; /* indicies */ u_int lastLink; /* for round robin */ hook_p hooks[HOOK_INDEX_MAX]; /* non-link hooks */ TAILQ_HEAD(ng_ppp_fraglist, ng_ppp_frag) /* fragment queue */ frags; int qlen; /* fraq queue length */ struct callout_handle fragTimer; /* fraq queue check */ }; typedef struct ng_ppp_private *priv_p; /* Netgraph node methods */ static ng_constructor_t ng_ppp_constructor; static ng_rcvmsg_t ng_ppp_rcvmsg; static ng_shutdown_t ng_ppp_shutdown; static ng_newhook_t ng_ppp_newhook; static ng_rcvdata_t ng_ppp_rcvdata; static ng_disconnect_t ng_ppp_disconnect; /* Helper functions */ static int ng_ppp_input(node_p node, int bypass, int linkNum, item_p item); static int ng_ppp_output(node_p node, int bypass, int proto, int linkNum, item_p item); static int ng_ppp_mp_input(node_p node, int linkNum, item_p item); static int ng_ppp_check_packet(node_p node); -static void ng_ppp_get_packet(node_p node, struct mbuf **mp, meta_p *metap); +static void ng_ppp_get_packet(node_p node, struct mbuf **mp); static int ng_ppp_frag_process(node_p node); static int ng_ppp_frag_trim(node_p node); static void ng_ppp_frag_timeout(void *arg); static void ng_ppp_frag_checkstale(node_p node); static void ng_ppp_frag_reset(node_p node); -static int ng_ppp_mp_output(node_p node, struct mbuf *m, meta_p meta); +static int ng_ppp_mp_output(node_p node, struct mbuf *m); static void ng_ppp_mp_strategy(node_p node, int len, int *distrib); static int ng_ppp_intcmp(const void *v1, const void *v2); static struct mbuf *ng_ppp_addproto(struct mbuf *m, int proto, int compOK); static struct mbuf *ng_ppp_prepend(struct mbuf *m, const void *buf, int len); static int ng_ppp_config_valid(node_p node, const struct ng_ppp_node_conf *newConf); static void ng_ppp_update(node_p node, int newConf); static void ng_ppp_start_frag_timer(node_p node); static void ng_ppp_stop_frag_timer(node_p node); /* Parse type for struct ng_ppp_mp_state_type */ static const struct ng_parse_fixedarray_info ng_ppp_rseq_array_info = { &ng_parse_hint32_type, NG_PPP_MAX_LINKS }; static const struct ng_parse_type ng_ppp_rseq_array_type = { &ng_parse_fixedarray_type, &ng_ppp_rseq_array_info, }; static const struct ng_parse_struct_field ng_ppp_mp_state_type_fields[] = NG_PPP_MP_STATE_TYPE_INFO(&ng_ppp_rseq_array_type); static const struct ng_parse_type ng_ppp_mp_state_type = { &ng_parse_struct_type, &ng_ppp_mp_state_type_fields }; /* Parse type for struct ng_ppp_link_conf */ static const struct ng_parse_struct_field ng_ppp_link_type_fields[] = NG_PPP_LINK_TYPE_INFO; static const struct ng_parse_type ng_ppp_link_type = { &ng_parse_struct_type, &ng_ppp_link_type_fields }; /* Parse type for struct ng_ppp_bund_conf */ static const struct ng_parse_struct_field ng_ppp_bund_type_fields[] = NG_PPP_BUND_TYPE_INFO; static const struct ng_parse_type ng_ppp_bund_type = { &ng_parse_struct_type, &ng_ppp_bund_type_fields }; /* Parse type for struct ng_ppp_node_conf */ static const struct ng_parse_fixedarray_info ng_ppp_array_info = { &ng_ppp_link_type, NG_PPP_MAX_LINKS }; static const struct ng_parse_type ng_ppp_link_array_type = { &ng_parse_fixedarray_type, &ng_ppp_array_info, }; static const struct ng_parse_struct_field ng_ppp_conf_type_fields[] = NG_PPP_CONFIG_TYPE_INFO(&ng_ppp_bund_type, &ng_ppp_link_array_type); static const struct ng_parse_type ng_ppp_conf_type = { &ng_parse_struct_type, &ng_ppp_conf_type_fields }; /* Parse type for struct ng_ppp_link_stat */ static const struct ng_parse_struct_field ng_ppp_stats_type_fields[] = NG_PPP_STATS_TYPE_INFO; static const struct ng_parse_type ng_ppp_stats_type = { &ng_parse_struct_type, &ng_ppp_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_ppp_cmds[] = { { NGM_PPP_COOKIE, NGM_PPP_SET_CONFIG, "setconfig", &ng_ppp_conf_type, NULL }, { NGM_PPP_COOKIE, NGM_PPP_GET_CONFIG, "getconfig", NULL, &ng_ppp_conf_type }, { NGM_PPP_COOKIE, NGM_PPP_GET_MP_STATE, "getmpstate", NULL, &ng_ppp_mp_state_type }, { NGM_PPP_COOKIE, NGM_PPP_GET_LINK_STATS, "getstats", &ng_parse_int16_type, &ng_ppp_stats_type }, { NGM_PPP_COOKIE, NGM_PPP_CLR_LINK_STATS, "clrstats", &ng_parse_int16_type, NULL }, { NGM_PPP_COOKIE, NGM_PPP_GETCLR_LINK_STATS, "getclrstats", &ng_parse_int16_type, &ng_ppp_stats_type }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_ppp_typestruct = { .version = NG_ABI_VERSION, .name = NG_PPP_NODE_TYPE, .constructor = ng_ppp_constructor, .rcvmsg = ng_ppp_rcvmsg, .shutdown = ng_ppp_shutdown, .newhook = ng_ppp_newhook, .rcvdata = ng_ppp_rcvdata, .disconnect = ng_ppp_disconnect, .cmdlist = ng_ppp_cmds, }; NETGRAPH_INIT(ppp, &ng_ppp_typestruct); static int *compareLatencies; /* hack for ng_ppp_intcmp() */ /* Address and control field header */ static const u_char ng_ppp_acf[2] = { 0xff, 0x03 }; /* Maximum time we'll let a complete incoming packet sit in the queue */ static const struct timeval ng_ppp_max_staleness = { 2, 0 }; /* 2 seconds */ #define ERROUT(x) do { error = (x); goto done; } while (0) /************************************************************************ NETGRAPH NODE STUFF ************************************************************************/ /* * Node type constructor */ static int ng_ppp_constructor(node_p node) { priv_p priv; int i; /* Allocate private structure */ MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH_PPP, M_NOWAIT | M_ZERO); if (priv == NULL) return (ENOMEM); NG_NODE_SET_PRIVATE(node, priv); /* Initialize state */ TAILQ_INIT(&priv->frags); for (i = 0; i < NG_PPP_MAX_LINKS; i++) priv->links[i].seq = MP_NOSEQ; callout_handle_init(&priv->fragTimer); /* Done */ return (0); } /* * Give our OK for a hook to be added */ static int ng_ppp_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); int linkNum = -1; hook_p *hookPtr = NULL; int hookIndex = -1; /* Figure out which hook it is */ if (strncmp(name, NG_PPP_HOOK_LINK_PREFIX, /* a link hook? */ strlen(NG_PPP_HOOK_LINK_PREFIX)) == 0) { const char *cp; char *eptr; cp = name + strlen(NG_PPP_HOOK_LINK_PREFIX); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); linkNum = (int)strtoul(cp, &eptr, 10); if (*eptr != '\0' || linkNum < 0 || linkNum >= NG_PPP_MAX_LINKS) return (EINVAL); hookPtr = &priv->links[linkNum].hook; hookIndex = ~linkNum; } else { /* must be a non-link hook */ int i; for (i = 0; ng_ppp_hook_names[i] != NULL; i++) { if (strcmp(name, ng_ppp_hook_names[i]) == 0) { hookPtr = &priv->hooks[i]; hookIndex = i; break; } } if (ng_ppp_hook_names[i] == NULL) return (EINVAL); /* no such hook */ } /* See if hook is already connected */ if (*hookPtr != NULL) return (EISCONN); /* Disallow more than one link unless multilink is enabled */ if (linkNum != -1 && priv->links[linkNum].conf.enableLink && !priv->conf.enableMultilink && priv->numActiveLinks >= 1) return (ENODEV); /* OK */ *hookPtr = hook; NG_HOOK_SET_PRIVATE(hook, (void *)(intptr_t)hookIndex); ng_ppp_update(node, 0); return (0); } /* * Receive a control message */ static int ng_ppp_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_PPP_COOKIE: switch (msg->header.cmd) { case NGM_PPP_SET_CONFIG: { struct ng_ppp_node_conf *const conf = (struct ng_ppp_node_conf *)msg->data; int i; /* Check for invalid or illegal config */ if (msg->header.arglen != sizeof(*conf)) ERROUT(EINVAL); if (!ng_ppp_config_valid(node, conf)) ERROUT(EINVAL); /* Copy config */ priv->conf = conf->bund; for (i = 0; i < NG_PPP_MAX_LINKS; i++) priv->links[i].conf = conf->links[i]; ng_ppp_update(node, 1); break; } case NGM_PPP_GET_CONFIG: { struct ng_ppp_node_conf *conf; int i; NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); conf = (struct ng_ppp_node_conf *)resp->data; conf->bund = priv->conf; for (i = 0; i < NG_PPP_MAX_LINKS; i++) conf->links[i] = priv->links[i].conf; break; } case NGM_PPP_GET_MP_STATE: { struct ng_ppp_mp_state *info; int i; NG_MKRESPONSE(resp, msg, sizeof(*info), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); info = (struct ng_ppp_mp_state *)resp->data; bzero(info, sizeof(*info)); for (i = 0; i < NG_PPP_MAX_LINKS; i++) { if (priv->links[i].seq != MP_NOSEQ) info->rseq[i] = priv->links[i].seq; } info->mseq = priv->mseq; info->xseq = priv->xseq; break; } case NGM_PPP_GET_LINK_STATS: case NGM_PPP_CLR_LINK_STATS: case NGM_PPP_GETCLR_LINK_STATS: { struct ng_ppp_link_stat *stats; u_int16_t linkNum; if (msg->header.arglen != sizeof(u_int16_t)) ERROUT(EINVAL); linkNum = *((u_int16_t *) msg->data); if (linkNum >= NG_PPP_MAX_LINKS && linkNum != NG_PPP_BUNDLE_LINKNUM) ERROUT(EINVAL); stats = (linkNum == NG_PPP_BUNDLE_LINKNUM) ? &priv->bundleStats : &priv->links[linkNum].stats; if (msg->header.cmd != NGM_PPP_CLR_LINK_STATS) { NG_MKRESPONSE(resp, msg, sizeof(struct ng_ppp_link_stat), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); bcopy(stats, resp->data, sizeof(*stats)); } if (msg->header.cmd != NGM_PPP_GET_LINK_STATS) bzero(stats, sizeof(*stats)); break; } default: error = EINVAL; break; } break; case NGM_VJC_COOKIE: { /* * Forward it to the vjc node. leave the * old return address alone. * If we have no hook, let NG_RESPOND_MSG * clean up any remaining resources. * Because we have no resp, the item will be freed * along with anything it references. Don't * let msg be freed twice. */ NGI_MSG(item) = msg; /* put it back in the item */ msg = NULL; if ((lasthook = priv->links[HOOK_INDEX_VJC_IP].hook)) { NG_FWD_ITEM_HOOK(error, item, lasthook); } return (error); } 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_ppp_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); const int index = (intptr_t)NG_HOOK_PRIVATE(hook); u_int16_t linkNum = NG_PPP_BUNDLE_LINKNUM; hook_p outHook = NULL; int proto = 0, error; struct mbuf *m; NGI_GET_M(item, m); /* Did it come from a link hook? */ if (index < 0) { struct ng_ppp_link *link; /* Convert index into a link number */ linkNum = (u_int16_t)~index; KASSERT(linkNum < NG_PPP_MAX_LINKS, ("%s: bogus index 0x%x", __func__, index)); link = &priv->links[linkNum]; /* Stats */ link->stats.recvFrames++; link->stats.recvOctets += m->m_pkthdr.len; /* Strip address and control fields, if present */ if (m->m_pkthdr.len >= 2) { if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } if (bcmp(mtod(m, u_char *), &ng_ppp_acf, 2) == 0) m_adj(m, 2); } /* Dispatch incoming frame (if not enabled, to bypass) */ NGI_M(item) = m; /* put changed m back in item */ return ng_ppp_input(node, !link->conf.enableLink, linkNum, item); } /* Get protocol & check if data allowed from this hook */ NGI_M(item) = m; /* put possibly changed m back in item */ switch (index) { /* Outgoing data */ case HOOK_INDEX_ATALK: if (!priv->conf.enableAtalk) { NG_FREE_ITEM(item); return (ENXIO); } proto = PROT_APPLETALK; break; case HOOK_INDEX_IPX: if (!priv->conf.enableIPX) { NG_FREE_ITEM(item); return (ENXIO); } proto = PROT_IPX; break; case HOOK_INDEX_IPV6: if (!priv->conf.enableIPv6) { NG_FREE_ITEM(item); return (ENXIO); } proto = PROT_IPV6; break; case HOOK_INDEX_INET: case HOOK_INDEX_VJC_VJIP: if (!priv->conf.enableIP) { NG_FREE_ITEM(item); return (ENXIO); } proto = PROT_IP; break; case HOOK_INDEX_VJC_COMP: if (!priv->conf.enableVJCompression) { NG_FREE_ITEM(item); return (ENXIO); } proto = PROT_VJCOMP; break; case HOOK_INDEX_VJC_UNCOMP: if (!priv->conf.enableVJCompression) { NG_FREE_ITEM(item); return (ENXIO); } proto = PROT_VJUNCOMP; break; case HOOK_INDEX_COMPRESS: if (!priv->conf.enableCompression) { NG_FREE_ITEM(item); return (ENXIO); } proto = PROT_COMPD; break; case HOOK_INDEX_ENCRYPT: if (!priv->conf.enableEncryption) { NG_FREE_ITEM(item); return (ENXIO); } proto = PROT_CRYPTD; break; case HOOK_INDEX_BYPASS: if (m->m_pkthdr.len < 4) { NG_FREE_ITEM(item); return (EINVAL); } if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { NGI_M(item) = NULL; /* don't free twice */ NG_FREE_ITEM(item); return (ENOBUFS); } NGI_M(item) = m; /* m may have changed */ linkNum = ntohs(mtod(m, u_int16_t *)[0]); proto = ntohs(mtod(m, u_int16_t *)[1]); m_adj(m, 4); if (linkNum >= NG_PPP_MAX_LINKS && linkNum != NG_PPP_BUNDLE_LINKNUM) { NG_FREE_ITEM(item); return (EINVAL); } break; /* Incoming data */ case HOOK_INDEX_VJC_IP: if (!priv->conf.enableIP || !priv->conf.enableVJDecompression) { NG_FREE_ITEM(item); return (ENXIO); } break; case HOOK_INDEX_DECOMPRESS: if (!priv->conf.enableDecompression) { NG_FREE_ITEM(item); return (ENXIO); } break; case HOOK_INDEX_DECRYPT: if (!priv->conf.enableDecryption) { NG_FREE_ITEM(item); return (ENXIO); } break; default: panic("%s: bogus index 0x%x", __func__, index); } /* Now figure out what to do with the frame */ switch (index) { /* Outgoing data */ case HOOK_INDEX_INET: if (priv->conf.enableVJCompression && priv->vjCompHooked) { outHook = priv->hooks[HOOK_INDEX_VJC_IP]; break; } /* FALLTHROUGH */ case HOOK_INDEX_ATALK: case HOOK_INDEX_IPV6: case HOOK_INDEX_IPX: case HOOK_INDEX_VJC_COMP: case HOOK_INDEX_VJC_UNCOMP: case HOOK_INDEX_VJC_VJIP: if (priv->conf.enableCompression && priv->hooks[HOOK_INDEX_COMPRESS] != NULL) { if ((m = ng_ppp_addproto(m, proto, 1)) == NULL) { NGI_M(item) = NULL; NG_FREE_ITEM(item); return (ENOBUFS); } NGI_M(item) = m; /* m may have changed */ outHook = priv->hooks[HOOK_INDEX_COMPRESS]; break; } /* FALLTHROUGH */ case HOOK_INDEX_COMPRESS: if (priv->conf.enableEncryption && priv->hooks[HOOK_INDEX_ENCRYPT] != NULL) { if ((m = ng_ppp_addproto(m, proto, 1)) == NULL) { NGI_M(item) = NULL; NG_FREE_ITEM(item); return (ENOBUFS); } NGI_M(item) = m; /* m may have changed */ outHook = priv->hooks[HOOK_INDEX_ENCRYPT]; break; } /* FALLTHROUGH */ case HOOK_INDEX_ENCRYPT: return ng_ppp_output(node, 0, proto, NG_PPP_BUNDLE_LINKNUM, item); case HOOK_INDEX_BYPASS: return ng_ppp_output(node, 1, proto, linkNum, item); /* Incoming data */ case HOOK_INDEX_DECRYPT: case HOOK_INDEX_DECOMPRESS: return ng_ppp_input(node, 0, NG_PPP_BUNDLE_LINKNUM, item); case HOOK_INDEX_VJC_IP: outHook = priv->hooks[HOOK_INDEX_INET]; break; } /* Send packet out hook */ NG_FWD_ITEM_HOOK(error, item, outHook); return (error); } /* * Destroy node */ static int ng_ppp_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); /* Stop fragment queue timer */ ng_ppp_stop_frag_timer(node); /* Take down netgraph node */ ng_ppp_frag_reset(node); bzero(priv, sizeof(*priv)); FREE(priv, M_NETGRAPH_PPP); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); /* let the node escape */ return (0); } /* * Hook disconnection */ static int ng_ppp_disconnect(hook_p hook) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); const int index = (intptr_t)NG_HOOK_PRIVATE(hook); /* Zero out hook pointer */ if (index < 0) priv->links[~index].hook = NULL; else priv->hooks[index] = NULL; /* Update derived info (or go away if no hooks left) */ if (NG_NODE_NUMHOOKS(node) > 0) { ng_ppp_update(node, 0); } else { if (NG_NODE_IS_VALID(node)) { ng_rmnode_self(node); } } return (0); } /************************************************************************ HELPER STUFF ************************************************************************/ /* * Handle an incoming frame. Extract the PPP protocol number * and dispatch accordingly. */ static int ng_ppp_input(node_p node, int bypass, int linkNum, item_p item) { const priv_p priv = NG_NODE_PRIVATE(node); hook_p outHook = NULL; int proto, error; struct mbuf *m; NGI_GET_M(item, m); /* Extract protocol number */ for (proto = 0; !PROT_VALID(proto) && m->m_pkthdr.len > 0; ) { if (m->m_len < 1 && (m = m_pullup(m, 1)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } proto = (proto << 8) + *mtod(m, u_char *); m_adj(m, 1); } if (!PROT_VALID(proto)) { if (linkNum == NG_PPP_BUNDLE_LINKNUM) priv->bundleStats.badProtos++; else priv->links[linkNum].stats.badProtos++; NG_FREE_ITEM(item); NG_FREE_M(m); return (EINVAL); } /* Bypass frame? */ if (bypass) goto bypass; /* Check protocol */ switch (proto) { case PROT_COMPD: if (priv->conf.enableDecompression) outHook = priv->hooks[HOOK_INDEX_DECOMPRESS]; break; case PROT_CRYPTD: if (priv->conf.enableDecryption) outHook = priv->hooks[HOOK_INDEX_DECRYPT]; break; case PROT_VJCOMP: if (priv->conf.enableVJDecompression && priv->vjCompHooked) outHook = priv->hooks[HOOK_INDEX_VJC_COMP]; break; case PROT_VJUNCOMP: if (priv->conf.enableVJDecompression && priv->vjCompHooked) outHook = priv->hooks[HOOK_INDEX_VJC_UNCOMP]; break; case PROT_MP: if (priv->conf.enableMultilink && linkNum != NG_PPP_BUNDLE_LINKNUM) { NGI_M(item) = m; return ng_ppp_mp_input(node, linkNum, item); } break; case PROT_APPLETALK: if (priv->conf.enableAtalk) outHook = priv->hooks[HOOK_INDEX_ATALK]; break; case PROT_IPX: if (priv->conf.enableIPX) outHook = priv->hooks[HOOK_INDEX_IPX]; break; case PROT_IP: if (priv->conf.enableIP) outHook = priv->hooks[HOOK_INDEX_INET]; break; case PROT_IPV6: if (priv->conf.enableIPv6) outHook = priv->hooks[HOOK_INDEX_IPV6]; break; } bypass: /* For unknown/inactive protocols, forward out the bypass hook */ if (outHook == NULL) { u_int16_t hdr[2]; hdr[0] = htons(linkNum); hdr[1] = htons((u_int16_t)proto); if ((m = ng_ppp_prepend(m, &hdr, 4)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } outHook = priv->hooks[HOOK_INDEX_BYPASS]; } /* Forward frame */ NG_FWD_NEW_DATA(error, item, outHook, m); return (error); } /* * Deliver a frame out a link, either a real one or NG_PPP_BUNDLE_LINKNUM. * If the link is not enabled then ENXIO is returned, unless "bypass" is != 0. * * If the frame is too big for the particular link, return EMSGSIZE. */ static int ng_ppp_output(node_p node, int bypass, int proto, int linkNum, item_p item) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_link *link; int len, error; struct mbuf *m; u_int16_t mru; /* Extract mbuf */ NGI_GET_M(item, m); /* If not doing MP, map bundle virtual link to (the only) link */ if (linkNum == NG_PPP_BUNDLE_LINKNUM && !priv->conf.enableMultilink) linkNum = priv->activeLinks[0]; /* Get link pointer (optimization) */ link = (linkNum != NG_PPP_BUNDLE_LINKNUM) ? &priv->links[linkNum] : NULL; /* Check link status (if real) */ if (linkNum != NG_PPP_BUNDLE_LINKNUM) { if (!bypass && !link->conf.enableLink) { NG_FREE_M(m); NG_FREE_ITEM(item); return (ENXIO); } if (link->hook == NULL) { NG_FREE_M(m); NG_FREE_ITEM(item); return (ENETDOWN); } } /* Check peer's MRU for this link */ mru = (link != NULL) ? link->conf.mru : priv->conf.mrru; if (mru != 0 && m->m_pkthdr.len > mru) { NG_FREE_M(m); NG_FREE_ITEM(item); return (EMSGSIZE); } /* Prepend protocol number, possibly compressed */ if ((m = ng_ppp_addproto(m, proto, linkNum == NG_PPP_BUNDLE_LINKNUM || link->conf.enableProtoComp)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } /* Special handling for the MP virtual link */ if (linkNum == NG_PPP_BUNDLE_LINKNUM) { - meta_p meta; - - /* strip off and discard the queue item */ - NGI_GET_META(item, meta); + /* discard the queue item */ NG_FREE_ITEM(item); - return ng_ppp_mp_output(node, m, meta); + return ng_ppp_mp_output(node, m); } /* Prepend address and control field (unless compressed) */ if (proto == PROT_LCP || !link->conf.enableACFComp) { if ((m = ng_ppp_prepend(m, &ng_ppp_acf, 2)) == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } } /* Deliver frame */ len = m->m_pkthdr.len; NG_FWD_NEW_DATA(error, item, link->hook, m); /* Update stats and 'bytes in queue' counter */ if (error == 0) { link->stats.xmitFrames++; link->stats.xmitOctets += len; link->bytesInQueue += len; getmicrouptime(&link->lastWrite); } return error; } /* * Handle an incoming multi-link fragment * * The fragment reassembly algorithm is somewhat complex. This is mainly * because we are required not to reorder the reconstructed packets, yet * fragments are only guaranteed to arrive in order on a per-link basis. * In other words, when we have a complete packet ready, but the previous * packet is still incomplete, we have to decide between delivering the * complete packet and throwing away the incomplete one, or waiting to * see if the remainder of the incomplete one arrives, at which time we * can deliver both packets, in order. * * This problem is exacerbated by "sequence number slew", which is when * the sequence numbers coming in from different links are far apart from * each other. In particular, certain unnamed equipment (*cough* Ascend) * has been seen to generate sequence number slew of up to 10 on an ISDN * 2B-channel MP link. There is nothing invalid about sequence number slew * but it makes the reasssembly process have to work harder. * * However, the peer is required to transmit fragments in order on each * link. That means if we define MSEQ as the minimum over all links of * the highest sequence number received on that link, then we can always * give up any hope of receiving a fragment with sequence number < MSEQ in * the future (all of this using 'wraparound' sequence number space). * Therefore we can always immediately throw away incomplete packets * missing fragments with sequence numbers < MSEQ. * * Here is an overview of our algorithm: * * o Received fragments are inserted into a queue, for which we * maintain these invariants between calls to this function: * * - Fragments are ordered in the queue by sequence number * - If a complete packet is at the head of the queue, then * the first fragment in the packet has seq# > MSEQ + 1 * (otherwise, we could deliver it immediately) * - If any fragments have seq# < MSEQ, then they are necessarily * part of a packet whose missing seq#'s are all > MSEQ (otherwise, * we can throw them away because they'll never be completed) * - The queue contains at most MP_MAX_QUEUE_LEN fragments * * o We have a periodic timer that checks the queue for the first * complete packet that has been sitting in the queue "too long". * When one is detected, all previous (incomplete) fragments are * discarded, their missing fragments are declared lost and MSEQ * is increased. * * o If we recieve a fragment with seq# < MSEQ, we throw it away * because we've already delcared it lost. * * This assumes linkNum != NG_PPP_BUNDLE_LINKNUM. */ static int ng_ppp_mp_input(node_p node, int linkNum, item_p item) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_link *const link = &priv->links[linkNum]; struct ng_ppp_frag frag0, *frag = &frag0; struct ng_ppp_frag *qent; int i, diff, inserted; struct mbuf *m; - meta_p meta; NGI_GET_M(item, m); - NGI_GET_META(item, meta); NG_FREE_ITEM(item); /* Stats */ priv->bundleStats.recvFrames++; priv->bundleStats.recvOctets += m->m_pkthdr.len; /* Extract fragment information from MP header */ if (priv->conf.recvShortSeq) { u_int16_t shdr; if (m->m_pkthdr.len < 2) { link->stats.runts++; NG_FREE_M(m); - NG_FREE_META(meta); return (EINVAL); } - if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { - NG_FREE_META(meta); + if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) return (ENOBUFS); - } + shdr = ntohs(*mtod(m, u_int16_t *)); frag->seq = MP_SHORT_EXTEND(shdr); frag->first = (shdr & MP_SHORT_FIRST_FLAG) != 0; frag->last = (shdr & MP_SHORT_LAST_FLAG) != 0; diff = MP_SHORT_SEQ_DIFF(frag->seq, priv->mseq); m_adj(m, 2); } else { u_int32_t lhdr; if (m->m_pkthdr.len < 4) { link->stats.runts++; NG_FREE_M(m); - NG_FREE_META(meta); return (EINVAL); } - if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { - NG_FREE_META(meta); + if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) return (ENOBUFS); - } + lhdr = ntohl(*mtod(m, u_int32_t *)); frag->seq = MP_LONG_EXTEND(lhdr); frag->first = (lhdr & MP_LONG_FIRST_FLAG) != 0; frag->last = (lhdr & MP_LONG_LAST_FLAG) != 0; diff = MP_LONG_SEQ_DIFF(frag->seq, priv->mseq); m_adj(m, 4); } frag->data = m; - frag->meta = meta; getmicrouptime(&frag->timestamp); /* If sequence number is < MSEQ, we've already declared this fragment as lost, so we have no choice now but to drop it */ if (diff < 0) { link->stats.dropFragments++; NG_FREE_M(m); - NG_FREE_META(meta); return (0); } /* Update highest received sequence number on this link and MSEQ */ priv->mseq = link->seq = frag->seq; for (i = 0; i < priv->numActiveLinks; i++) { struct ng_ppp_link *const alink = &priv->links[priv->activeLinks[i]]; if (MP_RECV_SEQ_DIFF(priv, alink->seq, priv->mseq) < 0) priv->mseq = alink->seq; } /* Allocate a new frag struct for the queue */ MALLOC(frag, struct ng_ppp_frag *, sizeof(*frag), M_NETGRAPH_PPP, M_NOWAIT); if (frag == NULL) { NG_FREE_M(m); - NG_FREE_META(meta); ng_ppp_frag_process(node); return (ENOMEM); } *frag = frag0; /* Add fragment to queue, which is sorted by sequence number */ inserted = 0; TAILQ_FOREACH_REVERSE(qent, &priv->frags, ng_ppp_fraglist, f_qent) { diff = MP_RECV_SEQ_DIFF(priv, frag->seq, qent->seq); if (diff > 0) { TAILQ_INSERT_AFTER(&priv->frags, qent, frag, f_qent); inserted = 1; break; } else if (diff == 0) { /* should never happen! */ link->stats.dupFragments++; NG_FREE_M(frag->data); - NG_FREE_META(frag->meta); FREE(frag, M_NETGRAPH_PPP); return (EINVAL); } } if (!inserted) TAILQ_INSERT_HEAD(&priv->frags, frag, f_qent); priv->qlen++; /* Process the queue */ return ng_ppp_frag_process(node); } /* * Examine our list of fragments, and determine if there is a * complete and deliverable packet at the head of the list. * Return 1 if so, zero otherwise. */ static int ng_ppp_check_packet(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_frag *qent, *qnext; /* Check for empty queue */ if (TAILQ_EMPTY(&priv->frags)) return (0); /* Check first fragment is the start of a deliverable packet */ qent = TAILQ_FIRST(&priv->frags); if (!qent->first || MP_RECV_SEQ_DIFF(priv, qent->seq, priv->mseq) > 1) return (0); /* Check that all the fragments are there */ while (!qent->last) { qnext = TAILQ_NEXT(qent, f_qent); if (qnext == NULL) /* end of queue */ return (0); if (qnext->seq != MP_NEXT_RECV_SEQ(priv, qent->seq)) return (0); qent = qnext; } /* Got one */ return (1); } /* * Pull a completed packet off the head of the incoming fragment queue. * This assumes there is a completed packet there to pull off. */ static void -ng_ppp_get_packet(node_p node, struct mbuf **mp, meta_p *metap) +ng_ppp_get_packet(node_p node, struct mbuf **mp) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_frag *qent, *qnext; struct mbuf *m = NULL, *tail; qent = TAILQ_FIRST(&priv->frags); KASSERT(!TAILQ_EMPTY(&priv->frags) && qent->first, ("%s: no packet", __func__)); for (tail = NULL; qent != NULL; qent = qnext) { qnext = TAILQ_NEXT(qent, f_qent); KASSERT(!TAILQ_EMPTY(&priv->frags), ("%s: empty q", __func__)); TAILQ_REMOVE(&priv->frags, qent, f_qent); - if (tail == NULL) { + if (tail == NULL) tail = m = qent->data; - *metap = qent->meta; /* inherit first frag's meta */ - } else { + else { m->m_pkthdr.len += qent->data->m_pkthdr.len; tail->m_next = qent->data; - NG_FREE_META(qent->meta); /* drop other frags' metas */ } while (tail->m_next != NULL) tail = tail->m_next; if (qent->last) qnext = NULL; FREE(qent, M_NETGRAPH_PPP); priv->qlen--; } *mp = m; } /* * Trim fragments from the queue whose packets can never be completed. * This assumes a complete packet is NOT at the beginning of the queue. * Returns 1 if fragments were removed, zero otherwise. */ static int ng_ppp_frag_trim(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_frag *qent, *qnext = NULL; int removed = 0; /* Scan for "dead" fragments and remove them */ while (1) { int dead = 0; /* If queue is empty, we're done */ if (TAILQ_EMPTY(&priv->frags)) break; /* Determine whether first fragment can ever be completed */ TAILQ_FOREACH(qent, &priv->frags, f_qent) { if (MP_RECV_SEQ_DIFF(priv, qent->seq, priv->mseq) >= 0) break; qnext = TAILQ_NEXT(qent, f_qent); KASSERT(qnext != NULL, ("%s: last frag < MSEQ?", __func__)); if (qnext->seq != MP_NEXT_RECV_SEQ(priv, qent->seq) || qent->last || qnext->first) { dead = 1; break; } } if (!dead) break; /* Remove fragment and all others in the same packet */ while ((qent = TAILQ_FIRST(&priv->frags)) != qnext) { KASSERT(!TAILQ_EMPTY(&priv->frags), ("%s: empty q", __func__)); priv->bundleStats.dropFragments++; TAILQ_REMOVE(&priv->frags, qent, f_qent); NG_FREE_M(qent->data); - NG_FREE_META(qent->meta); FREE(qent, M_NETGRAPH_PPP); priv->qlen--; removed = 1; } } return (removed); } /* * Run the queue, restoring the queue invariants */ static int ng_ppp_frag_process(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); struct mbuf *m; - meta_p meta; item_p item; /* Deliver any deliverable packets */ while (ng_ppp_check_packet(node)) { - ng_ppp_get_packet(node, &m, &meta); - item = ng_package_data(m, meta); + ng_ppp_get_packet(node, &m); + item = ng_package_data(m, NULL); ng_ppp_input(node, 0, NG_PPP_BUNDLE_LINKNUM, item); } /* Delete dead fragments and try again */ if (ng_ppp_frag_trim(node)) { while (ng_ppp_check_packet(node)) { - ng_ppp_get_packet(node, &m, &meta); - item = ng_package_data(m, meta); + ng_ppp_get_packet(node, &m); + item = ng_package_data(m, NULL); ng_ppp_input(node, 0, NG_PPP_BUNDLE_LINKNUM, item); } } /* Check for stale fragments while we're here */ ng_ppp_frag_checkstale(node); /* Check queue length */ if (priv->qlen > MP_MAX_QUEUE_LEN) { struct ng_ppp_frag *qent; int i; /* Get oldest fragment */ KASSERT(!TAILQ_EMPTY(&priv->frags), ("%s: empty q", __func__)); qent = TAILQ_FIRST(&priv->frags); /* Bump MSEQ if necessary */ if (MP_RECV_SEQ_DIFF(priv, priv->mseq, qent->seq) < 0) { priv->mseq = qent->seq; for (i = 0; i < priv->numActiveLinks; i++) { struct ng_ppp_link *const alink = &priv->links[priv->activeLinks[i]]; if (MP_RECV_SEQ_DIFF(priv, alink->seq, priv->mseq) < 0) alink->seq = priv->mseq; } } /* Drop it */ priv->bundleStats.dropFragments++; TAILQ_REMOVE(&priv->frags, qent, f_qent); NG_FREE_M(qent->data); - NG_FREE_META(qent->meta); FREE(qent, M_NETGRAPH_PPP); priv->qlen--; /* Process queue again */ return ng_ppp_frag_process(node); } /* Done */ return (0); } /* * Check for 'stale' completed packets that need to be delivered * * If a link goes down or has a temporary failure, MSEQ can get * "stuck", because no new incoming fragments appear on that link. * This can cause completed packets to never get delivered if * their sequence numbers are all > MSEQ + 1. * * This routine checks how long all of the completed packets have * been sitting in the queue, and if too long, removes fragments * from the queue and increments MSEQ to allow them to be delivered. */ static void ng_ppp_frag_checkstale(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_frag *qent, *beg, *end; struct timeval now, age; struct mbuf *m; - meta_p meta; int i, seq; item_p item; int endseq; now.tv_sec = 0; /* uninitialized state */ while (1) { /* If queue is empty, we're done */ if (TAILQ_EMPTY(&priv->frags)) break; /* Find the first complete packet in the queue */ beg = end = NULL; seq = TAILQ_FIRST(&priv->frags)->seq; TAILQ_FOREACH(qent, &priv->frags, f_qent) { if (qent->first) beg = qent; else if (qent->seq != seq) beg = NULL; if (beg != NULL && qent->last) { end = qent; break; } seq = MP_NEXT_RECV_SEQ(priv, seq); } /* If none found, exit */ if (end == NULL) break; /* Get current time (we assume we've been up for >= 1 second) */ if (now.tv_sec == 0) getmicrouptime(&now); /* Check if packet has been queued too long */ age = now; timevalsub(&age, &beg->timestamp); if (timevalcmp(&age, &ng_ppp_max_staleness, < )) break; /* Throw away junk fragments in front of the completed packet */ while ((qent = TAILQ_FIRST(&priv->frags)) != beg) { KASSERT(!TAILQ_EMPTY(&priv->frags), ("%s: empty q", __func__)); priv->bundleStats.dropFragments++; TAILQ_REMOVE(&priv->frags, qent, f_qent); NG_FREE_M(qent->data); - NG_FREE_META(qent->meta); FREE(qent, M_NETGRAPH_PPP); priv->qlen--; } /* Extract completed packet */ endseq = end->seq; - ng_ppp_get_packet(node, &m, &meta); + ng_ppp_get_packet(node, &m); /* Bump MSEQ if necessary */ if (MP_RECV_SEQ_DIFF(priv, priv->mseq, endseq) < 0) { priv->mseq = endseq; for (i = 0; i < priv->numActiveLinks; i++) { struct ng_ppp_link *const alink = &priv->links[priv->activeLinks[i]]; if (MP_RECV_SEQ_DIFF(priv, alink->seq, priv->mseq) < 0) alink->seq = priv->mseq; } } /* Deliver packet */ - item = ng_package_data(m, meta); + item = ng_package_data(m, NULL); ng_ppp_input(node, 0, NG_PPP_BUNDLE_LINKNUM, item); } } /* * Periodically call ng_ppp_frag_checkstale() */ static void ng_ppp_frag_timeout(void *arg) { const node_p node = arg; const priv_p priv = NG_NODE_PRIVATE(node); int s = splnet(); /* Handle the race where shutdown happens just before splnet() above */ if (NG_NODE_NOT_VALID(node)) { NG_NODE_UNREF(node); splx(s); return; } /* Reset timer state after timeout */ KASSERT(priv->timerActive, ("%s: !timerActive", __func__)); priv->timerActive = 0; KASSERT(node->nd_refs > 1, ("%s: nd_refs=%d", __func__, node->nd_refs)); NG_NODE_UNREF(node); /* Start timer again */ ng_ppp_start_frag_timer(node); /* Scan the fragment queue */ ng_ppp_frag_checkstale(node); splx(s); } /* * Deliver a frame out on the bundle, i.e., figure out how to fragment * the frame across the individual PPP links and do so. */ static int -ng_ppp_mp_output(node_p node, struct mbuf *m, meta_p meta) +ng_ppp_mp_output(node_p node, struct mbuf *m) { const priv_p priv = NG_NODE_PRIVATE(node); const int hdr_len = priv->conf.xmitShortSeq ? 2 : 4; int distrib[NG_PPP_MAX_LINKS]; int firstFragment; int activeLinkNum; item_p item; /* At least one link must be active */ if (priv->numActiveLinks == 0) { NG_FREE_M(m); - NG_FREE_META(meta); return (ENETDOWN); } /* Round-robin strategy */ if (priv->conf.enableRoundRobin || m->m_pkthdr.len < MP_MIN_FRAG_LEN) { activeLinkNum = priv->lastLink++ % priv->numActiveLinks; bzero(&distrib, priv->numActiveLinks * sizeof(distrib[0])); distrib[activeLinkNum] = m->m_pkthdr.len; goto deliver; } /* Strategy when all links are equivalent (optimize the common case) */ if (priv->allLinksEqual) { const int fraction = m->m_pkthdr.len / priv->numActiveLinks; int i, remain; for (i = 0; i < priv->numActiveLinks; i++) distrib[priv->lastLink++ % priv->numActiveLinks] = fraction; remain = m->m_pkthdr.len - (fraction * priv->numActiveLinks); while (remain > 0) { distrib[priv->lastLink++ % priv->numActiveLinks]++; remain--; } goto deliver; } /* Strategy when all links are not equivalent */ ng_ppp_mp_strategy(node, m->m_pkthdr.len, distrib); deliver: /* Update stats */ priv->bundleStats.xmitFrames++; priv->bundleStats.xmitOctets += m->m_pkthdr.len; /* Send alloted portions of frame out on the link(s) */ for (firstFragment = 1, activeLinkNum = priv->numActiveLinks - 1; activeLinkNum >= 0; activeLinkNum--) { const int linkNum = priv->activeLinks[activeLinkNum]; struct ng_ppp_link *const link = &priv->links[linkNum]; /* Deliver fragment(s) out the next link */ for ( ; distrib[activeLinkNum] > 0; firstFragment = 0) { int len, lastFragment, error; struct mbuf *m2; - meta_p meta2; /* Calculate fragment length; don't exceed link MTU */ len = distrib[activeLinkNum]; if (len > link->conf.mru - hdr_len) len = link->conf.mru - hdr_len; distrib[activeLinkNum] -= len; lastFragment = (len == m->m_pkthdr.len); /* Split off next fragment as "m2" */ m2 = m; if (!lastFragment) { struct mbuf *n = m_split(m, len, M_DONTWAIT); if (n == NULL) { NG_FREE_M(m); - NG_FREE_META(meta); return (ENOMEM); } m = n; } /* Prepend MP header */ if (priv->conf.xmitShortSeq) { u_int16_t shdr; shdr = priv->xseq; priv->xseq = (priv->xseq + 1) & MP_SHORT_SEQ_MASK; if (firstFragment) shdr |= MP_SHORT_FIRST_FLAG; if (lastFragment) shdr |= MP_SHORT_LAST_FLAG; shdr = htons(shdr); m2 = ng_ppp_prepend(m2, &shdr, 2); } else { u_int32_t lhdr; lhdr = priv->xseq; priv->xseq = (priv->xseq + 1) & MP_LONG_SEQ_MASK; if (firstFragment) lhdr |= MP_LONG_FIRST_FLAG; if (lastFragment) lhdr |= MP_LONG_LAST_FLAG; lhdr = htonl(lhdr); m2 = ng_ppp_prepend(m2, &lhdr, 4); } if (m2 == NULL) { if (!lastFragment) m_freem(m); - NG_FREE_META(meta); return (ENOBUFS); } - /* Copy the meta information, if any */ - meta2 = lastFragment ? meta : ng_copy_meta(meta); - /* Send fragment */ - item = ng_package_data(m2, meta2); + item = ng_package_data(m2, NULL); error = ng_ppp_output(node, 0, PROT_MP, linkNum, item); if (error != 0) { - if (!lastFragment) { + if (!lastFragment) NG_FREE_M(m); - NG_FREE_META(meta); - } return (error); } } } /* Done */ return (0); } /* * Computing the optimal fragmentation * ----------------------------------- * * This routine tries to compute the optimal fragmentation pattern based * on each link's latency, bandwidth, and calculated additional latency. * The latter quantity is the additional latency caused by previously * written data that has not been transmitted yet. * * This algorithm is only useful when not all of the links have the * same latency and bandwidth values. * * The essential idea is to make the last bit of each fragment of the * frame arrive at the opposite end at the exact same time. This greedy * algorithm is optimal, in that no other scheduling could result in any * packet arriving any sooner unless packets are delivered out of order. * * Suppose link i has bandwidth b_i (in tens of bytes per milisecond) and * latency l_i (in miliseconds). Consider the function function f_i(t) * which is equal to the number of bytes that will have arrived at * the peer after t miliseconds if we start writing continuously at * time t = 0. Then f_i(t) = b_i * (t - l_i) = ((b_i * t) - (l_i * b_i). * That is, f_i(t) is a line with slope b_i and y-intersect -(l_i * b_i). * Note that the y-intersect is always <= zero because latency can't be * negative. Note also that really the function is f_i(t) except when * f_i(t) is negative, in which case the function is zero. To take * care of this, let Q_i(t) = { if (f_i(t) > 0) return 1; else return 0; }. * So the actual number of bytes that will have arrived at the peer after * t miliseconds is f_i(t) * Q_i(t). * * At any given time, each link has some additional latency a_i >= 0 * due to previously written fragment(s) which are still in the queue. * This value is easily computed from the time since last transmission, * the previous latency value, the number of bytes written, and the * link's bandwidth. * * Assume that l_i includes any a_i already, and that the links are * sorted by latency, so that l_i <= l_{i+1}. * * Let N be the total number of bytes in the current frame we are sending. * * Suppose we were to start writing bytes at time t = 0 on all links * simultaneously, which is the most we can possibly do. Then let * F(t) be equal to the total number of bytes received by the peer * after t miliseconds. Then F(t) = Sum_i (f_i(t) * Q_i(t)). * * Our goal is simply this: fragment the frame across the links such * that the peer is able to reconstruct the completed frame as soon as * possible, i.e., at the least possible value of t. Call this value t_0. * * Then it follows that F(t_0) = N. Our strategy is first to find the value * of t_0, and then deduce how many bytes to write to each link. * * Rewriting F(t_0): * * t_0 = ( N + Sum_i ( l_i * b_i * Q_i(t_0) ) ) / Sum_i ( b_i * Q_i(t_0) ) * * Now, we note that Q_i(t) is constant for l_i <= t <= l_{i+1}. t_0 will * lie in one of these ranges. To find it, we just need to find the i such * that F(l_i) <= N <= F(l_{i+1}). Then we compute all the constant values * for Q_i() in this range, plug in the remaining values, solving for t_0. * * Once t_0 is known, then the number of bytes to send on link i is * just f_i(t_0) * Q_i(t_0). * * In other words, we start allocating bytes to the links one at a time. * We keep adding links until the frame is completely sent. Some links * may not get any bytes because their latency is too high. * * Is all this work really worth the trouble? Depends on the situation. * The bigger the ratio of computer speed to link speed, and the more * important total bundle latency is (e.g., for interactive response time), * the more it's worth it. There is however the cost of calling this * function for every frame. The running time is O(n^2) where n is the * number of links that receive a non-zero number of bytes. * * Since latency is measured in miliseconds, the "resolution" of this * algorithm is one milisecond. * * To avoid this algorithm altogether, configure all links to have the * same latency and bandwidth. */ static void ng_ppp_mp_strategy(node_p node, int len, int *distrib) { const priv_p priv = NG_NODE_PRIVATE(node); int latency[NG_PPP_MAX_LINKS]; int sortByLatency[NG_PPP_MAX_LINKS]; int activeLinkNum; int t0, total, topSum, botSum; struct timeval now; int i, numFragments; /* If only one link, this gets real easy */ if (priv->numActiveLinks == 1) { distrib[0] = len; return; } /* Get current time */ getmicrouptime(&now); /* Compute latencies for each link at this point in time */ for (activeLinkNum = 0; activeLinkNum < priv->numActiveLinks; activeLinkNum++) { struct ng_ppp_link *alink; struct timeval diff; int xmitBytes; /* Start with base latency value */ alink = &priv->links[priv->activeLinks[activeLinkNum]]; latency[activeLinkNum] = alink->conf.latency; sortByLatency[activeLinkNum] = activeLinkNum; /* see below */ /* Any additional latency? */ if (alink->bytesInQueue == 0) continue; /* Compute time delta since last write */ diff = now; timevalsub(&diff, &alink->lastWrite); if (now.tv_sec < 0 || diff.tv_sec >= 10) { /* sanity */ alink->bytesInQueue = 0; continue; } /* How many bytes could have transmitted since last write? */ xmitBytes = (alink->conf.bandwidth * diff.tv_sec) + (alink->conf.bandwidth * (diff.tv_usec / 1000)) / 100; alink->bytesInQueue -= xmitBytes; if (alink->bytesInQueue < 0) alink->bytesInQueue = 0; else latency[activeLinkNum] += (100 * alink->bytesInQueue) / alink->conf.bandwidth; } /* Sort active links by latency */ compareLatencies = latency; qsort(sortByLatency, priv->numActiveLinks, sizeof(*sortByLatency), ng_ppp_intcmp); compareLatencies = NULL; /* Find the interval we need (add links in sortByLatency[] order) */ for (numFragments = 1; numFragments < priv->numActiveLinks; numFragments++) { for (total = i = 0; i < numFragments; i++) { int flowTime; flowTime = latency[sortByLatency[numFragments]] - latency[sortByLatency[i]]; total += ((flowTime * priv->links[ priv->activeLinks[sortByLatency[i]]].conf.bandwidth) + 99) / 100; } if (total >= len) break; } /* Solve for t_0 in that interval */ for (topSum = botSum = i = 0; i < numFragments; i++) { int bw = priv->links[ priv->activeLinks[sortByLatency[i]]].conf.bandwidth; topSum += latency[sortByLatency[i]] * bw; /* / 100 */ botSum += bw; /* / 100 */ } t0 = ((len * 100) + topSum + botSum / 2) / botSum; /* Compute f_i(t_0) all i */ bzero(distrib, priv->numActiveLinks * sizeof(*distrib)); for (total = i = 0; i < numFragments; i++) { int bw = priv->links[ priv->activeLinks[sortByLatency[i]]].conf.bandwidth; distrib[sortByLatency[i]] = (bw * (t0 - latency[sortByLatency[i]]) + 50) / 100; total += distrib[sortByLatency[i]]; } /* Deal with any rounding error */ if (total < len) { struct ng_ppp_link *fastLink = &priv->links[priv->activeLinks[sortByLatency[0]]]; int fast = 0; /* Find the fastest link */ for (i = 1; i < numFragments; i++) { struct ng_ppp_link *const link = &priv->links[priv->activeLinks[sortByLatency[i]]]; if (link->conf.bandwidth > fastLink->conf.bandwidth) { fast = i; fastLink = link; } } distrib[sortByLatency[fast]] += len - total; } else while (total > len) { struct ng_ppp_link *slowLink = &priv->links[priv->activeLinks[sortByLatency[0]]]; int delta, slow = 0; /* Find the slowest link that still has bytes to remove */ for (i = 1; i < numFragments; i++) { struct ng_ppp_link *const link = &priv->links[priv->activeLinks[sortByLatency[i]]]; if (distrib[sortByLatency[slow]] == 0 || (distrib[sortByLatency[i]] > 0 && link->conf.bandwidth < slowLink->conf.bandwidth)) { slow = i; slowLink = link; } } delta = total - len; if (delta > distrib[sortByLatency[slow]]) delta = distrib[sortByLatency[slow]]; distrib[sortByLatency[slow]] -= delta; total -= delta; } } /* * Compare two integers */ static int ng_ppp_intcmp(const void *v1, const void *v2) { const int index1 = *((const int *) v1); const int index2 = *((const int *) v2); return compareLatencies[index1] - compareLatencies[index2]; } /* * Prepend a possibly compressed PPP protocol number in front of a frame */ static struct mbuf * ng_ppp_addproto(struct mbuf *m, int proto, int compOK) { if (compOK && PROT_COMPRESSABLE(proto)) { u_char pbyte = (u_char)proto; return ng_ppp_prepend(m, &pbyte, 1); } else { u_int16_t pword = htons((u_int16_t)proto); return ng_ppp_prepend(m, &pword, 2); } } /* * Prepend some bytes to an mbuf */ static struct mbuf * ng_ppp_prepend(struct mbuf *m, const void *buf, int len) { M_PREPEND(m, len, M_DONTWAIT); if (m == NULL || (m->m_len < len && (m = m_pullup(m, len)) == NULL)) return (NULL); bcopy(buf, mtod(m, u_char *), len); return (m); } /* * Update private information that is derived from other private information */ static void ng_ppp_update(node_p node, int newConf) { const priv_p priv = NG_NODE_PRIVATE(node); int i; /* Update active status for VJ Compression */ priv->vjCompHooked = priv->hooks[HOOK_INDEX_VJC_IP] != NULL && priv->hooks[HOOK_INDEX_VJC_COMP] != NULL && priv->hooks[HOOK_INDEX_VJC_UNCOMP] != NULL && priv->hooks[HOOK_INDEX_VJC_VJIP] != NULL; /* Increase latency for each link an amount equal to one MP header */ if (newConf) { for (i = 0; i < NG_PPP_MAX_LINKS; i++) { int hdrBytes; hdrBytes = (priv->links[i].conf.enableACFComp ? 0 : 2) + (priv->links[i].conf.enableProtoComp ? 1 : 2) + (priv->conf.xmitShortSeq ? 2 : 4); priv->links[i].conf.latency += ((hdrBytes * priv->links[i].conf.bandwidth) + 50) / 100; } } /* Update list of active links */ bzero(&priv->activeLinks, sizeof(priv->activeLinks)); priv->numActiveLinks = 0; priv->allLinksEqual = 1; for (i = 0; i < NG_PPP_MAX_LINKS; i++) { struct ng_ppp_link *const link = &priv->links[i]; /* Is link active? */ if (link->conf.enableLink && link->hook != NULL) { struct ng_ppp_link *link0; /* Add link to list of active links */ priv->activeLinks[priv->numActiveLinks++] = i; link0 = &priv->links[priv->activeLinks[0]]; /* Determine if all links are still equal */ if (link->conf.latency != link0->conf.latency || link->conf.bandwidth != link0->conf.bandwidth) priv->allLinksEqual = 0; /* Initialize rec'd sequence number */ if (link->seq == MP_NOSEQ) { link->seq = (link == link0) ? MP_INITIAL_SEQ : link0->seq; } } else link->seq = MP_NOSEQ; } /* Update MP state as multi-link is active or not */ if (priv->conf.enableMultilink && priv->numActiveLinks > 0) ng_ppp_start_frag_timer(node); else { ng_ppp_stop_frag_timer(node); ng_ppp_frag_reset(node); priv->xseq = MP_INITIAL_SEQ; priv->mseq = MP_INITIAL_SEQ; for (i = 0; i < NG_PPP_MAX_LINKS; i++) { struct ng_ppp_link *const link = &priv->links[i]; bzero(&link->lastWrite, sizeof(link->lastWrite)); link->bytesInQueue = 0; link->seq = MP_NOSEQ; } } } /* * Determine if a new configuration would represent a valid change * from the current configuration and link activity status. */ static int ng_ppp_config_valid(node_p node, const struct ng_ppp_node_conf *newConf) { const priv_p priv = NG_NODE_PRIVATE(node); int i, newNumLinksActive; /* Check per-link config and count how many links would be active */ for (newNumLinksActive = i = 0; i < NG_PPP_MAX_LINKS; i++) { if (newConf->links[i].enableLink && priv->links[i].hook != NULL) newNumLinksActive++; if (!newConf->links[i].enableLink) continue; if (newConf->links[i].mru < MP_MIN_LINK_MRU) return (0); if (newConf->links[i].bandwidth == 0) return (0); if (newConf->links[i].bandwidth > NG_PPP_MAX_BANDWIDTH) return (0); if (newConf->links[i].latency > NG_PPP_MAX_LATENCY) return (0); } /* Check bundle parameters */ if (newConf->bund.enableMultilink && newConf->bund.mrru < MP_MIN_MRRU) return (0); /* Disallow changes to multi-link configuration while MP is active */ if (priv->numActiveLinks > 0 && newNumLinksActive > 0) { if (!priv->conf.enableMultilink != !newConf->bund.enableMultilink || !priv->conf.xmitShortSeq != !newConf->bund.xmitShortSeq || !priv->conf.recvShortSeq != !newConf->bund.recvShortSeq) return (0); } /* At most one link can be active unless multi-link is enabled */ if (!newConf->bund.enableMultilink && newNumLinksActive > 1) return (0); /* Configuration change would be valid */ return (1); } /* * Free all entries in the fragment queue */ static void ng_ppp_frag_reset(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_ppp_frag *qent, *qnext; for (qent = TAILQ_FIRST(&priv->frags); qent; qent = qnext) { qnext = TAILQ_NEXT(qent, f_qent); NG_FREE_M(qent->data); - NG_FREE_META(qent->meta); FREE(qent, M_NETGRAPH_PPP); } TAILQ_INIT(&priv->frags); priv->qlen = 0; } /* * Start fragment queue timer */ static void ng_ppp_start_frag_timer(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); if (!priv->timerActive) { priv->fragTimer = timeout(ng_ppp_frag_timeout, node, MP_FRAGTIMER_INTERVAL); priv->timerActive = 1; NG_NODE_REF(node); } } /* * Stop fragment queue timer */ static void ng_ppp_stop_frag_timer(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); if (priv->timerActive) { untimeout(ng_ppp_frag_timeout, node, priv->fragTimer); priv->timerActive = 0; KASSERT(node->nd_refs > 1, ("%s: nd_refs=%d", __func__, node->nd_refs)); NG_NODE_UNREF(node); } } Index: head/sys/netgraph/ng_pppoe.c =================================================================== --- head/sys/netgraph/ng_pppoe.c (revision 131154) +++ head/sys/netgraph/ng_pppoe.c (revision 131155) @@ -1,1767 +1,1767 @@ /* * ng_pppoe.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 * * $FreeBSD$ * $Whistle: ng_pppoe.c,v 1.10 1999/11/01 09:24:52 julian Exp $ */ #if 0 #define AAA printf("pppoe: %s\n", __func__ ); #define BBB printf("-%d-", __LINE__ ); #else #define AAA #define BBB #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_PPPOE, "netgraph_pppoe", "netgraph pppoe node"); #else #define M_NETGRAPH_PPPOE M_NETGRAPH #endif #define SIGNOFF "session closed" #define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0)) /* * This section contains the netgraph method declarations for the * pppoe node. These methods define the netgraph pppoe 'type'. */ static ng_constructor_t ng_pppoe_constructor; static ng_rcvmsg_t ng_pppoe_rcvmsg; static ng_shutdown_t ng_pppoe_shutdown; static ng_newhook_t ng_pppoe_newhook; static ng_connect_t ng_pppoe_connect; static ng_rcvdata_t ng_pppoe_rcvdata; static ng_disconnect_t ng_pppoe_disconnect; /* Parse type for struct ngpppoe_init_data */ static const struct ng_parse_struct_field ngpppoe_init_data_type_fields[] = NG_PPPOE_INIT_DATA_TYPE_INFO; static const struct ng_parse_type ngpppoe_init_data_state_type = { &ng_parse_struct_type, &ngpppoe_init_data_type_fields }; /* Parse type for struct ngpppoe_sts */ static const struct ng_parse_struct_field ng_pppoe_sts_type_fields[] = NG_PPPOE_STS_TYPE_INFO; static const struct ng_parse_type ng_pppoe_sts_state_type = { &ng_parse_struct_type, &ng_pppoe_sts_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_pppoe_cmds[] = { { NGM_PPPOE_COOKIE, NGM_PPPOE_CONNECT, "pppoe_connect", &ngpppoe_init_data_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_LISTEN, "pppoe_listen", &ngpppoe_init_data_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_OFFER, "pppoe_offer", &ngpppoe_init_data_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_SERVICE, "pppoe_service", &ngpppoe_init_data_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_SUCCESS, "pppoe_success", &ng_pppoe_sts_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_FAIL, "pppoe_fail", &ng_pppoe_sts_state_type, NULL }, { NGM_PPPOE_COOKIE, NGM_PPPOE_CLOSE, "pppoe_close", &ng_pppoe_sts_state_type, NULL }, { 0 } }; /* Netgraph node type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_PPPOE_NODE_TYPE, .constructor = ng_pppoe_constructor, .rcvmsg = ng_pppoe_rcvmsg, .shutdown = ng_pppoe_shutdown, .newhook = ng_pppoe_newhook, .connect = ng_pppoe_connect, .rcvdata = ng_pppoe_rcvdata, .disconnect = ng_pppoe_disconnect, .cmdlist = ng_pppoe_cmds, }; NETGRAPH_INIT(pppoe, &typestruct); /* Depend on ng_ether so we can use the Ethernet parse type */ MODULE_DEPEND(ng_pppoe, ng_ether, 1, 1, 1); /* * States for the session state machine. * These have no meaning if there is no hook attached yet. */ enum state { PPPOE_SNONE=0, /* [both] Initial state */ PPPOE_LISTENING, /* [Daemon] Listening for discover initiation pkt */ PPPOE_SINIT, /* [Client] Sent discovery initiation */ PPPOE_PRIMED, /* [Server] Awaiting PADI from daemon */ PPPOE_SOFFER, /* [Server] Sent offer message (got PADI)*/ PPPOE_SREQ, /* [Client] Sent a Request */ PPPOE_NEWCONNECTED, /* [Server] Connection established, No data received */ PPPOE_CONNECTED, /* [Both] Connection established, Data received */ PPPOE_DEAD /* [Both] */ }; #define NUMTAGS 20 /* number of tags we are set up to work with */ /* * Information we store for each hook on each node for negotiating the * session. The mbuf and cluster are freed once negotiation has completed. * The whole negotiation block is then discarded. */ struct sess_neg { struct mbuf *m; /* holds cluster with last sent packet */ union packet *pkt; /* points within the above cluster */ struct callout_handle timeout_handle; /* see timeout(9) */ u_int timeout; /* 0,1,2,4,8,16 etc. seconds */ u_int numtags; const struct pppoe_tag *tags[NUMTAGS]; u_int service_len; u_int ac_name_len; struct datatag service; struct datatag ac_name; }; typedef struct sess_neg *negp; /* * Session information that is needed after connection. */ struct sess_con { hook_p hook; u_int16_t Session_ID; enum state state; ng_ID_t creator; /* who to notify */ struct pppoe_full_hdr pkt_hdr; /* used when connected */ negp neg; /* used when negotiating */ /*struct sess_con *hash_next;*/ /* not yet used */ }; typedef struct sess_con *sessp; /* * Information we store for each node */ struct PPPOE { node_p node; /* back pointer to node */ hook_p ethernet_hook; hook_p debug_hook; u_int packets_in; /* packets in from ethernet */ u_int packets_out; /* packets out towards ethernet */ u_int32_t flags; /*struct sess_con *buckets[HASH_SIZE];*/ /* not yet used */ }; typedef struct PPPOE *priv_p; struct ether_header eh_prototype = {{0xff,0xff,0xff,0xff,0xff,0xff}, {0x00,0x00,0x00,0x00,0x00,0x00}, ETHERTYPE_PPPOE_DISC}; #define PPPOE_KEEPSTANDARD -1 /* never switch to nonstandard mode */ #define PPPOE_STANDARD 0 /* try standard mode (dangerous!) */ #define PPPOE_NONSTANDARD 1 /* just be in nonstandard mode */ static int pppoe_mode = PPPOE_KEEPSTANDARD; static int ngpppoe_set_ethertype(SYSCTL_HANDLER_ARGS) { int error; int val; val = pppoe_mode; error = sysctl_handle_int(oidp, &val, sizeof(int), req); if (error != 0 || req->newptr == NULL) return (error); switch (val) { case PPPOE_NONSTANDARD: pppoe_mode = PPPOE_NONSTANDARD; eh_prototype.ether_type = ETHERTYPE_PPPOE_STUPID_DISC; break; case PPPOE_STANDARD: pppoe_mode = PPPOE_STANDARD; eh_prototype.ether_type = ETHERTYPE_PPPOE_DISC; break; case PPPOE_KEEPSTANDARD: pppoe_mode = PPPOE_KEEPSTANDARD; eh_prototype.ether_type = ETHERTYPE_PPPOE_DISC; break; default: return (EINVAL); } return (0); } SYSCTL_PROC(_net_graph, OID_AUTO, nonstandard_pppoe, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(int), ngpppoe_set_ethertype, "I", "select normal or stupid ISP"); union uniq { char bytes[sizeof(void *)]; void * pointer; }; #define LEAVE(x) do { error = x; goto quit; } while(0) static void pppoe_start(sessp sp); static void sendpacket(sessp sp); static void pppoe_ticker(void *arg); static const struct pppoe_tag *scan_tags(sessp sp, const struct pppoe_hdr* ph); static int pppoe_send_event(sessp sp, enum cmd cmdid); /************************************************************************* * Some basic utilities from the Linux version with author's permission.* * Author: Michal Ostrowski * ************************************************************************/ /* * Generate a new session id * XXX find out the FreeBSD locking scheme. */ static u_int16_t get_new_sid(node_p node) { static int pppoe_sid = 10; sessp sp; hook_p hook; u_int16_t val; priv_p privp = NG_NODE_PRIVATE(node); AAA restart: val = pppoe_sid++; /* * Spec says 0xFFFF is reserved. * Also don't use 0x0000 */ if (val == 0xffff) { pppoe_sid = 20; goto restart; } /* Check it isn't already in use */ LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { /* don't check special hooks */ if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook) || (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook)) continue; sp = NG_HOOK_PRIVATE(hook); if (sp->Session_ID == val) goto restart; } return val; } /* * Return the location where the next tag can be put */ static __inline const struct pppoe_tag* next_tag(const struct pppoe_hdr* ph) { return (const struct pppoe_tag*)(((const char*)&ph->tag[0]) + ntohs(ph->length)); } /* * Look for a tag of a specific type * Don't trust any length the other end says. * but assume we already sanity checked ph->length. */ static const struct pppoe_tag* get_tag(const struct pppoe_hdr* ph, u_int16_t idx) { const char *const end = (const char *)next_tag(ph); const char *ptn; const struct pppoe_tag *pt = &ph->tag[0]; /* * Keep processing tags while a tag header will still fit. */ AAA while((const char*)(pt + 1) <= end) { /* * If the tag data would go past the end of the packet, abort. */ ptn = (((const char *)(pt + 1)) + ntohs(pt->tag_len)); if(ptn > end) return NULL; if(pt->tag_type == idx) return pt; pt = (const struct pppoe_tag*)ptn; } return NULL; } /************************************************************************** * inlines to initialise or add tags to a session's tag list, **************************************************************************/ /* * Initialise the session's tag list */ static void init_tags(sessp sp) { AAA if(sp->neg == NULL) { printf("pppoe: asked to init NULL neg pointer\n"); return; } sp->neg->numtags = 0; } static void insert_tag(sessp sp, const struct pppoe_tag *tp) { int i; negp neg; AAA if((neg = sp->neg) == NULL) { printf("pppoe: asked to use NULL neg pointer\n"); return; } if ((i = neg->numtags++) < NUMTAGS) { neg->tags[i] = tp; } else { printf("pppoe: asked to add too many tags to packet\n"); neg->numtags--; } } /* * Make up a packet, using the tags filled out for the session. * * Assume that the actual pppoe header and ethernet header * are filled out externally to this routine. * Also assume that neg->wh points to the correct * location at the front of the buffer space. */ static void make_packet(sessp sp) { struct pppoe_full_hdr *wh = &sp->neg->pkt->pkt_header; const struct pppoe_tag **tag; char *dp; int count; int tlen; u_int16_t length = 0; AAA if ((sp->neg == NULL) || (sp->neg->m == NULL)) { printf("pppoe: make_packet called from wrong state\n"); } dp = (char *)wh->ph.tag; for (count = 0, tag = sp->neg->tags; ((count < sp->neg->numtags) && (count < NUMTAGS)); tag++, count++) { tlen = ntohs((*tag)->tag_len) + sizeof(**tag); if ((length + tlen) > (ETHER_MAX_LEN - 4 - sizeof(*wh))) { printf("pppoe: tags too long\n"); sp->neg->numtags = count; break; /* XXX chop off what's too long */ } bcopy(*tag, (char *)dp, tlen); length += tlen; dp += tlen; } wh->ph.length = htons(length); sp->neg->m->m_len = length + sizeof(*wh); sp->neg->m->m_pkthdr.len = length + sizeof(*wh); } /************************************************************************** * Routine to match a service offered * **************************************************************************/ /* * Find a hook that has a service string that matches that * we are seeking. for now use a simple string. * In the future we may need something like regexp(). * for testing allow a null string to match 1st found and a null service * to match all requests. Also make '*' do the same. */ #define NG_MATCH_EXACT 1 #define NG_MATCH_ANY 2 static hook_p pppoe_match_svc(node_p node, const char *svc_name, int svc_len, int match) { sessp sp = NULL; negp neg = NULL; priv_p privp = NG_NODE_PRIVATE(node); hook_p allhook = NULL; hook_p hook; AAA LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { /* skip any hook that is debug or ethernet */ if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook) || (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook)) continue; sp = NG_HOOK_PRIVATE(hook); /* Skip any sessions which are not in LISTEN mode. */ if ( sp->state != PPPOE_LISTENING) continue; neg = sp->neg; /* Special case for a blank or "*" service name (wildcard) */ if (match == NG_MATCH_ANY && neg->service_len == 1 && neg->service.data[0] == '*') { allhook = hook; continue; } /* If the lengths don't match, that aint it. */ if (neg->service_len != svc_len) continue; /* An exact match? */ if (svc_len == 0) break; if (strncmp(svc_name, neg->service.data, svc_len) == 0) break; } return (hook ? hook : allhook); } /************************************************************************** * Routine to find a particular session that matches an incoming packet * **************************************************************************/ static hook_p pppoe_findsession(node_p node, const struct pppoe_full_hdr *wh) { sessp sp = NULL; hook_p hook = NULL; priv_p privp = NG_NODE_PRIVATE(node); u_int16_t session = ntohs(wh->ph.sid); /* * find matching peer/session combination. */ AAA LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { /* don't check special hooks */ if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook) || (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook)) { continue; } sp = NG_HOOK_PRIVATE(hook); if ( ( (sp->state == PPPOE_CONNECTED) || (sp->state == PPPOE_NEWCONNECTED) ) && (sp->Session_ID == session) && (bcmp(sp->pkt_hdr.eh.ether_dhost, wh->eh.ether_shost, ETHER_ADDR_LEN)) == 0) { break; } } return (hook); } static hook_p pppoe_finduniq(node_p node, const struct pppoe_tag *tag) { hook_p hook = NULL; priv_p privp = NG_NODE_PRIVATE(node); union uniq uniq; AAA bcopy(tag->tag_data, uniq.bytes, sizeof(void *)); /* cycle through all known hooks */ LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { /* don't check special hooks */ if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook) || (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook)) continue; if (uniq.pointer == NG_HOOK_PRIVATE(hook)) break; } return (hook); } /************************************************************************** * start of Netgraph entrypoints * **************************************************************************/ /* * Allocate the private data structure and the generic node * and link them together. * * ng_make_node_common() returns with a generic node struct * with a single reference for us.. we transfer it to the * private structure.. when we free the private struct we must * unref the node so it gets freed too. */ static int ng_pppoe_constructor(node_p node) { priv_p privdata; AAA /* Initialize private descriptor */ MALLOC(privdata, priv_p, sizeof(*privdata), M_NETGRAPH_PPPOE, M_NOWAIT | M_ZERO); if (privdata == NULL) return (ENOMEM); /* Link structs together; this counts as our one reference to *nodep */ NG_NODE_SET_PRIVATE(node, privdata); privdata->node = node; return (0); } /* * Give our ok for a hook to be added... * point the hook's private info to the hook structure. * * The following hook names are special: * Ethernet: the hook that should be connected to a NIC. * debug: copies of data sent out here (when I write the code). * All other hook names need only be unique. (the framework checks this). */ static int ng_pppoe_newhook(node_p node, hook_p hook, const char *name) { const priv_p privp = NG_NODE_PRIVATE(node); sessp sp; AAA if (strcmp(name, NG_PPPOE_HOOK_ETHERNET) == 0) { privp->ethernet_hook = hook; NG_HOOK_SET_PRIVATE(hook, &privp->ethernet_hook); } else if (strcmp(name, NG_PPPOE_HOOK_DEBUG) == 0) { privp->debug_hook = hook; NG_HOOK_SET_PRIVATE(hook, &privp->debug_hook); } else { /* * Any other unique name is OK. * The infrastructure has already checked that it's unique, * so just allocate it and hook it in. */ MALLOC(sp, sessp, sizeof(*sp), M_NETGRAPH_PPPOE, M_NOWAIT | M_ZERO); if (sp == NULL) { return (ENOMEM); } NG_HOOK_SET_PRIVATE(hook, sp); sp->hook = hook; } return(0); } /* * Get a netgraph control message. * Check it is one we understand. If needed, send a response. * We sometimes save the address for an async action later. * Always free the message. */ static int ng_pppoe_rcvmsg(node_p node, item_p item, hook_p lasthook) { priv_p privp = NG_NODE_PRIVATE(node); struct ngpppoe_init_data *ourmsg = NULL; struct ng_mesg *resp = NULL; int error = 0; hook_p hook = NULL; sessp sp = NULL; negp neg = NULL; struct ng_mesg *msg; AAA NGI_GET_MSG(item, msg); /* Deal with message according to cookie and command */ switch (msg->header.typecookie) { case NGM_PPPOE_COOKIE: switch (msg->header.cmd) { case NGM_PPPOE_CONNECT: case NGM_PPPOE_LISTEN: case NGM_PPPOE_OFFER: case NGM_PPPOE_SERVICE: ourmsg = (struct ngpppoe_init_data *)msg->data; if (msg->header.arglen < sizeof(*ourmsg)) { printf("pppoe: init data too small\n"); LEAVE(EMSGSIZE); } if (msg->header.arglen - sizeof(*ourmsg) > PPPOE_SERVICE_NAME_SIZE) { printf("pppoe_rcvmsg: service name too big"); LEAVE(EMSGSIZE); } if (msg->header.arglen - sizeof(*ourmsg) < ourmsg->data_len) { printf("pppoe: init data has bad length," " %d should be %zd\n", ourmsg->data_len, msg->header.arglen - sizeof (*ourmsg)); LEAVE(EMSGSIZE); } /* make sure strcmp will terminate safely */ ourmsg->hook[sizeof(ourmsg->hook) - 1] = '\0'; /* cycle through all known hooks */ LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { if (NG_HOOK_NAME(hook) && strcmp(NG_HOOK_NAME(hook), ourmsg->hook) == 0) break; } if (hook == NULL) { LEAVE(ENOENT); } if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook) || (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook)) { LEAVE(EINVAL); } sp = NG_HOOK_PRIVATE(hook); if (msg->header.cmd == NGM_PPPOE_LISTEN) { /* * Ensure we aren't already listening for this * service. */ if (pppoe_match_svc(node, ourmsg->data, ourmsg->data_len, NG_MATCH_EXACT) != NULL) { LEAVE(EEXIST); } } /* * PPPOE_SERVICE advertisments are set up * on sessions that are in PRIMED state. */ if (msg->header.cmd == NGM_PPPOE_SERVICE) { break; } if (sp->state |= PPPOE_SNONE) { printf("pppoe: Session already active\n"); LEAVE(EISCONN); } /* * set up prototype header */ MALLOC(neg, negp, sizeof(*neg), M_NETGRAPH_PPPOE, M_NOWAIT | M_ZERO); if (neg == NULL) { printf("pppoe: Session out of memory\n"); LEAVE(ENOMEM); } MGETHDR(neg->m, M_DONTWAIT, MT_DATA); if(neg->m == NULL) { printf("pppoe: Session out of mbufs\n"); FREE(neg, M_NETGRAPH_PPPOE); LEAVE(ENOBUFS); } neg->m->m_pkthdr.rcvif = NULL; MCLGET(neg->m, M_DONTWAIT); if ((neg->m->m_flags & M_EXT) == 0) { printf("pppoe: Session out of mcls\n"); m_freem(neg->m); FREE(neg, M_NETGRAPH_PPPOE); LEAVE(ENOBUFS); } sp->neg = neg; callout_handle_init( &neg->timeout_handle); neg->m->m_len = sizeof(struct pppoe_full_hdr); neg->pkt = mtod(neg->m, union packet*); neg->pkt->pkt_header.eh = eh_prototype; neg->pkt->pkt_header.ph.ver = 0x1; neg->pkt->pkt_header.ph.type = 0x1; neg->pkt->pkt_header.ph.sid = 0x0000; neg->timeout = 0; sp->creator = NGI_RETADDR(item); } switch (msg->header.cmd) { case NGM_PPPOE_GET_STATUS: { struct ngpppoestat *stats; NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); if (!resp) { LEAVE(ENOMEM); } stats = (struct ngpppoestat *) resp->data; stats->packets_in = privp->packets_in; stats->packets_out = privp->packets_out; break; } case NGM_PPPOE_CONNECT: /* * Check the hook exists and is Uninitialised. * Send a PADI request, and start the timeout logic. * Store the originator of this message so we can send * a success of fail message to them later. * Move the session to SINIT * Set up the session to the correct state and * start it. */ neg->service.hdr.tag_type = PTT_SRV_NAME; neg->service.hdr.tag_len = htons((u_int16_t)ourmsg->data_len); if (ourmsg->data_len) bcopy(ourmsg->data, neg->service.data, ourmsg->data_len); neg->service_len = ourmsg->data_len; pppoe_start(sp); break; case NGM_PPPOE_LISTEN: /* * Check the hook exists and is Uninitialised. * Install the service matching string. * Store the originator of this message so we can send * a success of fail message to them later. * Move the hook to 'LISTENING' */ neg->service.hdr.tag_type = PTT_SRV_NAME; neg->service.hdr.tag_len = htons((u_int16_t)ourmsg->data_len); if (ourmsg->data_len) bcopy(ourmsg->data, neg->service.data, ourmsg->data_len); neg->service_len = ourmsg->data_len; neg->pkt->pkt_header.ph.code = PADT_CODE; /* * wait for PADI packet coming from ethernet */ sp->state = PPPOE_LISTENING; break; case NGM_PPPOE_OFFER: /* * Check the hook exists and is Uninitialised. * Store the originator of this message so we can send * a success of fail message to them later. * Store the AC-Name given and go to PRIMED. */ neg->ac_name.hdr.tag_type = PTT_AC_NAME; neg->ac_name.hdr.tag_len = htons((u_int16_t)ourmsg->data_len); if (ourmsg->data_len) bcopy(ourmsg->data, neg->ac_name.data, ourmsg->data_len); neg->ac_name_len = ourmsg->data_len; neg->pkt->pkt_header.ph.code = PADO_CODE; /* * Wait for PADI packet coming from hook */ sp->state = PPPOE_PRIMED; break; case NGM_PPPOE_SERVICE: /* * Check the session is primed. * for now just allow ONE service to be advertised. * If you do it twice you just overwrite. */ if (sp->state != PPPOE_PRIMED) { printf("pppoe: Session not primed\n"); LEAVE(EISCONN); } neg = sp->neg; neg->service.hdr.tag_type = PTT_SRV_NAME; neg->service.hdr.tag_len = htons((u_int16_t)ourmsg->data_len); if (ourmsg->data_len) bcopy(ourmsg->data, neg->service.data, ourmsg->data_len); neg->service_len = ourmsg->data_len; break; default: LEAVE(EINVAL); } break; default: LEAVE(EINVAL); } /* Take care of synchronous response, if any */ quit: NG_RESPOND_MSG(error, node, item, resp); /* Free the message and return */ NG_FREE_MSG(msg); return(error); } /* * Start a client into the first state. A separate function because * it can be needed if the negotiation times out. */ static void pppoe_start(sessp sp) { struct { struct pppoe_tag hdr; union uniq data; } __packed uniqtag; /* * kick the state machine into starting up */ AAA sp->state = PPPOE_SINIT; /* reset the packet header to broadcast */ sp->neg->pkt->pkt_header.eh = eh_prototype; sp->neg->pkt->pkt_header.ph.code = PADI_CODE; uniqtag.hdr.tag_type = PTT_HOST_UNIQ; uniqtag.hdr.tag_len = htons((u_int16_t)sizeof(uniqtag.data)); uniqtag.data.pointer = sp; init_tags(sp); insert_tag(sp, &uniqtag.hdr); insert_tag(sp, &sp->neg->service.hdr); make_packet(sp); sendpacket(sp); } static int send_acname(sessp sp, const struct pppoe_tag *tag) { int error, tlen; struct ng_mesg *msg; struct ngpppoe_sts *sts; NG_MKMESSAGE(msg, NGM_PPPOE_COOKIE, NGM_PPPOE_ACNAME, sizeof(struct ngpppoe_sts), M_NOWAIT); if (msg == NULL) return (ENOMEM); sts = (struct ngpppoe_sts *)msg->data; tlen = min(NG_HOOKSIZ - 1, ntohs(tag->tag_len)); strncpy(sts->hook, tag->tag_data, tlen); sts->hook[tlen] = '\0'; NG_SEND_MSG_ID(error, NG_HOOK_NODE(sp->hook), msg, sp->creator, 0); return (error); } static int send_sessionid(sessp sp) { int error; struct ng_mesg *msg; NG_MKMESSAGE(msg, NGM_PPPOE_COOKIE, NGM_PPPOE_SESSIONID, sizeof(u_int16_t), M_NOWAIT); if (msg == NULL) return (ENOMEM); *(u_int16_t *)msg->data = sp->Session_ID; NG_SEND_MSG_ID(error, NG_HOOK_NODE(sp->hook), msg, sp->creator, 0); return (error); } /* * Receive data, and do something with it. - * The caller will never free m or meta, so - * if we use up this data or abort we must free BOTH of these. + * The caller will never free m, so if we use up this data + * or abort we must free it. */ static int ng_pppoe_rcvdata(hook_p hook, item_p item) { node_p node = NG_HOOK_NODE(hook); const priv_p privp = NG_NODE_PRIVATE(node); sessp sp = NG_HOOK_PRIVATE(hook); const struct pppoe_full_hdr *wh; const struct pppoe_hdr *ph; int error = 0; u_int16_t session; u_int16_t length; u_int8_t code; const struct pppoe_tag *utag = NULL, *tag = NULL; hook_p sendhook; struct { struct pppoe_tag hdr; union uniq data; } __packed uniqtag; negp neg = NULL; struct mbuf *m; AAA NGI_GET_M(item, m); if (NG_HOOK_PRIVATE(hook) == &privp->debug_hook) { /* * Data from the debug hook gets sent without modification * straight to the ethernet. */ NG_FWD_ITEM_HOOK( error, item, privp->ethernet_hook); privp->packets_out++; } else if (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook) { /* * Incoming data. * Dig out various fields from the packet. * use them to decide where to send it. */ privp->packets_in++; if( m->m_len < sizeof(*wh)) { m = m_pullup(m, sizeof(*wh)); /* Checks length */ if (m == NULL) { printf("couldn't m_pullup\n"); LEAVE(ENOBUFS); } } wh = mtod(m, struct pppoe_full_hdr *); length = ntohs(wh->ph.length); switch(wh->eh.ether_type) { case ETHERTYPE_PPPOE_STUPID_DISC: if (pppoe_mode == PPPOE_STANDARD) { pppoe_mode = PPPOE_NONSTANDARD; eh_prototype.ether_type = ETHERTYPE_PPPOE_STUPID_DISC; log(LOG_NOTICE, "Switched to nonstandard PPPoE mode due to " "packet from %*D\n", ETHER_ADDR_LEN, wh->eh.ether_shost, ":"); } else if (pppoe_mode == PPPOE_KEEPSTANDARD) log(LOG_NOTICE, "Ignored nonstandard PPPoE packet " "from %*D\n", ETHER_ADDR_LEN, wh->eh.ether_shost, ":"); /* fall through */ case ETHERTYPE_PPPOE_DISC: /* * We need to try to make sure that the tag area * is contiguous, or we could wander off the end * of a buffer and make a mess. * (Linux wouldn't have this problem). */ if (m->m_pkthdr.len <= MHLEN) { if( m->m_len < m->m_pkthdr.len) { m = m_pullup(m, m->m_pkthdr.len); if (m == NULL) { printf("couldn't m_pullup\n"); LEAVE(ENOBUFS); } } } if (m->m_len != m->m_pkthdr.len) { /* * It's not all in one piece. * We need to do extra work. * Put it into a cluster. */ struct mbuf *n; n = m_dup(m, M_DONTWAIT); m_freem(m); m = n; if (m) { /* just check we got a cluster */ if (m->m_len != m->m_pkthdr.len) { m_freem(m); m = NULL; } } if (m == NULL) { printf("packet fragmented\n"); LEAVE(EMSGSIZE); } } wh = mtod(m, struct pppoe_full_hdr *); length = ntohs(wh->ph.length); ph = &wh->ph; session = ntohs(wh->ph.sid); code = wh->ph.code; switch(code) { case PADI_CODE: /* * We are a server: * Look for a hook with the required service * and send the ENTIRE packet up there. * It should come back to a new hook in * PRIMED state. Look there for further * processing. */ tag = get_tag(ph, PTT_SRV_NAME); if (tag == NULL) { printf("no service tag\n"); LEAVE(ENETUNREACH); } sendhook = pppoe_match_svc(NG_HOOK_NODE(hook), tag->tag_data, ntohs(tag->tag_len), NG_MATCH_ANY); if (sendhook) { NG_FWD_NEW_DATA(error, item, sendhook, m); } else { LEAVE(ENETUNREACH); } break; case PADO_CODE: /* * We are a client: * Use the host_uniq tag to find the * hook this is in response to. * Received #2, now send #3 * For now simply accept the first we receive. */ utag = get_tag(ph, PTT_HOST_UNIQ); if ((utag == NULL) || (ntohs(utag->tag_len) != sizeof(sp))) { printf("no host unique field\n"); LEAVE(ENETUNREACH); } sendhook = pppoe_finduniq(node, utag); if (sendhook == NULL) { printf("no matching session\n"); LEAVE(ENETUNREACH); } /* * Check the session is in the right state. * It needs to be in PPPOE_SINIT. */ sp = NG_HOOK_PRIVATE(sendhook); if (sp->state != PPPOE_SINIT) { printf("session in wrong state\n"); LEAVE(ENETUNREACH); } neg = sp->neg; untimeout(pppoe_ticker, sendhook, neg->timeout_handle); /* * This is the first time we hear * from the server, so note it's * unicast address, replacing the * broadcast address . */ bcopy(wh->eh.ether_shost, neg->pkt->pkt_header.eh.ether_dhost, ETHER_ADDR_LEN); neg->timeout = 0; neg->pkt->pkt_header.ph.code = PADR_CODE; init_tags(sp); insert_tag(sp, utag); /* Host Unique */ if ((tag = get_tag(ph, PTT_AC_COOKIE))) insert_tag(sp, tag); /* return cookie */ if ((tag = get_tag(ph, PTT_AC_NAME))) { insert_tag(sp, tag); /* return it */ send_acname(sp, tag); } insert_tag(sp, &neg->service.hdr); /* Service */ scan_tags(sp, ph); make_packet(sp); sp->state = PPPOE_SREQ; sendpacket(sp); break; case PADR_CODE: /* * We are a server: * Use the ac_cookie tag to find the * hook this is in response to. */ utag = get_tag(ph, PTT_AC_COOKIE); if ((utag == NULL) || (ntohs(utag->tag_len) != sizeof(sp))) { LEAVE(ENETUNREACH); } sendhook = pppoe_finduniq(node, utag); if (sendhook == NULL) { LEAVE(ENETUNREACH); } /* * Check the session is in the right state. * It needs to be in PPPOE_SOFFER * or PPPOE_NEWCONNECTED. If the latter, * then this is a retry by the client. * so be nice, and resend. */ sp = NG_HOOK_PRIVATE(sendhook); if (sp->state == PPPOE_NEWCONNECTED) { /* * Whoa! drop back to resend that * PADS packet. * We should still have a copy of it. */ sp->state = PPPOE_SOFFER; } if (sp->state != PPPOE_SOFFER) { LEAVE (ENETUNREACH); break; } neg = sp->neg; untimeout(pppoe_ticker, sendhook, neg->timeout_handle); neg->pkt->pkt_header.ph.code = PADS_CODE; if (sp->Session_ID == 0) neg->pkt->pkt_header.ph.sid = htons(sp->Session_ID = get_new_sid(node)); send_sessionid(sp); neg->timeout = 0; /* * start working out the tags to respond with. */ init_tags(sp); insert_tag(sp, &neg->ac_name.hdr); /* AC_NAME */ if ((tag = get_tag(ph, PTT_SRV_NAME))) insert_tag(sp, tag);/* return service */ if ((tag = get_tag(ph, PTT_HOST_UNIQ))) insert_tag(sp, tag); /* return it */ insert_tag(sp, utag); /* ac_cookie */ scan_tags(sp, ph); make_packet(sp); sp->state = PPPOE_NEWCONNECTED; sendpacket(sp); /* * Having sent the last Negotiation header, * Set up the stored packet header to * be correct for the actual session. * But keep the negotialtion stuff * around in case we need to resend this last * packet. We'll discard it when we move * from NEWCONNECTED to CONNECTED */ sp->pkt_hdr = neg->pkt->pkt_header; if (pppoe_mode == PPPOE_NONSTANDARD) sp->pkt_hdr.eh.ether_type = ETHERTYPE_PPPOE_STUPID_SESS; else sp->pkt_hdr.eh.ether_type = ETHERTYPE_PPPOE_SESS; sp->pkt_hdr.ph.code = 0; pppoe_send_event(sp, NGM_PPPOE_SUCCESS); break; case PADS_CODE: /* * We are a client: * Use the host_uniq tag to find the * hook this is in response to. * take the session ID and store it away. * Also make sure the pre-made header is * correct and set us into Session mode. */ utag = get_tag(ph, PTT_HOST_UNIQ); if ((utag == NULL) || (ntohs(utag->tag_len) != sizeof(sp))) { LEAVE (ENETUNREACH); break; } sendhook = pppoe_finduniq(node, utag); if (sendhook == NULL) { LEAVE(ENETUNREACH); } /* * Check the session is in the right state. * It needs to be in PPPOE_SREQ. */ sp = NG_HOOK_PRIVATE(sendhook); if (sp->state != PPPOE_SREQ) { LEAVE(ENETUNREACH); } neg = sp->neg; untimeout(pppoe_ticker, sendhook, neg->timeout_handle); neg->pkt->pkt_header.ph.sid = wh->ph.sid; sp->Session_ID = ntohs(wh->ph.sid); send_sessionid(sp); neg->timeout = 0; sp->state = PPPOE_CONNECTED; /* * Now we have gone to Connected mode, * Free all resources needed for * negotiation. * Keep a copy of the header we will be using. */ sp->pkt_hdr = neg->pkt->pkt_header; if (pppoe_mode == PPPOE_NONSTANDARD) sp->pkt_hdr.eh.ether_type = ETHERTYPE_PPPOE_STUPID_SESS; else sp->pkt_hdr.eh.ether_type = ETHERTYPE_PPPOE_SESS; sp->pkt_hdr.ph.code = 0; m_freem(neg->m); FREE(sp->neg, M_NETGRAPH_PPPOE); sp->neg = NULL; pppoe_send_event(sp, NGM_PPPOE_SUCCESS); break; case PADT_CODE: /* * Send a 'close' message to the controlling * process (the one that set us up); * And then tear everything down. * * Find matching peer/session combination. */ sendhook = pppoe_findsession(node, wh); if (sendhook == NULL) { LEAVE(ENETUNREACH); } /* send message to creator */ /* close hook */ if (sendhook) { ng_rmhook_self(sendhook); } break; default: LEAVE(EPFNOSUPPORT); } break; case ETHERTYPE_PPPOE_STUPID_SESS: case ETHERTYPE_PPPOE_SESS: /* * find matching peer/session combination. */ sendhook = pppoe_findsession(node, wh); if (sendhook == NULL) { LEAVE (ENETUNREACH); break; } sp = NG_HOOK_PRIVATE(sendhook); m_adj(m, sizeof(*wh)); if (m->m_pkthdr.len < length) { /* Packet too short, dump it */ LEAVE(EMSGSIZE); } /* Also need to trim excess at the end */ if (m->m_pkthdr.len > length) { m_adj(m, -((int)(m->m_pkthdr.len - length))); } if ( sp->state != PPPOE_CONNECTED) { if (sp->state == PPPOE_NEWCONNECTED) { sp->state = PPPOE_CONNECTED; /* * Now we have gone to Connected mode, * Free all resources needed for * negotiation. Be paranoid about * whether there may be a timeout. */ m_freem(sp->neg->m); untimeout(pppoe_ticker, sendhook, sp->neg->timeout_handle); FREE(sp->neg, M_NETGRAPH_PPPOE); sp->neg = NULL; } else { LEAVE (ENETUNREACH); break; } } NG_FWD_NEW_DATA( error, item, sendhook, m); break; default: LEAVE(EPFNOSUPPORT); } } else { /* * Not ethernet or debug hook.. * * The packet has come in on a normal hook. * We need to find out what kind of hook, * So we can decide how to handle it. * Check the hook's state. */ sp = NG_HOOK_PRIVATE(hook); switch (sp->state) { case PPPOE_NEWCONNECTED: case PPPOE_CONNECTED: { static const u_char addrctrl[] = { 0xff, 0x03 }; struct pppoe_full_hdr *wh; /* * Remove PPP address and control fields, if any. * For example, ng_ppp(4) always sends LCP packets * with address and control fields as required by * generic PPP. PPPoE is an exception to the rule. */ if (m->m_pkthdr.len >= 2) { if (m->m_len < 2 && !(m = m_pullup(m, 2))) LEAVE(ENOBUFS); if (bcmp(mtod(m, u_char *), addrctrl, 2) == 0) m_adj(m, 2); } /* * Bang in a pre-made header, and set the length up * to be correct. Then send it to the ethernet driver. * But first correct the length. */ sp->pkt_hdr.ph.length = htons((short)(m->m_pkthdr.len)); M_PREPEND(m, sizeof(*wh), M_DONTWAIT); if (m == NULL) { LEAVE(ENOBUFS); } wh = mtod(m, struct pppoe_full_hdr *); bcopy(&sp->pkt_hdr, wh, sizeof(*wh)); NG_FWD_NEW_DATA( error, item, privp->ethernet_hook, m); privp->packets_out++; break; } case PPPOE_PRIMED: /* * A PADI packet is being returned by the application * that has set up this hook. This indicates that it * wants us to offer service. */ neg = sp->neg; if (m->m_len < sizeof(*wh)) { m = m_pullup(m, sizeof(*wh)); if (m == NULL) { LEAVE(ENOBUFS); } } wh = mtod(m, struct pppoe_full_hdr *); ph = &wh->ph; session = ntohs(wh->ph.sid); length = ntohs(wh->ph.length); code = wh->ph.code; if ( code != PADI_CODE) { LEAVE(EINVAL); }; untimeout(pppoe_ticker, hook, neg->timeout_handle); /* * This is the first time we hear * from the client, so note it's * unicast address, replacing the * broadcast address. */ bcopy(wh->eh.ether_shost, neg->pkt->pkt_header.eh.ether_dhost, ETHER_ADDR_LEN); sp->state = PPPOE_SOFFER; neg->timeout = 0; neg->pkt->pkt_header.ph.code = PADO_CODE; /* * start working out the tags to respond with. */ uniqtag.hdr.tag_type = PTT_AC_COOKIE; uniqtag.hdr.tag_len = htons((u_int16_t)sizeof(sp)); uniqtag.data.pointer = sp; init_tags(sp); insert_tag(sp, &neg->ac_name.hdr); /* AC_NAME */ if ((tag = get_tag(ph, PTT_SRV_NAME))) insert_tag(sp, tag); /* return service */ /* * If we have a NULL service request * and have an extra service defined in this hook, * then also add a tag for the extra service. * XXX this is a hack. eventually we should be able * to support advertising many services, not just one */ if (((tag == NULL) || (tag->tag_len == 0)) && (neg->service.hdr.tag_len != 0)) { insert_tag(sp, &neg->service.hdr); /* SERVICE */ } if ((tag = get_tag(ph, PTT_HOST_UNIQ))) insert_tag(sp, tag); /* returned hostunique */ insert_tag(sp, &uniqtag.hdr); scan_tags(sp, ph); make_packet(sp); sendpacket(sp); break; /* * Packets coming from the hook make no sense * to sessions in these states. Throw them away. */ case PPPOE_SINIT: case PPPOE_SREQ: case PPPOE_SOFFER: case PPPOE_SNONE: case PPPOE_LISTENING: case PPPOE_DEAD: default: LEAVE(ENETUNREACH); } } quit: if (item) NG_FREE_ITEM(item); NG_FREE_M(m); return error; } /* * Do local shutdown processing.. * If we are a persistant device, we might refuse to go away, and * we'd only remove our links and reset ourself. */ static int ng_pppoe_shutdown(node_p node) { const priv_p privdata = NG_NODE_PRIVATE(node); AAA NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(privdata->node); FREE(privdata, M_NETGRAPH_PPPOE); return (0); } /* * This is called once we've already connected a new hook to the other node. * It gives us a chance to balk at the last minute. */ static int ng_pppoe_connect(hook_p hook) { /* be really amiable and just say "YUP that's OK by me! " */ return (0); } /* * Hook disconnection * * Clean up all dangling links and information about the session/hook. * For this type, removal of the last link destroys the node */ static int ng_pppoe_disconnect(hook_p hook) { node_p node = NG_HOOK_NODE(hook); priv_p privp = NG_NODE_PRIVATE(node); sessp sp; int hooks; AAA hooks = NG_NODE_NUMHOOKS(node); /* this one already not counted */ if (NG_HOOK_PRIVATE(hook) == &privp->debug_hook) { privp->debug_hook = NULL; } else if (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook) { privp->ethernet_hook = NULL; if (NG_NODE_IS_VALID(node)) ng_rmnode_self(node); } else { sp = NG_HOOK_PRIVATE(hook); if (sp->state != PPPOE_SNONE ) { pppoe_send_event(sp, NGM_PPPOE_CLOSE); } /* * According to the spec, if we are connected, * we should send a DISC packet if we are shutting down * a session. */ if ((privp->ethernet_hook) && ((sp->state == PPPOE_CONNECTED) || (sp->state == PPPOE_NEWCONNECTED))) { struct mbuf *m; struct pppoe_full_hdr *wh; struct pppoe_tag *tag; int msglen = strlen(SIGNOFF); int error = 0; /* revert the stored header to DISC/PADT mode */ wh = &sp->pkt_hdr; wh->ph.code = PADT_CODE; if (pppoe_mode == PPPOE_NONSTANDARD) wh->eh.ether_type = ETHERTYPE_PPPOE_STUPID_DISC; else wh->eh.ether_type = ETHERTYPE_PPPOE_DISC; /* generate a packet of that type */ MGETHDR(m, M_DONTWAIT, MT_DATA); if(m == NULL) printf("pppoe: Session out of mbufs\n"); else { m->m_pkthdr.rcvif = NULL; m->m_pkthdr.len = m->m_len = sizeof(*wh); bcopy((caddr_t)wh, mtod(m, caddr_t), sizeof(*wh)); /* * Add a General error message and adjust * sizes */ wh = mtod(m, struct pppoe_full_hdr *); tag = wh->ph.tag; tag->tag_type = PTT_GEN_ERR; tag->tag_len = htons((u_int16_t)msglen); strncpy(tag->tag_data, SIGNOFF, msglen); m->m_pkthdr.len = (m->m_len += sizeof(*tag) + msglen); wh->ph.length = htons(sizeof(*tag) + msglen); NG_SEND_DATA_ONLY(error, privp->ethernet_hook, m); } } /* * As long as we have somewhere to store the timeout handle, * we may have a timeout pending.. get rid of it. */ if (sp->neg) { untimeout(pppoe_ticker, hook, sp->neg->timeout_handle); if (sp->neg->m) m_freem(sp->neg->m); FREE(sp->neg, M_NETGRAPH_PPPOE); } FREE(sp, M_NETGRAPH_PPPOE); NG_HOOK_SET_PRIVATE(hook, NULL); /* work out how many session hooks there are */ /* Node goes away on last session hook removal */ if (privp->ethernet_hook) hooks -= 1; if (privp->debug_hook) hooks -= 1; } if ((NG_NODE_NUMHOOKS(node) == 0) && (NG_NODE_IS_VALID(node))) ng_rmnode_self(node); return (0); } /* * timeouts come here. */ static void pppoe_ticker(void *arg) { int s = splnet(); hook_p hook = arg; sessp sp = NG_HOOK_PRIVATE(hook); negp neg = sp->neg; int error = 0; struct mbuf *m0 = NULL; priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); AAA switch(sp->state) { /* * resend the last packet, using an exponential backoff. * After a period of time, stop growing the backoff, * and either leave it, or revert to the start. */ case PPPOE_SINIT: case PPPOE_SREQ: /* timeouts on these produce resends */ m0 = m_copypacket(sp->neg->m, M_DONTWAIT); NG_SEND_DATA_ONLY( error, privp->ethernet_hook, m0); neg->timeout_handle = timeout(pppoe_ticker, hook, neg->timeout * hz); if ((neg->timeout <<= 1) > PPPOE_TIMEOUT_LIMIT) { if (sp->state == PPPOE_SREQ) { /* revert to SINIT mode */ pppoe_start(sp); } else { neg->timeout = PPPOE_TIMEOUT_LIMIT; } } break; case PPPOE_PRIMED: case PPPOE_SOFFER: /* a timeout on these says "give up" */ ng_rmhook_self(hook); break; default: /* timeouts have no meaning in other states */ printf("pppoe: unexpected timeout\n"); } splx(s); } static void sendpacket(sessp sp) { int error = 0; struct mbuf *m0 = NULL; hook_p hook = sp->hook; negp neg = sp->neg; priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); AAA switch(sp->state) { case PPPOE_LISTENING: case PPPOE_DEAD: case PPPOE_SNONE: case PPPOE_CONNECTED: printf("pppoe: sendpacket: unexpected state\n"); break; case PPPOE_NEWCONNECTED: /* send the PADS without a timeout - we're now connected */ m0 = m_copypacket(sp->neg->m, M_DONTWAIT); NG_SEND_DATA_ONLY( error, privp->ethernet_hook, m0); break; case PPPOE_PRIMED: /* No packet to send, but set up the timeout */ neg->timeout_handle = timeout(pppoe_ticker, hook, PPPOE_OFFER_TIMEOUT * hz); break; case PPPOE_SOFFER: /* * send the offer but if they don't respond * in PPPOE_OFFER_TIMEOUT seconds, forget about it. */ m0 = m_copypacket(sp->neg->m, M_DONTWAIT); NG_SEND_DATA_ONLY( error, privp->ethernet_hook, m0); neg->timeout_handle = timeout(pppoe_ticker, hook, PPPOE_OFFER_TIMEOUT * hz); break; case PPPOE_SINIT: case PPPOE_SREQ: m0 = m_copypacket(sp->neg->m, M_DONTWAIT); NG_SEND_DATA_ONLY( error, privp->ethernet_hook, m0); neg->timeout_handle = timeout(pppoe_ticker, hook, (hz * PPPOE_INITIAL_TIMEOUT)); neg->timeout = PPPOE_INITIAL_TIMEOUT * 2; break; default: error = EINVAL; printf("pppoe: timeout: bad state\n"); } /* return (error); */ } /* * Parse an incoming packet to see if any tags should be copied to the * output packet. Don't do any tags that have been handled in the main * state machine. */ static const struct pppoe_tag* scan_tags(sessp sp, const struct pppoe_hdr* ph) { const char *const end = (const char *)next_tag(ph); const char *ptn; const struct pppoe_tag *pt = &ph->tag[0]; /* * Keep processing tags while a tag header will still fit. */ AAA while((const char*)(pt + 1) <= end) { /* * If the tag data would go past the end of the packet, abort. */ ptn = (((const char *)(pt + 1)) + ntohs(pt->tag_len)); if(ptn > end) return NULL; switch (pt->tag_type) { case PTT_RELAY_SID: insert_tag(sp, pt); break; case PTT_EOL: return NULL; case PTT_SRV_NAME: case PTT_AC_NAME: case PTT_HOST_UNIQ: case PTT_AC_COOKIE: case PTT_VENDOR: case PTT_SRV_ERR: case PTT_SYS_ERR: case PTT_GEN_ERR: break; } pt = (const struct pppoe_tag*)ptn; } return NULL; } static int pppoe_send_event(sessp sp, enum cmd cmdid) { int error; struct ng_mesg *msg; struct ngpppoe_sts *sts; AAA NG_MKMESSAGE(msg, NGM_PPPOE_COOKIE, cmdid, sizeof(struct ngpppoe_sts), M_NOWAIT); if (msg == NULL) return (ENOMEM); sts = (struct ngpppoe_sts *)msg->data; strncpy(sts->hook, NG_HOOK_NAME(sp->hook), NG_HOOKSIZ); NG_SEND_MSG_ID(error, NG_HOOK_NODE(sp->hook), msg, sp->creator, 0); return (error); } Index: head/sys/netgraph/ng_sample.c =================================================================== --- head/sys/netgraph/ng_sample.c (revision 131154) +++ head/sys/netgraph/ng_sample.c (revision 131155) @@ -1,498 +1,498 @@ /* * ng_sample.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 * * $FreeBSD$ * $Whistle: ng_sample.c,v 1.13 1999/11/01 09:24:52 julian Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include /* If you do complicated mallocs you may want to do this */ /* and use it for your mallocs */ #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_XXX, "netgraph_xxx", "netgraph xxx node "); #else #define M_NETGRAPH_XXX M_NETGRAPH #endif /* * This section contains the netgraph method declarations for the * sample node. These methods define the netgraph 'type'. */ static ng_constructor_t ng_xxx_constructor; static ng_rcvmsg_t ng_xxx_rcvmsg; static ng_shutdown_t ng_xxx_shutdown; static ng_newhook_t ng_xxx_newhook; static ng_connect_t ng_xxx_connect; static ng_rcvdata_t ng_xxx_rcvdata; /* note these are both ng_rcvdata_t */ static ng_disconnect_t ng_xxx_disconnect; /* Parse type for struct ngxxxstat */ static const struct ng_parse_struct_field ng_xxx_stat_type_fields[] = NG_XXX_STATS_TYPE_INFO; static const struct ng_parse_type ng_xxx_stat_type = { &ng_parse_struct_type, &ng_xxx_stat_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_xxx_cmdlist[] = { { NGM_XXX_COOKIE, NGM_XXX_GET_STATUS, "getstatus", NULL, &ng_xxx_stat_type, }, { NGM_XXX_COOKIE, NGM_XXX_SET_FLAG, "setflag", &ng_parse_int32_type, NULL }, { 0 } }; /* Netgraph node type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_XXX_NODE_TYPE, .constructor = ng_xxx_constructor, .rcvmsg = ng_xxx_rcvmsg, .shutdown = ng_xxx_shutdown, .newhook = ng_xxx_newhook, /* .findhook = ng_xxx_findhook, */ .connect = ng_xxx_connect, .rcvdata = ng_xxx_rcvdata, .disconnect = ng_xxx_disconnect, .cmdlist = ng_xxx_cmdlist, }; NETGRAPH_INIT(xxx, &typestruct); /* Information we store for each hook on each node */ struct XXX_hookinfo { int dlci; /* The DLCI it represents, -1 == downstream */ int channel; /* The channel representing this DLCI */ hook_p hook; }; /* Information we store for each node */ struct XXX { struct XXX_hookinfo channel[XXX_NUM_DLCIS]; struct XXX_hookinfo downstream_hook; node_p node; /* back pointer to node */ hook_p debughook; u_int packets_in; /* packets in from downstream */ u_int packets_out; /* packets out towards downstream */ u_int32_t flags; }; typedef struct XXX *xxx_p; /* * Allocate the private data structure. The generic node has already * been created. Link them together. We arrive with a reference to the node * i.e. the reference count is incremented for us already. * * If this were a device node than this work would be done in the attach() * routine and the constructor would return EINVAL as you should not be able * to creatednodes that depend on hardware (unless you can add the hardware :) */ static int ng_xxx_constructor(node_p node) { xxx_p privdata; int i; /* Initialize private descriptor */ MALLOC(privdata, xxx_p, sizeof(*privdata), M_NETGRAPH, M_NOWAIT | M_ZERO); if (privdata == NULL) return (ENOMEM); for (i = 0; i < XXX_NUM_DLCIS; i++) { privdata->channel[i].dlci = -2; privdata->channel[i].channel = i; } /* Link structs together; this counts as our one reference to *nodep */ NG_NODE_SET_PRIVATE(node, privdata); privdata->node = node; return (0); } /* * Give our ok for a hook to be added... * If we are not running this might kick a device into life. * Possibly decode information out of the hook name. * Add the hook's private info to the hook structure. * (if we had some). In this example, we assume that there is a * an array of structs, called 'channel' in the private info, * one for each active channel. The private * pointer of each hook points to the appropriate XXX_hookinfo struct * so that the source of an input packet is easily identified. * (a dlci is a frame relay channel) */ static int ng_xxx_newhook(node_p node, hook_p hook, const char *name) { const xxx_p xxxp = NG_NODE_PRIVATE(node); const char *cp; int dlci = 0; int chan; #if 0 /* Possibly start up the device if it's not already going */ if ((xxxp->flags & SCF_RUNNING) == 0) { ng_xxx_start_hardware(xxxp); } #endif /* Example of how one might use hooks with embedded numbers: All * hooks start with 'dlci' and have a decimal trailing channel * number up to 4 digits Use the leadin defined int he associated .h * file. */ if (strncmp(name, NG_XXX_HOOK_DLCI_LEADIN, strlen(NG_XXX_HOOK_DLCI_LEADIN)) == 0) { char *eptr; cp = name + sizeof(NG_XXX_HOOK_DLCI_LEADIN); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); dlci = (int)strtoul(cp, &eptr, 10); if (*eptr != '\0' || dlci < 0 || dlci > 1023) return (EINVAL); /* We have a dlci, now either find it, or allocate it */ for (chan = 0; chan < XXX_NUM_DLCIS; chan++) if (xxxp->channel[chan].dlci == dlci) break; if (chan == XXX_NUM_DLCIS) { for (chan = 0; chan < XXX_NUM_DLCIS; chan++) if (xxxp->channel[chan].dlci != -2) continue; if (chan == XXX_NUM_DLCIS) return (ENOBUFS); } if (xxxp->channel[chan].hook != NULL) return (EADDRINUSE); NG_HOOK_SET_PRIVATE(hook, xxxp->channel + chan); xxxp->channel[chan].hook = hook; return (0); } else if (strcmp(name, NG_XXX_HOOK_DOWNSTREAM) == 0) { /* Example of simple predefined hooks. */ /* do something specific to the downstream connection */ xxxp->downstream_hook.hook = hook; NG_HOOK_SET_PRIVATE(hook, &xxxp->downstream_hook); } else if (strcmp(name, NG_XXX_HOOK_DEBUG) == 0) { /* do something specific to a debug connection */ xxxp->debughook = hook; NG_HOOK_SET_PRIVATE(hook, NULL); } else return (EINVAL); /* not a hook we know about */ return(0); } /* * Get a netgraph control message. * We actually recieve a queue item that has a pointer to the message. * If we free the item, the message will be freed too, unless we remove * it from the item using NGI_GET_MSG(); * The return address is also stored in the item, as an ng_ID_t, * accessible as NGI_RETADDR(item); * Check it is one we understand. If needed, send a response. * We could save the address for an async action later, but don't here. * Always free the message. * The response should be in a malloc'd region that the caller can 'free'. * A response is not required. * Theoretically you could respond defferently to old message types if * the cookie in the header didn't match what we consider to be current * (so that old userland programs could continue to work). */ static int ng_xxx_rcvmsg(node_p node, item_p item, hook_p lasthook) { const xxx_p xxxp = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); /* Deal with message according to cookie and command */ switch (msg->header.typecookie) { case NGM_XXX_COOKIE: switch (msg->header.cmd) { case NGM_XXX_GET_STATUS: { struct ngxxxstat *stats; NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); if (!resp) { error = ENOMEM; break; } stats = (struct ngxxxstat *) resp->data; stats->packets_in = xxxp->packets_in; stats->packets_out = xxxp->packets_out; break; } case NGM_XXX_SET_FLAG: if (msg->header.arglen != sizeof(u_int32_t)) { error = EINVAL; break; } xxxp->flags = *((u_int32_t *) msg->data); break; default: error = EINVAL; /* unknown command */ break; } break; default: error = EINVAL; /* unknown cookie type */ break; } /* Take care of synchronous response, if any */ NG_RESPOND_MSG(error, node, item, resp); /* Free the message and return */ NG_FREE_MSG(msg); return(error); } /* * Receive data, and do something with it. * Actually we receive a queue item which holds the data. - * If we free the item it wil also froo the data and metadata unless - * we have previously disassociated them using the NGI_GET_xxx() macros. + * If we free the item it will also free the data unless we have + * previously disassociated it using the NGI_GET_M() macro. * Possibly send it out on another link after processing. * Possibly do something different if it comes from different - * hooks. the caller will never free m or meta, so - * if we use up this data or abort we must free BOTH of these. + * hooks. The caller will never free m, so if we use up this data or + * abort we must free it. * * If we want, we may decide to force this data to be queued and reprocessed * at the netgraph NETISR time. * We would do that by setting the HK_QUEUE flag on our hook. We would do that * in the connect() method. */ static int ng_xxx_rcvdata(hook_p hook, item_p item ) { const xxx_p xxxp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); int chan = -2; int dlci = -2; int error; struct mbuf *m; NGI_GET_M(item, m); if (NG_HOOK_PRIVATE(hook)) { dlci = ((struct XXX_hookinfo *) NG_HOOK_PRIVATE(hook))->dlci; chan = ((struct XXX_hookinfo *) NG_HOOK_PRIVATE(hook))->channel; if (dlci != -1) { /* If received on a DLCI hook process for this * channel and pass it to the downstream module. * Normally one would add a multiplexing header at * the front here */ /* M_PREPEND(....) ; */ /* mtod(m, xxxxxx)->dlci = dlci; */ NG_FWD_NEW_DATA(error, item, xxxp->downstream_hook.hook, m); xxxp->packets_out++; } else { /* data came from the multiplexed link */ dlci = 1; /* get dlci from header */ /* madjust(....) *//* chop off header */ for (chan = 0; chan < XXX_NUM_DLCIS; chan++) if (xxxp->channel[chan].dlci == dlci) break; if (chan == XXX_NUM_DLCIS) { NG_FREE_ITEM(item); NG_FREE_M(m); return (ENETUNREACH); } /* If we were called at splnet, use the following: - * NG_SEND_DATA(error, otherhook, m, meta); if this + * NG_SEND_DATA_ONLY(error, otherhook, m); if this * node is running at some SPL other than SPLNET * then you should use instead: error = - * ng_queueit(otherhook, m, meta); m = NULL: meta = - * NULL; this queues the data using the standard - * NETISR system and schedules the data to be picked + * ng_queueit(otherhook, m, NULL); m = NULL; + * This queues the data using the standard NETISR + * system and schedules the data to be picked * up again once the system has moved to SPLNET and - * the processing of the data can continue. after - * these are run 'm' and 'meta' should be considered + * the processing of the data can continue. After + * these are run 'm' should be considered * as invalid and NG_SEND_DATA actually zaps them. */ NG_FWD_NEW_DATA(error, item, xxxp->channel[chan].hook, m); xxxp->packets_in++; } } else { /* It's the debug hook, throw it away.. */ if (hook == xxxp->downstream_hook.hook) { NG_FREE_ITEM(item); NG_FREE_M(m); } } return 0; } #if 0 /* * If this were a device node, the data may have been received in response * to some interrupt. * in which case it would probably look as follows: */ devintr() { int error; * here */ /* get packet from device and send on */ m = MGET(blah blah) NG_SEND_DATA_ONLY(error, xxxp->upstream_hook.hook, m); /* see note above in xxx_rcvdata() */ /* and ng_xxx_connect() */ } #endif /* 0 */ /* * Do local shutdown processing.. * All our links and the name have already been removed. * If we are a persistant device, we might refuse to go away. * In the case of a persistant node we signal the framework that we * are still in business by clearing the NG_INVALID bit. However * If we find the NG_REALLY_DIE bit set, this means that * we REALLY need to die (e.g. hardware removed). * This would have been set using the NG_NODE_REALLY_DIE(node) * macro in some device dependent function (not shown here) before * calling ng_rmnode_self(). */ static int ng_xxx_shutdown(node_p node) { const xxx_p privdata = NG_NODE_PRIVATE(node); #ifndef PERSISTANT_NODE NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(privdata->node); FREE(privdata, M_NETGRAPH); #else if (node->nd_flags & NG_REALLY_DIE) { /* * WE came here because the widget card is being unloaded, * so stop being persistant. * Actually undo all the things we did on creation. */ NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(privdata->node); FREE(privdata, M_NETGRAPH); return (0); } node->nd_flags &= ~NG_INVALID; /* reset invalid flag */ #endif /* PERSISTANT_NODE */ return (0); } /* * This is called once we've already connected a new hook to the other node. * It gives us a chance to balk at the last minute. */ static int ng_xxx_connect(hook_p hook) { #if 0 /* * If we were a driver running at other than splnet then * we should set the QUEUE bit on the edge so that we * will deliver by queing. */ if /*it is the upstream hook */ NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); #endif #if 0 /* * If for some reason we want incoming date to be queued * by the NETISR system and delivered later we can set the same bit on * OUR hook. (maybe to allow unwinding of the stack) */ if (NG_HOOK_PRIVATE(hook)) { int dlci; /* * If it's dlci 1023, requeue it so that it's handled * at a lower priority. This is how a node decides to * defer a data message. */ dlci = ((struct XXX_hookinfo *) NG_HOOK_PRIVATE(hook))->dlci; if (dlci == 1023) { NG_HOOK_FORCE_QUEUE(hook); } #endif /* otherwise be really amiable and just say "YUP that's OK by me! " */ return (0); } /* * Hook disconnection * * For this type, removal of the last link destroys the node */ static int ng_xxx_disconnect(hook_p hook) { if (NG_HOOK_PRIVATE(hook)) ((struct XXX_hookinfo *) (NG_HOOK_PRIVATE(hook)))->hook = NULL; if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) /* already shutting down? */ ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } Index: head/sys/netgraph/ng_tee.c =================================================================== --- head/sys/netgraph/ng_tee.c (revision 131154) +++ head/sys/netgraph/ng_tee.c (revision 131155) @@ -1,409 +1,398 @@ /* * 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 * * $FreeBSD$ * $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 /* Per hook info */ struct hookinfo { hook_p hook; struct ng_tee_hookstat stats; }; /* Per node info */ struct privdata { node_p node; struct hookinfo left; struct hookinfo right; struct hookinfo left2right; struct hookinfo right2left; }; typedef struct privdata *sc_p; /* Netgraph methods */ static ng_constructor_t ngt_constructor; static ng_rcvmsg_t ngt_rcvmsg; static ng_close_t ngt_close; static ng_shutdown_t ngt_shutdown; static ng_newhook_t ngt_newhook; static ng_rcvdata_t ngt_rcvdata; static ng_disconnect_t ngt_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 = ngt_constructor, .rcvmsg = ngt_rcvmsg, .close = ngt_close, .shutdown = ngt_shutdown, .newhook = ngt_newhook, .rcvdata = ngt_rcvdata, .disconnect = ngt_disconnect, .cmdlist = ng_tee_cmds, }; NETGRAPH_INIT(tee, &ng_tee_typestruct); /* * Node constructor */ static int ngt_constructor(node_p node) { sc_p privdata; MALLOC(privdata, sc_p, sizeof(*privdata), M_NETGRAPH, M_NOWAIT|M_ZERO); if (privdata == NULL) return (ENOMEM); NG_NODE_SET_PRIVATE(node, privdata); privdata->node = node; return (0); } /* * Add a hook */ static int ngt_newhook(node_p node, hook_p hook, const char *name) { const sc_p sc = NG_NODE_PRIVATE(node); if (strcmp(name, NG_TEE_HOOK_RIGHT) == 0) { sc->right.hook = hook; bzero(&sc->right.stats, sizeof(sc->right.stats)); NG_HOOK_SET_PRIVATE(hook, &sc->right); } else if (strcmp(name, NG_TEE_HOOK_LEFT) == 0) { sc->left.hook = hook; bzero(&sc->left.stats, sizeof(sc->left.stats)); NG_HOOK_SET_PRIVATE(hook, &sc->left); } else if (strcmp(name, NG_TEE_HOOK_RIGHT2LEFT) == 0) { sc->right2left.hook = hook; bzero(&sc->right2left.stats, sizeof(sc->right2left.stats)); NG_HOOK_SET_PRIVATE(hook, &sc->right2left); } else if (strcmp(name, NG_TEE_HOOK_LEFT2RIGHT) == 0) { sc->left2right.hook = hook; bzero(&sc->left2right.stats, sizeof(sc->left2right.stats)); NG_HOOK_SET_PRIVATE(hook, &sc->left2right); } else return (EINVAL); return (0); } /* * Receive a control message */ static int ngt_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) { if (lasthook == sc->left.hook) { if (sc->right.hook) { NGI_MSG(item) = msg; NG_FWD_ITEM_HOOK(error, item, sc->right.hook); return (error); } } else { if (sc->left.hook) { NGI_MSG(item) = msg; NG_FWD_ITEM_HOOK(error, item, sc->left.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 ngt_rcvdata(hook_p hook, item_p item) { const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook); struct hookinfo *dest; struct hookinfo *dup; int error = 0; struct mbuf *m; - meta_p meta; m = NGI_M(item); - meta = NGI_META(item); /* leave these owned by the item */ /* Which hook? */ if (hinfo == &sc->left) { dup = &sc->left2right; dest = &sc->right; } else if (hinfo == &sc->right) { dup = &sc->right2left; dest = &sc->left; } else if (hinfo == &sc->right2left) { dup = NULL; dest = &sc->right; } else if (hinfo == &sc->left2right) { dup = NULL; dest = &sc->left; } else { panic("%s: no hook!", __func__); #ifdef RESTARTABLE_PANICS return(EINVAL); #endif } /* Update stats on incoming hook */ hinfo->stats.inOctets += m->m_pkthdr.len; hinfo->stats.inFrames++; /* * Don't make a copy if only the dup hook exists. */ if ((dup && dup->hook) && (dest->hook == NULL)) { dest = dup; dup = NULL; } - /* Duplicate packet and meta info if requried */ + /* Duplicate packet if requried */ if (dup && dup->hook) { struct mbuf *m2; - meta_p meta2; /* Copy packet (failure will not stop the original)*/ m2 = m_dup(m, M_DONTWAIT); if (m2) { - - /* Copy meta info */ - /* If we can't get a copy, tough.. */ - if (meta != NULL) { - meta2 = ng_copy_meta(meta); - } else - meta2 = NULL; - /* Deliver duplicate */ dup->stats.outOctets += m->m_pkthdr.len; dup->stats.outFrames++; - NG_SEND_DATA(error, dup->hook, m2, meta2); + NG_SEND_DATA_ONLY(error, dup->hook, m2); } } /* Deliver frame out destination hook */ if (dest->hook) { dest->stats.outOctets += m->m_pkthdr.len; dest->stats.outFrames++; NG_FWD_ITEM_HOOK(error, item, dest->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 ngt_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 ngt_shutdown(node_p node) { const sc_p privdata = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(privdata->node); FREE(privdata, M_NETGRAPH); return (0); } /* * Hook disconnection */ static int ngt_disconnect(hook_p hook) { struct hookinfo *const hinfo = NG_HOOK_PRIVATE(hook); KASSERT(hinfo != NULL, ("%s: null info", __func__)); hinfo->hook = NULL; 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); }