/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023, 2025 Mark Johnston * * This software was developed by the University of Cambridge Computer * Laboratory (Department of Computer Science and Technology) under Innovate * UK project 105694, "Digital Security by Design (DSbD) Technology Platform * Prototype". * * 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. */ /* * The slirp backend enables unprivileged userspace networking via libslirp, * which must be installed on the host system via pkg or the ports tree. * libslirp.so is dlopen()ed into a helper process with which this backend * communicates. * * Packets received from the guest (i.e., transmitted by the frontend, such as a * virtio NIC device model) are injected into the slirp backend via slirp_send(), * which sends the packet to the helper process. * * Packets to be transmitted to the guest (i.e., inserted into the frontend's * receive buffers) are buffered in a per-interface socket pair and read by the * mevent loop. Sockets instantiated by libslirp are monitored by a thread * which uses poll() and slirp_pollfds_poll() to drive libslirp events; this * thread also handles timeout events from the libslirp context. */ #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "debug.h" #include "mevent.h" #include "net_utils.h" #include "net_backends.h" #include "net_backends_priv.h" #define DEFAULT_MTU 2048 struct slirp_priv { int s; pid_t helper; struct mevent *mevp; size_t mtu; uint8_t *buf; }; extern char **environ; static int slirp_init(struct net_backend *be, const char *devname __unused, nvlist_t *nvl, net_be_rxeof_t cb, void *param) { 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]; const char *mtu_value; size_t mtu; if (socketpair(PF_LOCAL, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, s) != 0) { EPRINTLN("socketpair"); 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"); 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; } } else { mtu = DEFAULT_MTU; } nvlist_add_number(config, "mtui", mtu); priv->mtu = mtu; priv->buf = malloc(mtu); if (priv->buf == NULL) { EPRINTLN("Could not allocate buffer"); goto err; } nvlist_add_string(config, "vmname", get_config_value("name")); error = nvlist_send(s[0], config); nvlist_destroy(config); if (error != 0) { EPRINTLN("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"); goto err; } priv->helper = child; priv->s = s[0]; (void)close(s[1]); return (0); err: free(priv->buf); (void)close(s[0]); (void)close(s[1]); return (-1); } static ssize_t slirp_send(struct net_backend *be, const struct iovec *iov, int iovcnt) { struct slirp_priv *priv = NET_BE_PRIV(be); struct msghdr hdr; memset(&hdr, 0, sizeof(hdr)); hdr.msg_iov = __DECONST(struct iovec *, iov); hdr.msg_iovlen = iovcnt; return (sendmsg(priv->s, &hdr, MSG_EOR)); } static void slirp_cleanup(struct net_backend *be) { struct slirp_priv *priv = NET_BE_PRIV(be); free(priv->buf); if (priv->helper > 0) { int status; if (kill(priv->helper, SIGKILL) != 0) { EPRINTLN("kill(bhyve-slirp-helper): %s", strerror(errno)); return; } (void)waitpid(priv->helper, &status, 0); } } static ssize_t slirp_peek_recvlen(struct net_backend *be) { struct slirp_priv *priv = NET_BE_PRIV(be); ssize_t n; /* * Copying into the buffer is totally unnecessary, but we don't * implement MSG_TRUNC for SEQPACKET sockets. */ n = recv(priv->s, priv->buf, priv->mtu, MSG_PEEK | MSG_DONTWAIT); if (n < 0) return (errno == EWOULDBLOCK ? 0 : -1); return (n); } static ssize_t slirp_recv(struct net_backend *be, const struct iovec *iov, int iovcnt) { struct slirp_priv *priv = NET_BE_PRIV(be); struct msghdr hdr; ssize_t n; hdr.msg_name = NULL; hdr.msg_namelen = 0; hdr.msg_iov = __DECONST(struct iovec *, iov); hdr.msg_iovlen = iovcnt; hdr.msg_control = NULL; hdr.msg_controllen = 0; hdr.msg_flags = 0; n = recvmsg(priv->s, &hdr, MSG_DONTWAIT); if (n < 0) { if (errno == EWOULDBLOCK) return (0); return (-1); } assert((size_t)n <= priv->mtu); return (n); } static void slirp_recv_enable(struct net_backend *be) { struct slirp_priv *priv = NET_BE_PRIV(be); mevent_enable(priv->mevp); } static void slirp_recv_disable(struct net_backend *be) { struct slirp_priv *priv = NET_BE_PRIV(be); mevent_disable(priv->mevp); } static uint64_t slirp_get_cap(struct net_backend *be __unused) { return (0); } static int slirp_set_cap(struct net_backend *be __unused, uint64_t features __unused, unsigned int vnet_hdr_len __unused) { return ((features || vnet_hdr_len) ? -1 : 0); } static struct net_backend slirp_backend = { .prefix = "slirp", .priv_size = sizeof(struct slirp_priv), .init = slirp_init, .cleanup = slirp_cleanup, .send = slirp_send, .peek_recvlen = slirp_peek_recvlen, .recv = slirp_recv, .recv_enable = slirp_recv_enable, .recv_disable = slirp_recv_disable, .get_cap = slirp_get_cap, .set_cap = slirp_set_cap, }; DATA_SET(net_backend_set, slirp_backend);