diff --git a/sys/netgraph/ng_bridge.c b/sys/netgraph/ng_bridge.c --- a/sys/netgraph/ng_bridge.c +++ b/sys/netgraph/ng_bridge.c @@ -124,6 +124,7 @@ unsigned int persistent : 1, /* can exist w/o hooks */ sendUnknown : 1;/* links receive unknowns by default */ struct callout timer; /* one second periodic timer */ + struct unrhdr *linkUnit; /* link unit number allocator */ }; typedef struct ng_bridge_private *priv_p; typedef struct ng_bridge_private const *priv_cp; /* read only access */ @@ -140,15 +141,24 @@ /* Hash table bucket declaration */ SLIST_HEAD(ng_bridge_bucket, ng_bridge_host); +/* [up]link prefix matching */ +struct ng_link_prefix { + const char * const prefix; + size_t len; +}; + /* 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_connect_t ng_bridge_connect; static ng_rcvdata_t ng_bridge_rcvdata; static ng_disconnect_t ng_bridge_disconnect; +static ng_findhook_t ng_bridge_findhook; /* Other internal functions */ +static struct ng_link_prefix *ng_get_link_prefix(const char *name); static void ng_bridge_free_link(link_p link); static struct ng_bridge_host *ng_bridge_get(priv_cp priv, const u_char *addr); static int ng_bridge_put(priv_p priv, const u_char *addr, link_p link); @@ -314,9 +324,11 @@ .rcvmsg = ng_bridge_rcvmsg, .shutdown = ng_bridge_shutdown, .newhook = ng_bridge_newhook, + .connect = ng_bridge_connect, .rcvdata = ng_bridge_rcvdata, .disconnect = ng_bridge_disconnect, .cmdlist = ng_bridge_cmdlist, + .findhook = ng_bridge_findhook, }; NETGRAPH_INIT(bridge, &ng_bridge_typestruct); @@ -350,6 +362,9 @@ NG_NODE_SET_PRIVATE(node, priv); priv->node = node; + /* allocator for links */ + priv->linkUnit = new_unrhdr(0, INT_MAX, NULL); + /* Start timer; timer is always running while node is alive */ ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0); @@ -364,29 +379,32 @@ ng_bridge_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); - char linkName[NG_HOOKSIZ]; - u_int32_t linkNum; link_p link; - const char *prefix = NG_BRIDGE_HOOK_LINK_PREFIX; bool isUplink; - /* Check for a link hook */ - if (strlen(name) <= strlen(prefix)) - return (EINVAL); /* Unknown hook name */ + struct ng_link_prefix *pfx = ng_get_link_prefix(name); + if (pfx->prefix == NULL) + return (EINVAL); /* not a valid prefix */ isUplink = (name[0] == 'u'); - if (isUplink) - prefix = NG_BRIDGE_HOOK_UPLINK_PREFIX; - - /* primitive parsing */ - linkNum = strtoul(name + strlen(prefix), NULL, 10); - /* validation by comparing against the reconstucted name */ - snprintf(linkName, sizeof(linkName), "%s%u", prefix, linkNum); - if (strcmp(linkName, name) != 0) - return (EINVAL); - if (linkNum == 0 && isUplink) - return (EINVAL); + if (strlen(name) > pfx->len) { /* given number */ + char linkName[NG_HOOKSIZ]; + u_int32_t linkNum; + /* primitive parsing */ + linkNum = strtoul(name + pfx->len, NULL, 10); + /* validation by comparing against the reconstucted name */ + snprintf(linkName, sizeof(linkName), "%s%u", pfx->prefix, + linkNum); + if (strcmp(linkName, name) != 0) + return (EINVAL); + if (linkNum == 0 && isUplink) + return (EINVAL); + } + /* + * In case where number will be assigned, it is up to ng_bridge_connect to + * ensure "uplink" does not get 0. + */ if(NG_PEER_NODE(hook) == node) return (ELOOP); @@ -761,6 +779,38 @@ return (1); } +static int +ng_bridge_connect(hook_p hook) +{ + char *name = NG_HOOK_NAME(hook); + const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + u_int32_t linkNum; + + struct ng_link_prefix *pfx = ng_get_link_prefix(name); + if (strlen(name) > pfx->len) { /* given number */ + /* + * It is possible to get this far requesting "link1" when + * "uplink1" already exists. That is because ng_bridge_findhook + * is doing a string match. It needs to for other operations. + * But the unr must only allow one to exist at a time. It isn't + * exactly an EEXIST situation, but it definitely can't be + * allowed so must be considered EINVAL. + */ + linkNum = strtoul(name + pfx->len, NULL, 10); + if (alloc_unr_specific(priv->linkUnit, linkNum) == -1) + return (EINVAL); /* already in use */ + } else { + linkNum = alloc_unr(priv->linkUnit); + if (name[0] == 'u' && linkNum == 0) { + /* uplink can't be 0, get another and give back 0 */ + linkNum = alloc_unr(priv->linkUnit); + free_unr(priv->linkUnit, 0); + } + snprintf(name, NG_HOOKSIZ, "%s%u", pfx->prefix, linkNum); + } + return (0); +} + static int ng_bridge_rcvdata(hook_p hook, item_p item) { @@ -913,6 +963,7 @@ KASSERT(priv->numLinks == 0 && priv->numHosts == 0, ("%s: numLinks=%d numHosts=%d", __func__, priv->numLinks, priv->numHosts)); + delete_unrhdr(priv->linkUnit); /* assert means no need to clear */ ng_uncallout(&priv->timer, node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); @@ -927,8 +978,11 @@ static int ng_bridge_disconnect(hook_p hook) { + char *name = NG_HOOK_NAME(hook); const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); link_p link = NG_HOOK_PRIVATE(hook); + struct ng_link_prefix *pfx = ng_get_link_prefix(name); + u_int32_t linkNum; /* Remove all hosts associated with this link */ ng_bridge_remove_hosts(priv, link); @@ -937,6 +991,9 @@ ng_bridge_free_link(link); priv->numLinks--; + linkNum = strtoul(name + pfx->len, NULL, 10); + free_unr(priv->linkUnit, linkNum); + /* If no more hooks, go away */ if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) @@ -946,6 +1003,28 @@ return (0); } + +/* + * `ng_findhook` would still work for ng_bridge, but this speeds up what should + * become a common case: requesting to connect and have link/uplink number + * assigned by the ng_bridge. + */ +static hook_p +ng_bridge_findhook(node_p node, const char *name) +{ + hook_p hook; + struct ng_link_prefix *pfx = ng_get_link_prefix(name); + + if (strlen(name) > pfx->len) { + LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) { + if (NG_HOOK_IS_VALID(hook) && + (strcmp(NG_HOOK_NAME(hook), name) == 0)) + return (hook); + } + } + return (NULL); +} + /****************************************************************** HASH TABLE FUNCTIONS ******************************************************************/ @@ -1095,6 +1174,32 @@ MISC FUNCTIONS ******************************************************************/ +static struct ng_link_prefix * +ng_get_link_prefix(const char *name) +{ + static struct ng_link_prefix hook_prefix[] = { + { + .prefix = NG_BRIDGE_HOOK_LINK_PREFIX, + .len = sizeof(NG_BRIDGE_HOOK_LINK_PREFIX) - 1 + }, + { + .prefix = NG_BRIDGE_HOOK_UPLINK_PREFIX, + .len = sizeof(NG_BRIDGE_HOOK_UPLINK_PREFIX) - 1 + }, + { + .prefix = NULL, + .len = 0 + } + }; + struct ng_link_prefix *iter; + + for (iter = hook_prefix; iter->prefix != NULL; iter++) { + if (strncmp(iter->prefix, name, iter->len) == 0) + break; + } + return (iter); +} + /* * Remove all hosts associated with a specific link from the hashtable. * If linkNum == -1, then remove all hosts in the table. diff --git a/usr.sbin/bhyve/net_backend_netgraph.c b/usr.sbin/bhyve/net_backend_netgraph.c --- a/usr.sbin/bhyve/net_backend_netgraph.c +++ b/usr.sbin/bhyve/net_backend_netgraph.c @@ -47,6 +47,49 @@ #define NG_SBUF_MAX_SIZE (4 * 1024 * 1024) +enum NgType { + NG_ERROR = -1, + NG_NOTEXIST = 0, + NG_ETHER, + NG_BRIDGE, + NG_UNKNOWN, +}; + +static enum NgType +get_ng_type(int ngs, const char *node) +{ + int rc; + size_t ix; + struct ng_mesg *resp; + struct nodeinfo *ninfo; + static struct { + const char * const key; + enum NgType value; + } Switch[] = { + {"ether", NG_ETHER}, + {"bridge", NG_BRIDGE}, + {"unknown", NG_UNKNOWN} + }; + + rc = NgSendMsg(ngs, node, NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0); + if (rc == -1) { + if (errno == ENOENT) + return (NG_NOTEXIST); /* no such node */ + else + return (NG_ERROR); /* error */ + } + if (-1 == NgAllocRecvMsg(ngs, &resp, NULL)) + return (NG_ERROR); + + ninfo = (struct nodeinfo *) resp->data; + for (ix = 0; ix < (sizeof(Switch) / sizeof(Switch[0])); ix++) { + if (0 == strcmp(Switch[ix].key, ninfo->type)) + break; + } + free(resp); + return (Switch[ix].value); /* could be unknown */ +} + static int ng_init(struct net_backend *be, const char *devname __unused, nvlist_t *nvl, net_be_rxeof_t cb, void *param) @@ -84,13 +127,6 @@ value = "vmlink"; strncpy(ngc.ourhook, value, NG_HOOKSIZ - 1); - value = get_config_value_node(nvl, "peerhook"); - if (value == NULL) { - EPRINTLN("peer hook must be provided"); - return (-1); - } - strncpy(ngc.peerhook, value, NG_HOOKSIZ - 1); - nodename = get_config_value_node(nvl, "socket"); if (NgMkSockNode(nodename, &ctrl_sock, &be->fd) < 0) { @@ -98,6 +134,28 @@ return (-1); } + value = get_config_value_node(nvl, "peerhook"); + if (value != NULL) { + strncpy(ngc.peerhook, value, NG_HOOKSIZ - 1); + } else { + switch (get_ng_type(ctrl_sock, ngc.path)) { + case NG_ETHER: + strncpy(ngc.peerhook, "lower", NG_HOOKSIZ - 1); + break; + case NG_BRIDGE: + /* this will autoselect lowest link number */ + strncpy(ngc.peerhook, "link", NG_HOOKSIZ - 1); + break; + case NG_NOTEXIST: + EPRINTLN("peerhook: no such node"); + /* fallthrough */ + default: + EPRINTLN("unable to use path for peerhook"); + close(ctrl_sock); + return (-1); + } + } + if (NgSendMsg(ctrl_sock, ".", NGM_GENERIC_COOKIE, NGM_CONNECT, &ngc, sizeof(ngc)) < 0) {