diff options
Diffstat (limited to 'usr.sbin/bhyve/slirp/slirp-helper.c')
| -rw-r--r-- | usr.sbin/bhyve/slirp/slirp-helper.c | 570 |
1 files changed, 570 insertions, 0 deletions
diff --git a/usr.sbin/bhyve/slirp/slirp-helper.c b/usr.sbin/bhyve/slirp/slirp-helper.c new file mode 100644 index 000000000000..06f393aab724 --- /dev/null +++ b/usr.sbin/bhyve/slirp/slirp-helper.c @@ -0,0 +1,570 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023, 2025 Mark Johnston <markj@FreeBSD.org> + * + * 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". + */ + +/* + * A helper process which lets bhyve's libslirp-based network backend work + * outside bhyve's Capsicum sandbox. We are started with a SOCK_SEQPACKET + * socket through which we pass and receive packets from the guest's frontend. + * + * At initialization time, we receive an nvlist over the socket which describes + * the desired slirp configuration. + */ + +#include <sys/nv.h> +#include <sys/socket.h> + +#include <assert.h> +#include <capsicum_helpers.h> +#include <dlfcn.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "config.h" +#include "libslirp.h" + +#define SLIRP_MTU 2048 + +struct slirp_priv { + Slirp *slirp; /* libslirp handle */ + int sock; /* data and control socket */ + int wakeup[2]; /* used to wake up the pollfd thread */ + struct pollfd *pollfds; + size_t npollfds; + size_t lastpollfd; +}; + +typedef int (*slirp_add_hostxfwd_p_t)(Slirp *, + const struct sockaddr *, socklen_t, const struct sockaddr *, socklen_t, + int); +typedef void (*slirp_cleanup_p_t)(Slirp *); +typedef void (*slirp_input_p_t)(Slirp *, const uint8_t *, int); +typedef Slirp *(*slirp_new_p_t)(const SlirpConfig *, const SlirpCb *, void *); +typedef void (*slirp_pollfds_fill_p_t)(Slirp *, uint32_t *timeout, + SlirpAddPollCb, void *); +typedef void (*slirp_pollfds_poll_p_t)(Slirp *, int, SlirpGetREventsCb, void *); + +/* Function pointer table, initialized by libslirp_init(). */ +static slirp_add_hostxfwd_p_t slirp_add_hostxfwd_p; +static slirp_cleanup_p_t slirp_cleanup_p; +static slirp_input_p_t slirp_input_p; +static slirp_new_p_t slirp_new_p; +static slirp_pollfds_fill_p_t slirp_pollfds_fill_p; +static slirp_pollfds_poll_p_t slirp_pollfds_poll_p; + +static int64_t +slirp_cb_clock_get_ns(void *param __unused) +{ + struct timespec ts; + int error; + + error = clock_gettime(CLOCK_MONOTONIC, &ts); + assert(error == 0); + return ((int64_t)(ts.tv_sec * 1000000000L + ts.tv_nsec)); +} + +static void +slirp_cb_notify(void *param) +{ + struct slirp_priv *priv; + + /* Wake up the poll thread. We assume that priv->mtx is held here. */ + priv = param; + (void)write(priv->wakeup[1], "M", 1); +} + +static void +slirp_cb_register_poll_fd(int fd, void *param __unused) +{ + const int one = 1; + + (void)setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(int)); +} + +static ssize_t +slirp_cb_send_packet(const void *buf, size_t len, void *param) +{ + struct slirp_priv *priv; + ssize_t n; + + priv = param; + + assert(len <= SLIRP_MTU); + n = send(priv->sock, buf, len, MSG_EOR); + if (n < 0) { + warn("slirp_cb_send_packet: send"); + return (n); + } + assert((size_t)n == len); + + return (n); +} + +static void +slirp_cb_unregister_poll_fd(int fd __unused, void *opaque __unused) +{ +} + +/* Callbacks invoked from within libslirp. */ +static const struct SlirpCb slirp_cbs = { + .clock_get_ns = slirp_cb_clock_get_ns, + .notify = slirp_cb_notify, + .register_poll_fd = slirp_cb_register_poll_fd, + .send_packet = slirp_cb_send_packet, + .unregister_poll_fd = slirp_cb_unregister_poll_fd, +}; + +static int +slirpev2pollev(int events) +{ + int ret; + + ret = 0; + if (events & SLIRP_POLL_IN) + ret |= POLLIN; + if (events & SLIRP_POLL_OUT) + ret |= POLLOUT; + if (events & SLIRP_POLL_PRI) + ret |= POLLPRI; + if (events & SLIRP_POLL_ERR) + ret |= POLLERR; + if (events & SLIRP_POLL_HUP) + ret |= POLLHUP; + return (ret); +} + +static int +pollev2slirpev(int events) +{ + int ret; + + ret = 0; + if (events & POLLIN) + ret |= SLIRP_POLL_IN; + if (events & POLLOUT) + ret |= SLIRP_POLL_OUT; + if (events & POLLPRI) + ret |= SLIRP_POLL_PRI; + if (events & POLLERR) + ret |= SLIRP_POLL_ERR; + if (events & POLLHUP) + ret |= SLIRP_POLL_HUP; + return (ret); +} + +static int +slirp_addpoll(struct slirp_priv *priv, int fd, int events) +{ + struct pollfd *pollfd, *pollfds; + size_t i; + + for (i = priv->lastpollfd + 1; i < priv->npollfds; i++) + if (priv->pollfds[i].fd == -1) + break; + if (i == priv->npollfds) { + const size_t POLLFD_GROW = 4; + + priv->npollfds += POLLFD_GROW; + pollfds = realloc(priv->pollfds, + sizeof(*pollfds) * priv->npollfds); + if (pollfds == NULL) + return (-1); + for (i = priv->npollfds - POLLFD_GROW; i < priv->npollfds; i++) + pollfds[i].fd = -1; + priv->pollfds = pollfds; + + i = priv->npollfds - POLLFD_GROW; + } + pollfd = &priv->pollfds[i]; + pollfd->fd = fd; + pollfd->events = slirpev2pollev(events); + pollfd->revents = 0; + priv->lastpollfd = i; + + return ((int)i); +} + +static int +slirp_addpoll_cb(int fd, int events, void *param) +{ + struct slirp_priv *priv; + + priv = param; + return (slirp_addpoll(priv, fd, events)); +} + +static int +slirp_poll_revents(int idx, void *param) +{ + struct slirp_priv *priv; + struct pollfd *pollfd; + short revents; + + priv = param; + assert(idx >= 0); + assert((unsigned int)idx < priv->npollfds); + pollfd = &priv->pollfds[idx]; + assert(pollfd->fd != -1); + + /* The kernel may report POLLHUP even if we didn't ask for it. */ + revents = pollfd->revents; + if ((pollfd->events & POLLHUP) == 0) + revents &= ~POLLHUP; + return (pollev2slirpev(revents)); +} + +/* + * Main loop. Poll libslirp's descriptors plus a couple of our own. + */ +static void +slirp_pollfd_loop(struct slirp_priv *priv) +{ + struct pollfd *pollfds; + size_t npollfds; + uint32_t timeout; + int error; + + for (;;) { + int input, wakeup; + + for (size_t i = 0; i < priv->npollfds; i++) + priv->pollfds[i].fd = -1; + priv->lastpollfd = -1; + + /* Register for notifications from slirp_cb_notify(). */ + wakeup = slirp_addpoll(priv, priv->wakeup[0], POLLIN); + /* Register for input from our parent process. */ + input = slirp_addpoll(priv, priv->sock, POLLIN | POLLRDHUP); + + timeout = UINT32_MAX; + slirp_pollfds_fill_p(priv->slirp, &timeout, slirp_addpoll_cb, + priv); + + pollfds = priv->pollfds; + npollfds = priv->npollfds; + error = poll(pollfds, npollfds, timeout); + if (error == -1 && errno != EINTR) + err(1, "poll"); + slirp_pollfds_poll_p(priv->slirp, error == -1, + slirp_poll_revents, priv); + + /* + * If we were woken up by the notify callback, mask the + * interrupt. + */ + if ((pollfds[wakeup].revents & POLLIN) != 0) { + ssize_t n; + + do { + uint8_t b; + + n = read(priv->wakeup[0], &b, 1); + } while (n == 1); + if (n != -1 || errno != EAGAIN) + err(1, "read"); + } + + /* + * If new packets arrived from our parent, feed them to + * libslirp. + */ + if ((pollfds[input].revents & (POLLHUP | POLLRDHUP)) != 0) + errx(1, "parent process closed connection"); + if ((pollfds[input].revents & POLLIN) != 0) { + ssize_t n; + + do { + uint8_t buf[SLIRP_MTU]; + + n = recv(priv->sock, buf, sizeof(buf), + MSG_DONTWAIT); + if (n < 0) { + if (errno == EWOULDBLOCK) + break; + err(1, "recv"); + } + slirp_input_p(priv->slirp, buf, (int)n); + } while (n >= 0); + } + } +} + +static int +parse_addr(char *addr, struct sockaddr_in *sinp) +{ + char *port; + int error, porti; + + memset(sinp, 0, sizeof(*sinp)); + sinp->sin_family = AF_INET; + sinp->sin_len = sizeof(struct sockaddr_in); + + port = strchr(addr, ':'); + if (port == NULL) + return (EINVAL); + *port++ = '\0'; + + if (strlen(addr) > 0) { + error = inet_pton(AF_INET, addr, &sinp->sin_addr); + if (error != 1) + return (error == 0 ? EPFNOSUPPORT : errno); + } else { + sinp->sin_addr.s_addr = htonl(INADDR_ANY); + } + + porti = strlen(port) > 0 ? atoi(port) : 0; + if (porti < 0 || porti > UINT16_MAX) + return (EINVAL); + sinp->sin_port = htons(porti); + + return (0); +} + +static int +parse_hostfwd_rule(const char *descr, int *is_udp, struct sockaddr *hostaddr, + struct sockaddr *guestaddr) +{ + struct sockaddr_in *hostaddrp, *guestaddrp; + const char *proto; + char *p, *host, *guest; + int error; + + error = 0; + *is_udp = 0; + + p = strdup(descr); + if (p == NULL) + return (ENOMEM); + + host = strchr(p, ':'); + if (host == NULL) { + error = EINVAL; + goto out; + } + *host++ = '\0'; + + proto = p; + *is_udp = strcmp(proto, "udp") == 0; + + guest = strchr(host, '-'); + if (guest == NULL) { + error = EINVAL; + goto out; + } + *guest++ = '\0'; + + hostaddrp = (struct sockaddr_in *)(void *)hostaddr; + error = parse_addr(host, hostaddrp); + if (error != 0) + goto out; + + guestaddrp = (struct sockaddr_in *)(void *)guestaddr; + error = parse_addr(guest, guestaddrp); + if (error != 0) + goto out; + +out: + free(p); + return (error); +} + +static void +config_one_hostfwd(Slirp *slirp, const char *rule) +{ + struct sockaddr hostaddr, guestaddr; + int error, is_udp; + + error = parse_hostfwd_rule(rule, &is_udp, &hostaddr, &guestaddr); + if (error != 0) + errx(1, "unable to parse hostfwd rule '%s': %s", rule, + strerror(error)); + + error = slirp_add_hostxfwd_p(slirp, &hostaddr, hostaddr.sa_len, + &guestaddr, guestaddr.sa_len, is_udp ? SLIRP_HOSTFWD_UDP : 0); + if (error != 0) + errx(1, "Unable to add hostfwd rule '%s': %s", rule, + strerror(errno)); +} + +/* + * Drop privileges to the "nobody" user. Ideally we'd chroot to somewhere like + * /var/empty but libslirp might need to access /etc/resolv.conf. + */ +static void +drop_privs(void) +{ + struct passwd *pw; + + if (geteuid() != 0) + return; + + pw = getpwnam("nobody"); + if (pw == NULL) + err(1, "getpwnam(nobody) failed"); + if (initgroups(pw->pw_name, pw->pw_gid) != 0) + err(1, "initgroups"); + if (setgid(pw->pw_gid) != 0) + err(1, "setgid"); + if (setuid(pw->pw_uid) != 0) + err(1, "setuid"); +} + +static void +libslirp_init(void) +{ + void *handle; + + handle = dlopen("libslirp.so.0", RTLD_LAZY); + if (handle == NULL) + errx(1, "unable to open libslirp.so.0: %s", dlerror()); + +#define IMPORT_SYM(sym) do { \ + sym##_p = (sym##_p_t)dlsym(handle, #sym); \ + if (sym##_p == NULL) \ + errx(1, "failed to resolve %s", #sym); \ +} while (0) + IMPORT_SYM(slirp_add_hostxfwd); + IMPORT_SYM(slirp_cleanup); + IMPORT_SYM(slirp_input); + IMPORT_SYM(slirp_new); + IMPORT_SYM(slirp_pollfds_fill); + IMPORT_SYM(slirp_pollfds_poll); +#undef IMPORT_SYM +} + +static void +usage(void) +{ + fprintf(stderr, "Usage: slirp-helper -S <socket>\n"); + exit(1); +} + +int +main(int argc, char **argv) +{ + struct slirp_priv priv; + SlirpConfig slirpconfig; + Slirp *slirp; + nvlist_t *config; + const char *hostfwd, *vmname; + int ch, fd, sd; + bool restricted; + + sd = -1; + while ((ch = getopt(argc, argv, "S:")) != -1) { + switch (ch) { + case 'S': + sd = atoi(optarg); + if (fcntl(sd, F_GETFD) == -1) + err(1, "invalid socket %s", optarg); + break; + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + if (sd == -1) + usage(); + + /* + * Clean the fd space: point stdio to /dev/null and keep our socket. + */ + fd = open("/dev/null", O_RDWR); + if (fd == -1) + err(1, "open(/dev/null)"); + if (dup2(fd, STDIN_FILENO) == -1) + err(1, "dup2(stdin)"); + if (dup2(fd, STDOUT_FILENO) == -1) + err(1, "dup2(stdout)"); + if (dup2(fd, STDERR_FILENO) == -1) + err(1, "dup2(stderr)"); + if (dup2(sd, 3) == -1) + err(1, "dup2(slirp socket)"); + sd = 3; + closefrom(sd + 1); + + memset(&priv, 0, sizeof(priv)); + priv.sock = sd; + if (pipe2(priv.wakeup, O_CLOEXEC | O_NONBLOCK) != 0) + err(1, "pipe2"); + + /* + * Apply the configuration we received from bhyve. + */ + config = nvlist_recv(sd, 0); + if (config == NULL) + err(1, "nvlist_recv"); + vmname = get_config_value_node(config, "vmname"); + if (vmname != NULL) + setproctitle("%s", vmname); + restricted = !get_config_bool_node_default(config, "open", false); + + slirpconfig = (SlirpConfig){ + .version = 4, + .if_mtu = SLIRP_MTU, + .restricted = restricted, + .in_enabled = true, + .vnetwork.s_addr = htonl(0x0a000200), /* 10.0.2.0/24 */ + .vnetmask.s_addr = htonl(0xffffff00), /* 255.255.255.0 */ + .vdhcp_start.s_addr = htonl(0x0a00020f),/* 10.0.2.15 */ + .vhost.s_addr = htonl(0x0a000202), /* 10.0.2.2 */ + .vnameserver.s_addr = htonl(0x0a000203),/* 10.0.2.3 */ + .enable_emu = false, + }; + libslirp_init(); + slirp = slirp_new_p(&slirpconfig, &slirp_cbs, &priv); + + hostfwd = get_config_value_node(config, "hostfwd"); + if (hostfwd != NULL) { + char *rules, *tofree; + const char *rule; + + tofree = rules = strdup(hostfwd); + if (rules == NULL) + err(1, "strdup"); + while ((rule = strsep(&rules, ";")) != NULL) + config_one_hostfwd(slirp, rule); + free(tofree); + } + + priv.slirp = slirp; + + /* + * Drop root privileges if we have them. + */ + drop_privs(); + + /* + * In restricted mode, we can enter a Capsicum sandbox without losing + * functionality. + */ + if (restricted && caph_enter() != 0) + err(1, "caph_enter"); + + /* + * Enter our main loop. If bhyve goes away, we should observe a hangup + * on the socket and exit. + */ + slirp_pollfd_loop(&priv); + /* NOTREACHED */ + + return (1); +} |
