#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"
struct slirp_priv {
Slirp *slirp;
int sock;
int wakeup[2];
struct pollfd *pollfds;
size_t npollfds;
size_t lastpollfd;
size_t mtu;
uint8_t *buf;
};
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 *);
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;
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 <= priv->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)
{
}
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);
revents = pollfd->revents;
if ((pollfd->events & POLLHUP) == 0)
revents &= ~POLLHUP;
return (pollev2slirpev(revents));
}
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;
wakeup = slirp_addpoll(priv, priv->wakeup[0], POLLIN);
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 ((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 ((pollfds[input].revents & (POLLHUP | POLLRDHUP)) != 0)
errx(1, "parent process closed connection");
if ((pollfds[input].revents & POLLIN) != 0) {
ssize_t n;
do {
n = recv(priv->sock, priv->buf, priv->mtu,
MSG_DONTWAIT);
if (n < 0) {
if (errno == EWOULDBLOCK)
break;
err(1, "recv");
}
slirp_input_p(priv->slirp, priv->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));
}
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;
size_t mtu;
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();
}
}
argc -= optind;
argv += optind;
if (sd == -1)
usage();
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 (ioctl(priv.sock, FIONBIO, &(int){0}) == -1)
err(1, "ioctl(FIONBIO)");
if (pipe2(priv.wakeup, O_CLOEXEC | O_NONBLOCK) != 0)
err(1, "pipe2");
config = nvlist_recv(sd, 0);
if (config == NULL)
err(1, "nvlist_recv");
mtu = nvlist_get_number(config, "mtui");
priv.mtu = mtu;
priv.buf = malloc(mtu);
if (priv.buf == NULL)
err(1, "malloc");
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 = mtu,
.restricted = restricted,
.in_enabled = true,
.vnetwork.s_addr = htonl(0x0a000200),
.vnetmask.s_addr = htonl(0xffffff00),
.vdhcp_start.s_addr = htonl(0x0a00020f),
.vhost.s_addr = htonl(0x0a000202),
.vnameserver.s_addr = htonl(0x0a000203),
.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_privs();
if (restricted && caph_enter() != 0)
err(1, "caph_enter");
slirp_pollfd_loop(&priv);
return (1);
}