diff --git a/lib/libbhyve/Makefile b/lib/libbhyve/Makefile --- a/lib/libbhyve/Makefile +++ b/lib/libbhyve/Makefile @@ -3,6 +3,7 @@ PACKAGE=lib${LIB} LIB= bhyve SRCS= config.c \ + net_backends.c \ pci.c INCS= bhyve/config.h \ bhyve/net_backends.h \ diff --git a/lib/libbhyve/net_backends.c b/lib/libbhyve/net_backends.c new file mode 100644 --- /dev/null +++ b/lib/libbhyve/net_backends.c @@ -0,0 +1,364 @@ +/* + * + * Copyright (c) 2019 Vincenzo Maffione + * Copyright (c) 2023 Mark Johnston + * Copyright (c) 2025 Bojan Novković + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#define NETMAP_WITH_LIBS +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bhyve/config.h" +#include "bhyve/net_backends.h" +#include "bhyve/pci.h" +#include "internal.h" + +struct netbe_info { + const char *prefix; + int (*init_fds)(nvlist_t *); + bool (*validate_hotplug_request)(nvlist_t *); +}; + +extern char **environ; + +static int +slirp_init_fds(nvlist_t *nvl) +{ + int error; + pid_t child; + int child_pd; + const char **argv; + char sockname[32]; + int sockpair_fds[2]; + posix_spawnattr_t attr; + posix_spawn_file_actions_t fa; + + error = socketpair(PF_LOCAL, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, + sockpair_fds); + if (error != 0) { + nvlist_add_stringf(nvl, "error", "Unable to create pipe: %s", + strerror(errno)); + return (error); + } + + /* + * The child will exit once its connection goes away, so make sure only + * one end is inherited by the child. + */ + if (posix_spawn_file_actions_init(&fa) != 0) { + nvlist_add_string(nvl, "error", + "posix_spawn_file_actions_init"); + return (error); + } + if (posix_spawn_file_actions_addclose(&fa, sockpair_fds[0]) != 0) { + nvlist_add_string(nvl, "error", + "posix_spawn_file_actions_addclose"); + posix_spawn_file_actions_destroy(&fa); + return (error); + } + if (posix_spawnattr_init(&attr) != 0) { + nvlist_add_string(nvl, "error", "posix_spawnattr_init"); + posix_spawn_file_actions_destroy(&fa); + return (error); + } + if (posix_spawnattr_setprocdescp_np(&attr, &child_pd, PD_CLOEXEC) != 0) { + nvlist_add_string(nvl, "error", + "posix_spawnattr_setprocdescp_np"); + posix_spawn_file_actions_destroy(&fa); + posix_spawnattr_destroy(&attr); + return (error); + } + (void)snprintf(sockname, sizeof(sockname), "%d", sockpair_fds[1]); + argv = (const char *[]){ + "/usr/libexec/bhyve-slirp-helper", "-S", sockname, NULL + }; + child_pd = -1; + error = posix_spawn(&child, "/usr/libexec/bhyve-slirp-helper", + &fa, &attr, __DECONST(char **, argv), environ); + posix_spawn_file_actions_destroy(&fa); + posix_spawnattr_destroy(&attr); + if (error != 0) { + nvlist_add_stringf(nvl, "error", "posix_spawn(bhyve-slirp-helper): %s", + strerror(error)); + return (error); + } + assert(child_pd != -1); + + nvlist_add_descriptor_array(nvl, "sockpair", sockpair_fds, + nitems(sockpair_fds)); + nvlist_add_descriptor(nvl, "helper_pd", child_pd); + + return (0); +} + +static bool +slirp_validate_hotplug_request(nvlist_t *nvl) +{ + if (!nvlist_exists_descriptor_array(nvl, "sockpair")) { + nvlist_add_string(nvl, "error", + "missing socket descriptor pair"); + return (false); + } + + if (!nvlist_exists_descriptor(nvl, "helper_pd")) { + nvlist_add_string(nvl, "error", + "missing slirp helper process descriptor"); + return (false); + } + + return (true); +} + +static int +tap_init_fds(nvlist_t *nvl) +{ + int fd; + char tbuf[80]; + const char *devname; + + if (!nvlist_exists_string(nvl, "backend")) { + nvlist_add_string(nvl, "error", "missing backend parameter"); + return (-1); + } + + devname = nvlist_get_string(nvl, "backend"); + strcpy(tbuf, "/dev/"); + strlcat(tbuf, devname, sizeof(tbuf)); + fd = open(tbuf, O_RDWR); + if (fd == -1) { + nvlist_add_stringf(nvl, "error", + "open of tap device %s failed: %s", tbuf, strerror(errno)); + return (-1); + } + nvlist_move_descriptor(nvl, "devfd", fd); + + return (0); +} + +static bool +tap_validate_hotplug_request(nvlist_t *nvl) +{ + if (!nvlist_exists_descriptor(nvl, "devfd")) { + nvlist_add_string(nvl, "error", + "missing tap device file descriptor"); + return (false); + } + return (true); +} + +static int +ng_init_fds(nvlist_t *nvl) +{ + int csp, dsp; + const char *nodename; + + if (!nvlist_exists_string(nvl, "socket")) { + nvlist_add_string(nvl, "error", "missing socket path"); + return (-1); + } + nodename = nvlist_get_string(nvl, "socket"); + if (NgMkSockNode(nodename, &csp, &dsp) < 0) { + nvlist_add_stringf(nvl, "error", "can't get Netgraph sockets"); + return (-1); + } + + nvlist_move_descriptor(nvl, "csp", csp); + nvlist_move_descriptor(nvl, "dsp", dsp); + + return (0); +} + +static bool +ng_validate_hotplug_request(nvlist_t *nvl) +{ + if (!nvlist_exists_descriptor(nvl, "csp")) { + nvlist_add_string(nvl, "error", + "missing control socket descriptor"); + return (false); + } + if (!nvlist_exists_descriptor(nvl, "dsp")) { + nvlist_add_string(nvl, "error", + "missing data socket descriptor"); + return (false); + } + + return (true); +} + +static int +netmap_init_fds(nvlist_t *nvl) +{ + struct nm_desc *nmd; + const char *devname; + + if (!nvlist_exists_string(nvl, "backend")) { + nvlist_add_string(nvl, "error", "missing backend parameter"); + return (-1); + } + devname = nvlist_get_string(nvl, "backend"); + nmd = nm_open(devname, NULL, NETMAP_NO_TX_POLL, NULL); + if (nmd == NULL) { + nvlist_add_stringf(nvl, "error", + "Unable to nm_open(): interface '%s', errno (%s)", devname, + strerror(errno)); + return (-1); + } + nvlist_move_binary(nvl, "nm_desc", nmd, sizeof(*nmd)); + + return (0); +} + +static bool +netmap_validate_hotplug_request(nvlist_t *nvl) +{ + size_t size; + struct nm_desc *nmd; + + if (!nvlist_exists_binary(nvl, "nm_desc")) { + nvlist_add_string(nvl, "error", + "missing netmap descriptor structure"); + return (false); + } + + (void)nvlist_get_binary(nvl, "nm_desc", &size); + if (size != sizeof(*nmd)) { + nvlist_add_string(nvl, "error", + "mismatched netmap descriptor structure size"); + return (false); + } + + return (true); +} + +static struct netbe_info netbe_tap = { + .prefix = "tap", + .init_fds = tap_init_fds, + .validate_hotplug_request = tap_validate_hotplug_request +}; + +static struct netbe_info netbe_ngd = { + .prefix = "ngd", + .init_fds = tap_init_fds, + .validate_hotplug_request = tap_validate_hotplug_request +}; + +static struct netbe_info netbe_netmap = { + .prefix = "netmap", + .init_fds = netmap_init_fds, + .validate_hotplug_request = netmap_validate_hotplug_request +}; + +static struct netbe_info netbe_netgraph = { + .prefix = "netgraph", + .init_fds = ng_init_fds, + .validate_hotplug_request = ng_validate_hotplug_request +}; + +static struct netbe_info netbe_slirp = { + .prefix = "slirp", + .init_fds = slirp_init_fds, + .validate_hotplug_request = slirp_validate_hotplug_request +}; + +SET_DECLARE(net_backends_info, struct netbe_info); +DATA_SET(net_backends_info, netbe_netgraph); +DATA_SET(net_backends_info, netbe_slirp); +DATA_SET(net_backends_info, netbe_netmap); +DATA_SET(net_backends_info, netbe_tap); +DATA_SET(net_backends_info, netbe_ngd); + +static struct netbe_info * +find_netbe_info(const char *backend) +{ + struct netbe_info **nbe; + + SET_FOREACH(nbe, net_backends_info) { + if (strncmp(backend, (*nbe)->prefix, + strlen((*nbe)->prefix)) == 0) { + return (*nbe); + } + } + return (NULL); +} + +int +netbe_init_fds(nvlist_t *nvl) +{ + const char *backend; + struct netbe_info *nbi; + + if (!nvlist_exists_string(nvl, "backend")) { + nvlist_add_string(nvl, "error", "missing backend argument"); + return (-1); + } + backend = nvlist_get_string(nvl, "backend"); + nbi = find_netbe_info(backend); + if (nbi == NULL) { + nvlist_add_stringf(nvl, "error", "unknown backend '%s'", + backend); + return (-1); + } + + if (nbi->init_fds == NULL) + return (0); + + return (nbi->init_fds(nvl)); +} + +int +netbe_legacy_config(nvlist_t *nvl, const char *opts) +{ + char *backend, *cp; + + if (opts == NULL) + return (0); + + cp = strchr(opts, ','); + if (cp == NULL) { + set_config_value_node(nvl, "backend", opts); + return (0); + } + backend = strndup(opts, cp - opts); + set_config_value_node(nvl, "backend", backend); + free(backend); + return (pci_parse_legacy_config(nvl, cp + 1)); +} + +bool +netbe_validate_hotplug_request(nvlist_t *nvl) +{ + const char *backend; + struct netbe_info *nbi; + + if (!nvlist_exists_string(nvl, "backend")) { + nvlist_add_string(nvl, "error", "missing backend argument"); + return (-1); + } + backend = nvlist_get_string(nvl, "backend"); + nbi = find_netbe_info(backend); + + if (nbi == NULL || nbi->validate_hotplug_request == NULL) + return (0); + + return (nbi->validate_hotplug_request(nvl)); +} 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 @@ -53,7 +53,7 @@ { struct tap_priv *p = NET_BE_PRIV(be); struct ngm_connect ngc; - const char *value, *nodename; + const char *value; int sbsz; int ctrl_sock; int flags; @@ -74,7 +74,7 @@ value = get_config_value_node(nvl, "path"); if (value == NULL) { - EPRINTLN("path must be provided"); + nvlist_add_string(nvl, "error", "path must be provided"); return (-1); } strncpy(ngc.path, value, NG_PATHSIZ - 1); @@ -86,22 +86,18 @@ value = get_config_value_node(nvl, "peerhook"); if (value == NULL) { - EPRINTLN("peer hook must be provided"); + nvlist_add_string(nvl, "error", "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) { - EPRINTLN("can't get Netgraph sockets"); - return (-1); - } + ctrl_sock = nvlist_take_descriptor(nvl, "csp"); + be->fd = nvlist_take_descriptor(nvl, "dsp"); if (NgSendMsg(ctrl_sock, ".", NGM_GENERIC_COOKIE, NGM_CONNECT, &ngc, sizeof(ngc)) < 0) { - EPRINTLN("can't connect to node"); + nvlist_add_string(nvl, "error", "can't connect to node"); close(ctrl_sock); goto error; } @@ -111,12 +107,12 @@ flags = fcntl(be->fd, F_GETFL); if (flags < 0) { - EPRINTLN("can't get socket flags"); + nvlist_add_string(nvl, "error", "can't get socket flags"); goto error; } if (fcntl(be->fd, F_SETFL, flags | O_NONBLOCK) < 0) { - EPRINTLN("can't set O_NONBLOCK flag"); + nvlist_add_string(nvl, "error", "can't set O_NONBLOCK flag"); goto error; } @@ -128,7 +124,8 @@ msbsz = sizeof(maxsbsz); if (sysctlbyname("kern.ipc.maxsockbuf", &maxsbsz, &msbsz, NULL, 0) < 0) { - EPRINTLN("can't get 'kern.ipc.maxsockbuf' value"); + nvlist_add_string(nvl, "error", + "can't get 'kern.ipc.maxsockbuf' value"); goto error; } @@ -142,13 +139,13 @@ if (setsockopt(be->fd, SOL_SOCKET, SO_SNDBUF, &sbsz, sizeof(sbsz)) < 0) { - EPRINTLN("can't set TX buffer size"); + nvlist_add_string(nvl, "error", "can't set TX buffer size"); goto error; } if (setsockopt(be->fd, SOL_SOCKET, SO_RCVBUF, &sbsz, sizeof(sbsz)) < 0) { - EPRINTLN("can't set RX buffer size"); + nvlist_add_string(nvl, "error", "can't set RX buffer size"); goto error; } @@ -163,7 +160,7 @@ p->mevp = mevent_add_disabled(be->fd, EVF_READ, cb, param); if (p->mevp == NULL) { - EPRINTLN("Could not register event"); + nvlist_add_string(nvl, "error", "Could not register event"); goto error; } diff --git a/usr.sbin/bhyve/net_backend_netmap.c b/usr.sbin/bhyve/net_backend_netmap.c --- a/usr.sbin/bhyve/net_backend_netmap.c +++ b/usr.sbin/bhyve/net_backend_netmap.c @@ -123,21 +123,15 @@ } static int -netmap_init(struct net_backend *be, const char *devname, - nvlist_t *nvl __unused, net_be_rxeof_t cb, void *param) +netmap_init(struct net_backend *be, const char *devname, nvlist_t *nvl, + net_be_rxeof_t cb, void *param) { struct netmap_priv *priv = NET_BE_PRIV(be); + size_t size; strlcpy(priv->ifname, devname, sizeof(priv->ifname)); priv->ifname[sizeof(priv->ifname) - 1] = '\0'; - - priv->nmd = nm_open(priv->ifname, NULL, NETMAP_NO_TX_POLL, NULL); - if (priv->nmd == NULL) { - EPRINTLN("Unable to nm_open(): interface '%s', errno (%s)", - devname, strerror(errno)); - return (-1); - } - + priv->nmd = nvlist_take_binary(nvl, "nm_desc", &size); priv->memid = priv->nmd->req.nr_arg2; priv->tx = NETMAP_TXRING(priv->nmd->nifp, 0); priv->rx = NETMAP_RXRING(priv->nmd->nifp, 0); @@ -147,7 +141,7 @@ priv->mevp = mevent_add_disabled(be->fd, EVF_READ, cb, param); if (priv->mevp == NULL) { - EPRINTLN("Could not register event"); + nvlist_add_string(nvl, "error", "Could not register event"); return (-1); } diff --git a/usr.sbin/bhyve/net_backend_slirp.c b/usr.sbin/bhyve/net_backend_slirp.c --- a/usr.sbin/bhyve/net_backend_slirp.c +++ b/usr.sbin/bhyve/net_backend_slirp.c @@ -47,6 +47,7 @@ * thread also handles timeout events from the libslirp context. */ +#include #include #include @@ -84,57 +85,31 @@ { struct slirp_priv *priv = NET_BE_PRIV(be); nvlist_t *config; - posix_spawn_file_actions_t fa; pid_t child; - const char **argv; - char sockname[32]; - int error, s[2]; + int error, *s; + size_t mtu, nitems; const char *mtu_value; - size_t mtu; + int helper_pd; - if (socketpair(PF_LOCAL, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, s) != 0) { - EPRINTLN("socketpair"); + s = nvlist_take_descriptor_array(nvl, "sockpair", &nitems); + if (nitems != 2) { + nvlist_add_string(nvl, "error", + "mismatched number of socket descriptors"); return (-1); } - /* - * The child will exit once its connection goes away, so make sure only - * one end is inherited by the child. - */ - if (posix_spawn_file_actions_init(&fa) != 0) { - EPRINTLN("posix_spawn_file_actions_init"); - goto err; - } - if (posix_spawn_file_actions_addclose(&fa, s[0]) != 0) { - EPRINTLN("posix_spawn_file_actions_addclose"); - posix_spawn_file_actions_destroy(&fa); - goto err; - } - - (void)snprintf(sockname, sizeof(sockname), "%d", s[1]); - argv = (const char *[]){ - "/usr/libexec/bhyve-slirp-helper", "-S", sockname, NULL - }; - error = posix_spawn(&child, "/usr/libexec/bhyve-slirp-helper", - &fa, NULL, __DECONST(char **, argv), environ); - posix_spawn_file_actions_destroy(&fa); - if (error != 0) { - EPRINTLN("posix_spawn(bhyve-slirp-helper): %s", - strerror(error)); - goto err; - } - config = nvlist_clone(nvl); if (config == NULL) { - EPRINTLN("nvlist_clone"); + nvlist_add_string(nvl, "error", + "nvlist_clone"); goto err; } mtu_value = get_config_value_node(config, "mtu"); if (mtu_value != NULL) { if (net_parsemtu(mtu_value, &mtu)) { - EPRINTLN("Could not parse MTU"); - goto err; + nvlist_add_string(nvl, "error", "Could not parse MTU"); + goto err; } } else { mtu = DEFAULT_MTU; @@ -144,7 +119,7 @@ priv->mtu = mtu; priv->buf = malloc(mtu); if (priv->buf == NULL) { - EPRINTLN("Could not allocate buffer"); + nvlist_add_string(nvl, "error", "Could not allocate MTU buffer"); goto err; } @@ -152,14 +127,21 @@ error = nvlist_send(s[0], config); nvlist_destroy(config); if (error != 0) { - EPRINTLN("nvlist_send"); + nvlist_add_string(nvl, "error", + "nvlist_send"); goto err; } be->fd = s[0]; priv->mevp = mevent_add_disabled(be->fd, EVF_READ, cb, param); if (priv->mevp == NULL) { - EPRINTLN("Could not register event"); + nvlist_add_string(nvl, "error", "Could not register event"); + goto err; + } + helper_pd = nvlist_take_descriptor(nvl, "helper_pd"); + if (pdgetpid(helper_pd, &child) != 0) { + nvlist_add_string(nvl, "error", + "pdgetpid failed"); goto err; } diff --git a/usr.sbin/bhyve/net_backends.c b/usr.sbin/bhyve/net_backends.c --- a/usr.sbin/bhyve/net_backends.c +++ b/usr.sbin/bhyve/net_backends.c @@ -85,11 +85,10 @@ } static int -tap_init(struct net_backend *be, const char *devname, - nvlist_t *nvl __unused, net_be_rxeof_t cb, void *param) +tap_init(struct net_backend *be, const char *devname __unused, nvlist_t *nvl, + net_be_rxeof_t cb, void *param) { struct tap_priv *priv = NET_BE_PRIV(be); - char tbuf[80]; int opt = 1, up = IFF_UP; #ifndef WITHOUT_CAPSICUM @@ -97,31 +96,25 @@ #endif if (cb == NULL) { - EPRINTLN("TAP backend requires non-NULL callback"); + nvlist_add_string(nvl, "error", + "TAP backend requires non-NULL callback"); return (-1); } - strcpy(tbuf, "/dev/"); - strlcat(tbuf, devname, sizeof(tbuf)); - - be->fd = open(tbuf, O_RDWR); - if (be->fd == -1) { - EPRINTLN("open of tap device %s failed", tbuf); - goto error; - } + be->fd = nvlist_take_descriptor(nvl, "devfd"); /* * Set non-blocking and register for read * notifications with the event loop */ if (ioctl(be->fd, FIONBIO, &opt) < 0) { - EPRINTLN("tap device O_NONBLOCK failed"); + nvlist_add_string(nvl, "error", "tap device O_NONBLOCK failed"); goto error; } if (strncmp("ngd", be->prefix, 3) && ioctl(be->fd, VMIO_SIOCSIFFLAGS, up)) { - EPRINTLN("tap device link up failed"); + nvlist_add_string(nvl, "error", "tap device link up failed"); goto error; } @@ -136,7 +129,7 @@ priv->mevp = mevent_add_disabled(be->fd, EVF_READ, cb, param); if (priv->mevp == NULL) { - EPRINTLN("Could not register event"); + nvlist_add_string(nvl, "error", "Could not register event"); goto error; } @@ -293,25 +286,6 @@ DATA_SET(net_backend_set, vmnet_backend); DATA_SET(net_backend_set, ngd_backend); -int -netbe_legacy_config(nvlist_t *nvl, const char *opts) -{ - char *backend, *cp; - - if (opts == NULL) - return (0); - - cp = strchr(opts, ','); - if (cp == NULL) { - set_config_value_node(nvl, "backend", opts); - return (0); - } - backend = strndup(opts, cp - opts); - set_config_value_node(nvl, "backend", backend); - free(backend); - return (pci_parse_legacy_config(nvl, cp + 1)); -} - /* * Initialize a backend and attach to the frontend. * This is called during frontend initialization. @@ -335,6 +309,7 @@ value = get_config_value_node(nvl, "backend"); if (value == NULL) { + nvlist_add_string(nvl, "error", "missing backend argument"); return (-1); } devname = strdup(value); @@ -367,6 +342,7 @@ *ret = NULL; if (tbe == NULL) { + nvlist_add_stringf(nvl, "error", "unknown backend '%s'", type); free(devname); return (EINVAL); }