#include <sys/socket.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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);
}
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;
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);