diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -170,6 +170,7 @@ SUBDIR.${MK_NETGRAPH}+= flowctl SUBDIR.${MK_NETGRAPH}+= ngctl SUBDIR.${MK_NETGRAPH}+= nghook +SUBDIR.${MK_NETGRAPH}+= ngportal SUBDIR.${MK_NIS}+= rpc.yppasswdd SUBDIR.${MK_NIS}+= rpc.ypupdated SUBDIR.${MK_NIS}+= rpc.ypxfrd diff --git a/usr.sbin/ngportal/Makefile b/usr.sbin/ngportal/Makefile new file mode 100644 --- /dev/null +++ b/usr.sbin/ngportal/Makefile @@ -0,0 +1,8 @@ +PROG= ngportal +MAN= ngportal.8 +SRCS= kld.c ng.c portal.c +LIBADD= jail netgraph + +WARNS?=1 + +.include diff --git a/usr.sbin/ngportal/kld.c b/usr.sbin/ngportal/kld.c new file mode 100644 --- /dev/null +++ b/usr.sbin/ngportal/kld.c @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 David Marker + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +#include "portal.h" + +void +kld_ensure_load(const char *search) +{ + int fileid, modid; + const char *cp; + struct module_stat mstat; + + assert(search != NULL); + + /* scan files in kernel */ + mstat.version = sizeof(struct module_stat); + for (fileid = kldnext(0); fileid > 0; fileid = kldnext(fileid)) { + /* scan modules in file */ + for (modid = kldfirstmod(fileid); modid > 0; + modid = modfnext(modid)) { + if (modstat(modid, &mstat) < 0) + continue; + /* strip bus name if present */ + if ((cp = strchr(mstat.name, '/')) != NULL) { + cp++; + } else { + cp = mstat.name; + } + + /* found, already loaded */ + if (strcmp(search, cp) == 0) + return; + } + } + + /* + * In theory you could use this in a jail before loading ng_wormhole or + * ng_socket. Only thing we can do is let you know the kernel modules + * can't be loaded. + */ + if (kldload(search) == -1) + err(ERREXIT, "%s: unable to load kernel module \"%s\"", + __func__, search); +} diff --git a/usr.sbin/ngportal/ng.c b/usr.sbin/ngportal/ng.c new file mode 100644 --- /dev/null +++ b/usr.sbin/ngportal/ng.c @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2025 David Marker + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "portal.h" + +/* in this file we always want the colon on the end */ +#define IDFMT "[%08x]:" + +ngctx +ng_create_context(void) +{ + int rc; + int ngs = -1; /* won't be touched if it fails */ + char name[NG_NODESIZ]; + + snprintf(name, sizeof(name), "ngctl%d", getpid()); + name[NG_NODESIZ - 1] = '\0'; + + rc = NgMkSockNode(name, &ngs, NULL); + if (rc == -1) + err(ERREXIT, "%s: failed to initialize netgraph(4)", __func__); + + return (ngs); +} + +/* + * NOTE: this is remains connected to control socket otherwise it would shutdown + * since it doesn't persist. It is disconnected as part of ng_wormhole_open. + */ +ng_ID_t +ng_wh_create(ngctx ctx) +{ +# define OURHK "tmp" + int rc; + struct ng_mesg *resp; + struct ngm_mkpeer msg = { + .type = NG_WORMHOLE_NODE_TYPE, + .ourhook = OURHK, + .peerhook = NG_WORMHOLE_HOOK, + }; + const char *pth = ".:" OURHK; + ng_ID_t nd; +# undef OURHK + + assert(ctx >= 0); + + rc = NgSendMsg(ctx, ".", NGM_GENERIC_COOKIE, NGM_MKPEER, &msg, + sizeof(msg)); + if (rc == -1) + err(ERREXIT, "unable to create %s", msg.type); + + rc = NgSendMsg(ctx, pth, NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0); + if (rc == -1) + errx(ERREXIT, "unable to request %s info, presumed dead", + msg.type); + + rc = NgAllocRecvMsg(ctx, &resp, NULL); + if (rc == -1) + err(ERREXIT, "unable to retrieve %s info, presumed dead", + msg.type); + + /* + * This warns about structure alignment but is also done in ngctl(8). + */ + nd = ((struct nodeinfo *) resp->data)->id; + free(resp); + + /* valid netgraph IDs start at 1 */ + if (nd == 0) + err(ERREXIT, "invalid node id for wormhole, presumed dead"); + + return (nd); +} + +void +ng_wh_name(ngctx ctx, ng_ID_t wh, const char *name) +{ + int rc; + struct ngm_name msg; + char pth[NG_NODESIZ + 1]; /* extra for ':' */ + + assert(ctx >= 0); + assert(wh > 0); + + if (name == NULL) + return; /* no name given */ + + assert(strlen(name) < NG_NODELEN); + + snprintf(msg.name, sizeof(msg.name), "%s", name); + snprintf(pth, sizeof(pth), IDFMT, wh); + + rc = NgSendMsg(ctx, pth, NGM_GENERIC_COOKIE, NGM_NAME, &msg, sizeof(msg)); + if (rc == -1) + err(ERREXIT, "failed to name `%s'", pth); +} + +void +ng_wh_connect(ngctx ctx, ng_ID_t wh, const char *peer, const char *peerhook) +{ + int rc; + struct ngm_connect msg = { .ourhook = NG_WORMHOLE_HOOK }; + char pth[NG_NODESIZ]; + + assert(ctx >= 0); + assert(wh > 0); + + if (peer == NULL) + return; /* nothing to connect */ + + assert(peerhook != NULL); + assert(strlen(peer) < sizeof(msg.path) - 2); /* for ':' and '\0' */ + assert(strlen(peerhook) < sizeof(msg.peerhook) - 1); /* for '\0' */ + + snprintf(pth, sizeof(pth), IDFMT, wh); + + snprintf(msg.path, sizeof(msg.path), "%s:", peer); + strncpy(msg.peerhook, peerhook, sizeof(msg.peerhook)); + + rc = NgSendMsg(ctx, pth, NGM_GENERIC_COOKIE, NGM_CONNECT, &msg, + sizeof(msg)); + if (rc == -1) { + /* something standard going on, like maybe node doesn't exist */ + if (strcmp(peerhook, NG_WORMHOLE_HOOK) != 0) + goto generic_err; + + /* + * You can connect wormholes together, `ngportal` does so when + * 2 jails specified. But there are 2 cases it will fail. + * + * EINVAL means other side not open. EDOOFUS means that if the + * connection were allowed (which it isn't by kernel) it would + * result in a connected pair of wormholes in the same vnet. + * Obviously pointles. + */ + if (errno == EINVAL) /* we opened before connect */ + err(EX_DATAERR, + "unable to connect to `%s%s', not opened", + msg.path, msg.peerhook); + + if (errno == EDOOFUS) + err(EX_DATAERR, + "forbidden: collapse would result in connected " + "wormholes in the same vnet"); + +generic_err: + err(EX_DATAERR, "unable to connect `%s%s' to `%s%s'", pth, + msg.ourhook, msg.path, msg.peerhook); + + } +} + +ng_ID_t +ng_wh_open(ngctx ctx, ng_ID_t wh, const char *jail) +{ + int rc; + ng_ID_t nd; + char pth[NG_NODESIZ]; + struct hooklist *hlist; + struct nodeinfo *ninfo; + struct ng_mesg *resp; + struct ngm_rmhook msg = { .ourhook = NG_WORMHOLE_HOOK }; + + assert(ctx >= 0); + assert(wh > 0); + assert(jail != NULL); + assert(strlen(jail) < MAXHOSTNAMELEN); + + snprintf(pth, sizeof(pth), IDFMT, wh); + + rc = NgSendMsg(ctx, pth, NGM_WORMHOLE_COOKIE, NGM_WORMHOLE_OPEN, jail, + strlen(jail) + 1); + if (rc == -1) + errx(ERREXIT, "unable to open wormhole in `%s'", jail); + + /* + * Now the struct calls them `eh` and `warp`. But to inform users what + * is going on we helpfully name the warp hooks after the jail ID they + * are in. It can't be the jail name as that can far exceed the length + * of hooks. + * + * The point is we have a jail name (not id, or it could be we don't + * know since both are valid). + * + */ + rc = NgSendMsg(ctx, pth, NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0); + if (rc == -1) + errx(ERREXIT, + "unable to request wormhole node list, presumed dead"); + + rc = NgAllocRecvMsg(ctx, &resp, NULL); + if (rc == -1) + errx(ERREXIT, "unable to get response for wormhole node list, " + "presumed dead"); + + hlist = (struct hooklist *) resp->data; + ninfo = &hlist->nodeinfo; + assert(ninfo->hooks == 2); /* socket and our other wormhole */ + if (strcmp(hlist->link[0].nodeinfo.type, NG_WORMHOLE_NODE_TYPE) == 0) { + nd = hlist->link[0].nodeinfo.id; + } else { + assert(strcmp(hlist->link[1].nodeinfo.type, + NG_WORMHOLE_NODE_TYPE) == 0); + nd = hlist->link[1].nodeinfo.id; + } + free(resp); + + rc = NgSendMsg(ctx, pth, NGM_GENERIC_COOKIE, NGM_RMHOOK, &msg, + sizeof(msg)); + if (rc == -1) + errx(ERREXIT, "unable to rmhook `%s' from `%s'", + msg.ourhook, pth); + + return (nd); +} + +/* NOTE: only called from the cleanup. already in an err function */ +void +ng_shutdown_node(ngctx ctx, ng_ID_t nd) +{ + int rc; + char pth[NG_NODESIZ]; + + assert(ctx >= 0); + assert(nd > 0); + + snprintf(pth, NG_NODESIZ, IDFMT, nd); + + rc = NgSendMsg(ctx, pth, NGM_GENERIC_COOKIE, NGM_SHUTDOWN, NULL, 0); + if (rc == -1) { + (void) fprintf( + stderr, + "Failed to shutdown node.\n" + "try:\n" + "\tngctl shutdown %s\n", + pth + ); + } +} diff --git a/usr.sbin/ngportal/ngportal.8 b/usr.sbin/ngportal/ngportal.8 new file mode 100644 --- /dev/null +++ b/usr.sbin/ngportal/ngportal.8 @@ -0,0 +1,139 @@ +.\" +.\" Copyright (c) 2025 David Marker +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd April 4, 2025 +.Dt NGPORTAL 8 +.Os +.Sh NAME +.Nm ngportal +.Nd netgraph portal gun to create connected wormhole pairs +.Sh SYNOPSIS +.Nm +.Op Fl n +.Op Fl j Ar jail +.Ar spec1 +.Op Ar spec2 +.Sh DESCRIPTION +The +.Nm +utility creates an +.Xr ng_wormhole 4 +and then opens it in a different jail to connect and complete the pair. +Additionally, as a convenience, +.Nm +can name one or both wormholes for +.Xr ngctl 8 , +as well as connect one or both sides event horizon +to an already existing +.Xr netgraph 4 +node. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl n +Disable automatic loading of +.Xr ng_socket 4 +and +.Xr ng_wormhole 4 +kernel modules. +.It Fl j Ar jail +Perform the actions inside the +.Ar jail . +This becomes the new default jail when none is specified. +.El +.Pp +Specifications are colon separated strings with the following +components, all of which are optional: +.Op Ar jail Ns : +.Op Ar name Ns : +.Op Ar node:hook +.Bd -literal -offset indent -compact +jail name or id of jail that has been created. +name a name to provide the wormhole in the jail referenced + above if present or the default. +node:hook a netgraph node and one of its hooks to connect to the + evthorizon of the wormhole in the jail referenced + above if present or the default. +.Ed +.Pp +.Ar jail +if left unspecified this means the location where you are running +.Nm . +In fact you will confuse the portal gun if you specify (by name +or jail ID) the jail where you are running +.Nm . +But wormhole pairs can +.Sy not +be in the same +.Ar jail . +So at least one +.Ar jail +must be specified. +.Pp +Trailing colons are optional, but to specify a wormhole +.Ar name +you must have a leading colon even if you do not provide a +.Ar jail . +The +.Ar node:hook +pair really must be that. +It is not a +.Em path +with +.Em hook . +It must simply +identify the name or ID of a +.Em node +and the name of a +.Em hook +on that +.Em node . +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Create a wormhole pair from the system to jail +.Li test : +.Dl # ngportal test +.Pp +Do the same but name the wormholes this time: +.Dl # ngportal :wh0a test:wh0b +.Pp +Assuming you have an +.Xr ng_bridge 4 +named +.Li br0 +where you are running +.Nm +and an +.Xr ng_eiface 4 +named +.Li ngeth0 +in the jail +.Li test +they can be connected via wormhole to each other. +The following will name the wormholes and connect them: +.Dl # ngportal :wh0a:br0:link test:wh0b:ngeth0:ether +.Pp +All previous examples used a default jail. +But to connect two +.Xr ng_eiface 4 +in two peer +.Li jails +.Li (ngeth0a +in left and +.Li ngeth0b +in center) use the following: +.Dl # ngportal left::ngeth0a:ether center::ngeth0b:ether +.Sh SEE ALSO +.Xr netgraph 4 , +.Xr ng_bridge 4 , +.Xr ng_eiface 4 , +.Xr ng_wormhole 4 , +.Xr ngctl 8 +.Sh AUTHORS +.An David Marker Aq Mt dave@freedave.net +.Sh BUGS +Until there is a way for a process to get the jail ID in which it is running, +the only way to correctly refer to that jail is to leave it unspecified. diff --git a/usr.sbin/ngportal/portal.h b/usr.sbin/ngportal/portal.h new file mode 100644 --- /dev/null +++ b/usr.sbin/ngportal/portal.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 David Marker + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +/* remove 1 for '\0' */ +#define NG_NODELEN (NG_NODESIZ - 1) +#define NG_HOOKLEN (NG_HOOKSIZ - 1) + +#define LLNAMSIZ 18 +#define LLNAMLEN (LLNAMSIZ - 1) + +#define ERREXIT ((errno == EPERM) ? EX_NOPERM : EX_OSERR) +#define ERRALT(alt) ((errno == EPERM) ? EX_NOPERM : alt) + + +/* module loading: kld.c */ +void kld_ensure_load(const char *); + +/* netgraph functions: ng.c */ +typedef int ngctx; + +ngctx ng_create_context(void); +void ng_shutdown_node(ngctx, ng_ID_t); + +ng_ID_t ng_wh_create(ngctx); +ng_ID_t ng_wh_open(ngctx, ng_ID_t, const char *); +void ng_wh_name(ngctx, ng_ID_t, const char *); +void ng_wh_connect(ngctx, ng_ID_t, const char *, const char *); diff --git a/usr.sbin/ngportal/portal.c b/usr.sbin/ngportal/portal.c new file mode 100644 --- /dev/null +++ b/usr.sbin/ngportal/portal.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2025 David Marker + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "portal.h" + +/* name of our utility */ +#define ME "ngportal" + +/* + * The single purpose of this utility is to create and connect an ng_wormhole(4) + * that exists in the current vnet and a separate jail vnet. + */ + +static void +Usage(const char *format, ...) +{ + if (format != NULL) { + va_list ap; + va_start(ap, format); + (void) vfprintf(stderr, format, ap); + va_end(ap); + } + + /* + * Sadly users don't need to know or care that providing both jail names + * means we create a 2 pairs of wormholes then collapse them into a + * single pair that remains in both jails. The portal gun is more + * mysterious than they will ever know. + */ + (void) fprintf(stderr, + "USAGE: " ME " [-n] [-j jail] spec1 [spec2]\n" + "-n\tDisable automatic loading of netgraph(4) kernel modules.\n" + "-j jail\tSwitch to jail for all references.\n\n" + "You provide 2 wormhole specifications (components of which are\n" + "separated by colons). The wormhole spec componentes are:\n" + "\t[jail][:name][:node:hook]\n" + "For each spec the components are:\n" + "jail\t\tjail reference for remaining components. If not preset it\n" + "\t\tdefaults to where `" ME "' is run (see `-j' above).\n" + "name\t\tset the netgraph name of the wormhole to [name] in [jail].\n" + "node:hook\tconnect the wormhole in [jail] to this [node:hook] pair.\n\n" + "Without a jail componet the spec assumes the jail where `" ME "' was\n" + "run or the jail from `-j'. But the jails from spec1 and spec2 MUST be\n" + "different. Without a name the wormhole can only be accessed by ID.\n" + "Without a [node:hook] netgraph path the wormhole is left unconnected\n" + "but is held open by the other side. It can be connected to later with\n" + "ngctl(8).\n"); + + exit(EX_USAGE); +} + +/* + * Module global, for err_cleanup to find everything. + * Start with invalid values our error cleanup can check for. + */ +static struct { + ngctx fd; + ng_ID_t wh1a; + ng_ID_t wh2a; +} G = { + .fd = -1, + .wh1a = 0, + .wh2a = 0 +}; + +static void +err_cleanup(int _) +{ + if (G.fd == -1) + return; /* can't shutdown without this */ + + if (G.wh1a != 0) + ng_shutdown_node(G.fd, G.wh1a); + if (G.wh2a != 0) + ng_shutdown_node(G.fd, G.wh2a); + + close(G.fd); +} + +/* + * This will split a string like "jail:name:node:hook" into separate + * parts: "jail", "name", "node", "hook". + * + * We have an extra ':' after jail. And an extra ':' after name. That's so + * we can replace with '\0' and slice string up with strsep. But + * the last component found does not need to have a ':' after it. + * + * But you can't provide node and not provide hook. + * + * So that users don't have to play "fetch a rock" with their input we + * warn and return -1 after reporting as many issues as we can find. + * + */ +static int +parse_spec(char *arg, const char **jail, const char **name, + const char **node, const char **hook) +{ + int rc = 0; + char **iter, *components[4]; + + assert(arg != NULL); + assert(jail != NULL); + assert(name != NULL); + assert(node != NULL); + assert(hook != NULL); + + memset(components, 0, sizeof(components)); + + /* almost straight from man page :) */ + for (iter = components; (*iter = strsep(&arg, ":")) != NULL;) + if (++iter >= &components[4]) + break; + + if (arg != NULL) + warnx("unrecognized components after wormhole spec `%s'", + arg), rc++; + +# define CHECKCOMPONENT(var, max, set) \ + do { \ + if (var != NULL) { \ + if (strlen(var) > (max)) { \ + warnx(#set " name too long: `%s'", \ + var), rc++; \ + } else \ + *set = var[0] == '\0' ? NULL: var; \ + } \ + } while(0) + CHECKCOMPONENT(components[0], MAXHOSTNAMELEN, jail); + CHECKCOMPONENT(components[1], NG_NODELEN, name); + CHECKCOMPONENT(components[2], NG_NODELEN, node); + CHECKCOMPONENT(components[3], NG_NODELEN, hook); +# undef CHECKCOMPONENT + + /* node,hook must both be set or both be unset */ + if (components[2] != NULL && components[3] == NULL) + warnx("node: `%s': set but missing hook", components[2]), rc++; + if (components[2] == NULL && components[3] != NULL) + warnx("hook: `%s': set but missing node", components[3]), rc++; + + return (rc == 0) ? 0 : -1; +} + +static void +jail_name_connect(int jid, ng_ID_t wh, const char *name, const char *node, + const char *hook) +{ + int rc; + pid_t pid; + assert(jid > 0); /* system is 0 */ + assert(wh > 0); + + if (name == NULL && node == NULL) + return; /* nothing to do so don't fork etc */ + + pid = fork(); + if (pid == -1) + err(ERREXIT, "%s: fork()", __func__); + if (pid == 0) { /* child */ + (void) close(G.fd); + G.fd = -1; + /* child never has to close wormholes */ + G.wh1a = 0; + G.wh2a = 0; + if (jail_attach(jid) != 0) + errx(ERRALT(EX_OSERR), "cannot attach to jail (jid=%d)", + jid); + /* must get new context */ + G.fd = ng_create_context(); + ng_wh_name(G.fd, wh, name); + ng_wh_connect(G.fd, wh, node, hook); + (void) close(G.fd); + exit(0); + } else { /* parent */ + int status; + do { + rc = wait(&status); + } while (rc == -1 && errno == EINTR); + if (rc == -1) + err(EX_OSERR, "failed to wait for child in prison"); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + err(EX_OSERR, "child failed its mission"); + } +} + + +int +main(int argc, char **argv) +{ + int ch, rc = 0, jid1 = 0, jid2 = 0, load_kmod = 1; + const char *jail = NULL; /* for -j not spec */ + const char *jail1, *name1, *node1, *hook1; + const char *jail2, *name2, *node2, *hook2; + ng_ID_t wh1b, wh2b; + + setvbuf(stdout, NULL, _IONBF, BUFSIZ); + + /* use getopt_long so they can place options anywhere */ + while ((ch = getopt_long(argc, argv, ":nj:", NULL, NULL)) != -1) { + switch (ch) { + case 'j': + jail = optarg; + if (strlen(jail) > MAXHOSTNAMELEN) + Usage(ME ": `%s' exceeds %d characters\n\n", + jail, MAXHOSTNAMELEN); + break; + case 'n': + load_kmod = 0; /* user asked not to */ + break; + default: + Usage(ME ": unrecognized option `%s'\n\n", + argv[optind - 1]); + } + } + argv += optind; + argc -= optind; + + jail1 = name1 = node1 = hook1 = NULL; + jail2 = name2 = node2 = hook2 = NULL; + switch (argc) { + case 0: + Usage(ME ": must minimally provide `jail' of one spec\n\n"); + case 1: + rc = parse_spec(argv[0], &jail1, &name1, &node1, &hook1); + jail2 = name2 = node2 = hook2 = NULL; + break; + case 2: + rc = parse_spec(argv[0], &jail1, &name1, &node1, &hook1); + rc += parse_spec(argv[1], &jail2, &name2, &node2, &hook2); + break; + default: + Usage(ME ": too many arguments provided\n\n"); + } + + if (rc != 0) + Usage("\n\n"); + + if (jail1 == NULL && jail2 == NULL) + Usage(ME ": duplicate (default) jail reference detected\n\n"); + + /* + * All that mattered is that we got at least one jail name. + * Arbitrarily lets make that jail1 if we only have 1 jail name. + * After this we know we have to do jail1 and may have to deal + * with jail2. + */ + if (jail1 == NULL) { + const char *tmp; +# define SWAP(cp1, cp2) \ + do { tmp = cp1; cp1 = cp2; cp2 = tmp; } while(0) + SWAP(jail2, jail1); + SWAP(name2, name1); + SWAP(node2, node1); + SWAP(hook2, hook1); +# undef SWAP + } + + /* + * if we have a jail to switch to it must be before we look up jids + * for wormhole specs + */ + if (jail != NULL) { + int jid = jail_getid(jail); + + if (jid == -1) + errx(ERRALT(EX_NOHOST), "%s", jail_errmsg); + if (jail_attach(jid) != 0) + errx(ERRALT(EX_OSERR), "cannot attach to jail"); + } + + /* comparing jail names won't work, one could be given numerically */ + jid1 = jail_getid(jail1); + if (jid1 == -1) + errx(ERRALT(EX_NOHOST), "%s", jail_errmsg); + if (jail2 != NULL) { + jid2 = jail_getid(jail2); + if (jid2 == -1) + errx(ERRALT(EX_NOHOST), "%s", jail_errmsg); + } + + /* + * the kernel code specifically prevents this too, but this is a bettter + * message than just "invalid argument". + */ + if (jid1 == jid2) + Usage( ME ": duplicate jail reference detected\n\n"); + + /* + * Unless told not to (or we definitely are in a jail) try to make sure + * we have modules loaded. + */ + if (load_kmod != 0 && jail == NULL) { + kld_ensure_load("ng_socket"); + kld_ensure_load("ng_wormhole"); + } + + /* set up error cleanup */ + err_set_exit(err_cleanup); + + /* open netgraph socket */ + G.fd = ng_create_context(); + + /* this one is always created and opened etc. */ + G.wh1a = ng_wh_create(G.fd); + wh1b = ng_wh_open(G.fd, G.wh1a, jail1); + jail_name_connect(jid1, wh1b, name1, node1, hook1); + + if (jid2 != 0) { + /* + * In this case we have to make another wormhole. + * Then we have to collapse it with wh2a. + */ + char pth[NG_NODESIZ]; + G.wh2a = ng_wh_create(G.fd); + wh2b = ng_wh_open(G.fd, G.wh2a, jail2); + jail_name_connect(jid2, wh2b, name2, node2, hook2); + + /* not IDFMT, it can't have ':' on end */ + snprintf(pth, sizeof(pth), "[%08x]", G.wh2a); + ng_wh_connect(G.fd, G.wh1a, pth, NG_WORMHOLE_HOOK); + } else { + /* just deal with connecting in current vnet */ + ng_wh_name(G.fd, G.wh1a, name2); + ng_wh_connect(G.fd, G.wh1a, node2, hook2); + } + + return (0); +}