diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile --- a/usr.sbin/bhyve/Makefile +++ b/usr.sbin/bhyve/Makefile @@ -110,6 +110,9 @@ CFLAGS+= -DBHYVE_SNAPSHOT .endif +CFLAGS+= -DBHYVE_SLIRP +CFLAGS+= -fsanitize=undefined + # Disable thread safety analysis since it only finds very simple bugs and # yields many false positives. NO_WTHREAD_SAFETY= diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8 --- a/usr.sbin/bhyve/bhyve.8 +++ b/usr.sbin/bhyve/bhyve.8 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd October 12, 2023 +.Dd November 8, 2023 .Dt BHYVE 8 .Os .Sh NAME @@ -426,6 +426,10 @@ .Op Cm \&,mac= Ar xx:xx:xx:xx:xx:xx .Op Cm \&,mtu= Ar N .Xc +.It +.Xo +.Cm slirp,hostfwd= Ar proto : Ar hostaddr : Ar hostport - Ar guestaddr : Ar guestport +.Xc .El .Sm on .Pp @@ -469,6 +473,17 @@ .Xr netgraph 4 addressing rules. .Pp +The slirp backend can be used to provide a NATed network to the guest. +This backend has poor performance but does not require any network +configuration on the host system. +It depends on the +.Pa net/libslirp +port. +The +.Cm hostfwd +option takes a 5-tuple describing how connections from the host are to be +forwarded to the guest. +.Pp Block storage device backends: .Sm off .Bl -bullet diff --git a/usr.sbin/bhyve/libslirp.h b/usr.sbin/bhyve/libslirp.h new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/libslirp.h @@ -0,0 +1,337 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef LIBSLIRP_H +#define LIBSLIRP_H + +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#include +typedef SSIZE_T slirp_ssize_t; +#ifdef BUILDING_LIBSLIRP +# define SLIRP_EXPORT __declspec(dllexport) +#else +# define SLIRP_EXPORT __declspec(dllimport) +#endif +#else +#include +typedef ssize_t slirp_ssize_t; +#include +#include +#define SLIRP_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Opaque structure containing the slirp state */ +typedef struct Slirp Slirp; + +/* Flags passed to SlirpAddPollCb and to be returned by SlirpGetREventsCb. */ +enum { + SLIRP_POLL_IN = 1 << 0, + SLIRP_POLL_OUT = 1 << 1, + SLIRP_POLL_PRI = 1 << 2, + SLIRP_POLL_ERR = 1 << 3, + SLIRP_POLL_HUP = 1 << 4, +}; + +/* Callback for application to get data from the guest */ +typedef slirp_ssize_t (*SlirpReadCb)(void *buf, size_t len, void *opaque); +/* Callback for application to send data to the guest */ +typedef slirp_ssize_t (*SlirpWriteCb)(const void *buf, size_t len, void *opaque); +/* Timer callback */ +typedef void (*SlirpTimerCb)(void *opaque); +/* Callback for libslirp to register polling callbacks */ +typedef int (*SlirpAddPollCb)(int fd, int events, void *opaque); +/* Callback for libslirp to get polling result */ +typedef int (*SlirpGetREventsCb)(int idx, void *opaque); + +/* For now libslirp creates only a timer for the IPv6 RA */ +typedef enum SlirpTimerId { + SLIRP_TIMER_RA, + SLIRP_TIMER_NUM, +} SlirpTimerId; + +/* + * Callbacks from slirp, to be set by the application. + * + * The opaque parameter is set to the opaque pointer given in the slirp_new / + * slirp_init call. + */ +typedef struct SlirpCb { + /* + * Send an ethernet frame to the guest network. The opaque parameter is the + * one given to slirp_init(). If the guest is not ready to receive a frame, + * the function can just drop the data. TCP will then handle retransmissions + * at a lower pace. + * <0 reports an IO error. + */ + SlirpWriteCb send_packet; + /* Print a message for an error due to guest misbehavior. */ + void (*guest_error)(const char *msg, void *opaque); + /* Return the virtual clock value in nanoseconds */ + int64_t (*clock_get_ns)(void *opaque); + /* Create a new timer with the given callback and opaque data. Not + * needed if timer_new_opaque is provided. */ + void *(*timer_new)(SlirpTimerCb cb, void *cb_opaque, void *opaque); + /* Remove and free a timer */ + void (*timer_free)(void *timer, void *opaque); + /* Modify a timer to expire at @expire_time (ms) */ + void (*timer_mod)(void *timer, int64_t expire_time, void *opaque); + /* Register a fd for future polling */ + void (*register_poll_fd)(int fd, void *opaque); + /* Unregister a fd */ + void (*unregister_poll_fd)(int fd, void *opaque); + /* Kick the io-thread, to signal that new events may be processed because some TCP buffer + * can now receive more data, i.e. slirp_socket_can_recv will return 1. */ + void (*notify)(void *opaque); + + /* + * Fields introduced in SlirpConfig version 4 begin + */ + + /* Initialization has completed and a Slirp* has been created. */ + void (*init_completed)(Slirp *slirp, void *opaque); + /* Create a new timer. When the timer fires, the application passes + * the SlirpTimerId and cb_opaque to slirp_handle_timer. */ + void *(*timer_new_opaque)(SlirpTimerId id, void *cb_opaque, void *opaque); +} SlirpCb; + +#define SLIRP_CONFIG_VERSION_MIN 1 +#define SLIRP_CONFIG_VERSION_MAX 5 + +typedef struct SlirpConfig { + /* Version must be provided */ + uint32_t version; + /* + * Fields introduced in SlirpConfig version 1 begin + */ + /* Whether to prevent the guest from accessing the Internet */ + int restricted; + /* Whether IPv4 is enabled */ + bool in_enabled; + /* Virtual network for the guest */ + struct in_addr vnetwork; + /* Mask for the virtual network for the guest */ + struct in_addr vnetmask; + /* Virtual address for the host exposed to the guest */ + struct in_addr vhost; + /* Whether IPv6 is enabled */ + bool in6_enabled; + /* Virtual IPv6 network for the guest */ + struct in6_addr vprefix_addr6; + /* Len of the virtual IPv6 network for the guest */ + uint8_t vprefix_len; + /* Virtual address for the host exposed to the guest */ + struct in6_addr vhost6; + /* Hostname exposed to the guest in DHCP hostname option */ + const char *vhostname; + /* Hostname exposed to the guest in the DHCP TFTP server name option */ + const char *tftp_server_name; + /* Path of the files served by TFTP */ + const char *tftp_path; + /* Boot file name exposed to the guest via DHCP */ + const char *bootfile; + /* Start of the DHCP range */ + struct in_addr vdhcp_start; + /* Virtual address for the DNS server exposed to the guest */ + struct in_addr vnameserver; + /* Virtual IPv6 address for the DNS server exposed to the guest */ + struct in6_addr vnameserver6; + /* DNS search names exposed to the guest via DHCP */ + const char **vdnssearch; + /* Domain name exposed to the guest via DHCP */ + const char *vdomainname; + /* MTU when sending packets to the guest */ + /* Default: IF_MTU_DEFAULT */ + size_t if_mtu; + /* MRU when receiving packets from the guest */ + /* Default: IF_MRU_DEFAULT */ + size_t if_mru; + /* Prohibit connecting to 127.0.0.1:* */ + bool disable_host_loopback; + /* + * Enable emulation code (*warning*: this code isn't safe, it is not + * recommended to enable it) + */ + bool enable_emu; + + /* + * Fields introduced in SlirpConfig version 2 begin + */ + /* Address to be used when sending data to the Internet */ + struct sockaddr_in *outbound_addr; + /* IPv6 Address to be used when sending data to the Internet */ + struct sockaddr_in6 *outbound_addr6; + + /* + * Fields introduced in SlirpConfig version 3 begin + */ + /* slirp will not redirect/serve any DNS packet */ + bool disable_dns; + + /* + * Fields introduced in SlirpConfig version 4 begin + */ + /* slirp will not reply to any DHCP requests */ + bool disable_dhcp; + + /* + * Fields introduced in SlirpConfig version 5 begin + */ + /* Manufacturer ID (IANA Private Enterprise number) */ + uint32_t mfr_id; + /* + * MAC address allocated for an out-of-band management controller, to be + * retrieved through NC-SI. + */ + uint8_t oob_eth_addr[6]; +} SlirpConfig; + +/* Create a new instance of a slirp stack */ +SLIRP_EXPORT +Slirp *slirp_new(const SlirpConfig *cfg, const SlirpCb *callbacks, + void *opaque); +/* slirp_init is deprecated in favor of slirp_new */ +SLIRP_EXPORT +Slirp *slirp_init(int restricted, bool in_enabled, struct in_addr vnetwork, + struct in_addr vnetmask, struct in_addr vhost, + bool in6_enabled, struct in6_addr vprefix_addr6, + uint8_t vprefix_len, struct in6_addr vhost6, + const char *vhostname, const char *tftp_server_name, + const char *tftp_path, const char *bootfile, + struct in_addr vdhcp_start, struct in_addr vnameserver, + struct in6_addr vnameserver6, const char **vdnssearch, + const char *vdomainname, const SlirpCb *callbacks, + void *opaque); +/* Shut down an instance of a slirp stack */ +SLIRP_EXPORT +void slirp_cleanup(Slirp *slirp); + +/* This is called by the application when it is about to sleep through poll(). + * *timeout is set to the amount of virtual time (in ms) that the application intends to + * wait (UINT32_MAX if infinite). slirp_pollfds_fill updates it according to + * e.g. TCP timers, so the application knows it should sleep a smaller amount of + * time. slirp_pollfds_fill calls add_poll for each file descriptor + * that should be monitored along the sleep. The opaque pointer is passed as + * such to add_poll, and add_poll returns an index. */ +SLIRP_EXPORT +void slirp_pollfds_fill(Slirp *slirp, uint32_t *timeout, + SlirpAddPollCb add_poll, void *opaque); + +/* This is called by the application after sleeping, to report which file + * descriptors are available. slirp_pollfds_poll calls get_revents on each file + * descriptor, giving it the index that add_poll returned during the + * slirp_pollfds_fill call, to know whether the descriptor is available for + * read/write/etc. (SLIRP_POLL_*) + * select_error should be passed 1 if poll() returned an error. */ +SLIRP_EXPORT +void slirp_pollfds_poll(Slirp *slirp, int select_error, + SlirpGetREventsCb get_revents, void *opaque); + +/* This is called by the application when the guest emits a packet on the + * guest network, to be interpreted by slirp. */ +SLIRP_EXPORT +void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len); + +/* This is called by the application when a timer expires, if it provides + * the timer_new_opaque callback. It is not needed if the application only + * uses timer_new. */ +SLIRP_EXPORT +void slirp_handle_timer(Slirp *slirp, SlirpTimerId id, void *cb_opaque); + +/* These set up / remove port forwarding between a host port in the real world + * and the guest network. */ +SLIRP_EXPORT +int slirp_add_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port, struct in_addr guest_addr, int guest_port); +SLIRP_EXPORT +int slirp_remove_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port); + +#define SLIRP_HOSTFWD_UDP 1 +#define SLIRP_HOSTFWD_V6ONLY 2 +SLIRP_EXPORT +int slirp_add_hostxfwd(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *gaddr, socklen_t gaddrlen, + int flags); +SLIRP_EXPORT +int slirp_remove_hostxfwd(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + int flags); + +/* Set up port forwarding between a port in the guest network and a + * command running on the host */ +SLIRP_EXPORT +int slirp_add_exec(Slirp *slirp, const char *cmdline, + struct in_addr *guest_addr, int guest_port); +/* Set up port forwarding between a port in the guest network and a + * Unix port on the host */ +SLIRP_EXPORT +int slirp_add_unix(Slirp *slirp, const char *unixsock, + struct in_addr *guest_addr, int guest_port); +/* Set up port forwarding between a port in the guest network and a + * callback that will receive the data coming from the port */ +SLIRP_EXPORT +int slirp_add_guestfwd(Slirp *slirp, SlirpWriteCb write_cb, void *opaque, + struct in_addr *guest_addr, int guest_port); + +/* TODO: rather identify a guestfwd through an opaque pointer instead of through + * the guest_addr */ + +/* This is called by the application for a guestfwd, to determine how much data + * can be received by the forwarded port through a call to slirp_socket_recv. */ +SLIRP_EXPORT +size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr guest_addr, + int guest_port); +/* This is called by the application for a guestfwd, to provide the data to be + * sent on the forwarded port */ +SLIRP_EXPORT +void slirp_socket_recv(Slirp *slirp, struct in_addr guest_addr, int guest_port, + const uint8_t *buf, int size); + +/* Remove entries added by slirp_add_exec, slirp_add_unix or slirp_add_guestfwd */ +SLIRP_EXPORT +int slirp_remove_guestfwd(Slirp *slirp, struct in_addr guest_addr, + int guest_port); + +/* Return a human-readable state of the slirp stack */ +SLIRP_EXPORT +char *slirp_connection_info(Slirp *slirp); + +/* Return a human-readable state of the NDP/ARP tables */ +SLIRP_EXPORT +char *slirp_neighbor_info(Slirp *slirp); + +/* Save the slirp state through the write_cb. The opaque pointer is passed as + * such to the write_cb. */ +SLIRP_EXPORT +int slirp_state_save(Slirp *s, SlirpWriteCb write_cb, void *opaque); + +/* Returns the version of the slirp state, to be saved along the state */ +SLIRP_EXPORT +int slirp_state_version(void); + +/* Load the slirp state through the read_cb. The opaque pointer is passed as + * such to the read_cb. The version should be given as it was obtained from + * slirp_state_version when slirp_state_save was called. */ +SLIRP_EXPORT +int slirp_state_load(Slirp *s, int version_id, SlirpReadCb read_cb, + void *opaque); + +/* Return the version of the slirp implementation */ +SLIRP_EXPORT +const char *slirp_version_string(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LIBSLIRP_H */ diff --git a/usr.sbin/bhyve/net_backends.h b/usr.sbin/bhyve/net_backends.h --- a/usr.sbin/bhyve/net_backends.h +++ b/usr.sbin/bhyve/net_backends.h @@ -34,7 +34,7 @@ typedef struct net_backend net_backend_t; /* Interface between network frontends and the network backends. */ -typedef void (*net_be_rxeof_t)(int, enum ev_type, void *param); +typedef void (*net_be_rxeof_t)(int fd, enum ev_type, void *param); int netbe_init(net_backend_t **be, nvlist_t *nvl, net_be_rxeof_t cb, void *param); int netbe_legacy_config(nvlist_t *nvl, const char *opts); 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 @@ -51,9 +51,11 @@ #ifndef WITHOUT_CAPSICUM #include #endif +#include #include #include #include +#include #include #include #include @@ -101,7 +103,8 @@ /* * Called to serve a guest transmit request. The scatter-gather * vector provided by the caller has 'iovcnt' elements and contains - * the packet to send. + * the packet to send. Returns the number of bytes transmitted, or + * -1 in the case of an error. */ ssize_t (*send)(struct net_backend *be, const struct iovec *iov, int iovcnt); @@ -897,6 +900,569 @@ DATA_SET(net_backend_set, netmap_backend); DATA_SET(net_backend_set, vale_backend); +#ifdef BHYVE_SLIRP +#include "libslirp.h" + +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 slirp_dlopen(). */ +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 int +slirp_dlopen(void) +{ + static void *handle = NULL; + + if (handle != NULL) + return (0); + handle = dlopen("libslirp.so.0", RTLD_LAZY); + if (handle == NULL) { + EPRINTLN("Unable to open libslirp.so.0: %s", dlerror()); + return (ENOENT); + } + +#define IMPORT_SYM(sym) do { \ + sym##_p = (sym##_p_t)dlsym(handle, #sym); \ + if (sym##_p == NULL) { \ + EPRINTLN("failed to resolve %s", #sym); \ + goto err; \ + } \ +} 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 + + return (0); + +err: + dlclose(handle); + handle = NULL; + return (ENOENT); +} + +struct slirp_priv { + Slirp *slirp; + +#define SLIRP_MTU 2048 + struct mevent *mevp; + int pipe[2]; + + pthread_t pollfd_td; + struct pollfd *pollfds; + size_t npollfds; + + /* Serializes libslirp calls. */ + pthread_mutex_t mtx; +}; + +static void +slirp_priv_init(struct slirp_priv *priv) +{ + int error; + + memset(priv, 0, sizeof(*priv)); + priv->pipe[0] = priv->pipe[1] = -1; + error = pthread_mutex_init(&priv->mtx, NULL); + assert(error == 0); +} + +static void +slirp_priv_cleanup(struct slirp_priv *priv) +{ + int error; + + if (priv->pipe[0] != -1) { + error = close(priv->pipe[0]); + assert(error == 0); + } + if (priv->pipe[1] != -1) { + error = close(priv->pipe[1]); + assert(error == 0); + } + if (priv->mevp) + mevent_delete(priv->mevp); + if (priv->slirp != NULL) + slirp_cleanup_p(priv->slirp); + error = pthread_mutex_destroy(&priv->mtx); + assert(error == 0); +} + +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_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->pipe[1], buf, len, 0); + 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, + .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_cb(int fd, int events, void *param) +{ + struct slirp_priv *priv; + struct pollfd *pollfd, *pollfds; + size_t i; + + priv = param; + + for (i = 0; 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) + err(1, "realloc failed"); + 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; + + return ((int)i); +} + +static int +slirp_poll_revents(int idx, void *param) +{ + struct slirp_priv *priv; + struct pollfd *pollfd; + + priv = param; + pollfd = &priv->pollfds[idx]; + assert(pollfd->fd != -1); + return (pollev2slirpev(pollfd->revents)); +} + +static void * +slirp_pollfd_td_loop(void *param) +{ + struct slirp_priv *priv; + struct pollfd *pollfds; + size_t npollfds; + uint32_t timeout; + int error; + + pthread_set_name_np(pthread_self(), "slirp pollfd"); + priv = param; + + pthread_mutex_lock(&priv->mtx); + for (;;) { + for (size_t i = 0; i < priv->npollfds; i++) + priv->pollfds[i].fd = -1; + + timeout = UINT32_MAX; + slirp_pollfds_fill_p(priv->slirp, &timeout, slirp_addpoll_cb, + priv); + + pollfds = priv->pollfds; + npollfds = priv->npollfds; + pthread_mutex_unlock(&priv->mtx); + for (;;) { + error = poll(pollfds, npollfds, timeout); + if (error == -1) { + if (errno != EINTR) + err(1, "poll"); + continue; + } + break; + } + pthread_mutex_lock(&priv->mtx); + slirp_pollfds_poll_p(priv->slirp, error == -1, + slirp_poll_revents, priv); + } +} + +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(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) { + error = errno; + goto out; + } + + 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 *)hostaddr; + error = parse_addr(host, hostaddrp); + if (error != 0) + goto out; + + guestaddrp = (struct sockaddr_in *)guestaddr; + error = parse_addr(guest, guestaddrp); + if (error != 0) + goto out; + +out: + free(p); + return (error); +} + +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); + SlirpConfig config = { + .version = 4, + .if_mtu = SLIRP_MTU, + .restricted = true, + .in_enabled = true, + .vnetwork.s_addr = htonl(0x0a000200), /* 10.0.2.0/24 */ + .vnetmask.s_addr = htonl(0xffffff00), + .vdhcp_start.s_addr = htonl(0x0a00020f),/* 10.0.2.15 */ + .vhost.s_addr = htonl(0x0a000202), /* 10.0.2.2 */ + .enable_emu = false, + }; + const char *hostfwd; + int error; + + error = slirp_dlopen(); + if (error != 0) + return (error); + + slirp_priv_init(priv); + + priv->slirp = slirp_new_p(&config, &slirp_cbs, priv); + if (priv->slirp == NULL) { + EPRINTLN("Unable to create slirp instance"); + goto err; + } + + /* XXX-MJ only supports one hostfwd rule at the moment */ + hostfwd = get_config_value_node(nvl, "hostfwd"); + if (hostfwd != NULL) { + struct sockaddr hostaddr, guestaddr; + int is_udp; + + error = parse_hostfwd(hostfwd, &is_udp, &hostaddr, + &guestaddr); + if (error != 0) { + EPRINTLN("Unable to parse hostfwd: %s", + strerror(error)); + goto err; + } + + error = slirp_add_hostxfwd_p(priv->slirp, + &hostaddr, hostaddr.sa_len, &guestaddr, guestaddr.sa_len, + is_udp ? SLIRP_HOSTFWD_UDP : 0); + if (error != 0) { + EPRINTLN("Unable to add hostfwd rule: %s", + strerror(errno)); + goto err; + } + } + error = socketpair(PF_LOCAL, SOCK_DGRAM, 0, priv->pipe); + if (error != 0) { + EPRINTLN("Unable to create pipe: %s", strerror(errno)); + goto err; + } + + be->fd = priv->pipe[0]; + priv->mevp = mevent_add_disabled(be->fd, EVF_READ, cb, param); + if (priv->mevp == NULL) { + EPRINTLN("Could not register event"); + goto err; + } + + error = pthread_create(&priv->pollfd_td, NULL, slirp_pollfd_td_loop, + priv); + if (error != 0) { + EPRINTLN("Unable to create pollfd thread: %s", strerror(error)); + goto err; + } + + return (0); + +err: + slirp_priv_cleanup(priv); + 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); + + if (iovcnt == 1) { + /* We can avoid copying if there's a single segment. */ + pthread_mutex_lock(&priv->mtx); + slirp_input_p(priv->slirp, iov->iov_base, + (int)iov->iov_len); + pthread_mutex_unlock(&priv->mtx); + return (iov[0].iov_len); + } else { + uint8_t *pkt; + size_t pktlen; + + pktlen = 0; + for (int i = 0; i < iovcnt; i++) + pktlen += iov[i].iov_len; + pkt = malloc(pktlen); + if (pkt == NULL) + return (-1); + pktlen = 0; + for (int i = 0; i < iovcnt; i++) { + memcpy(pkt + pktlen, iov[i].iov_base, iov[i].iov_len); + pktlen += iov[i].iov_len; + } + pthread_mutex_lock(&priv->mtx); + slirp_input_p(priv->slirp, pkt, (int)pktlen); + pthread_mutex_unlock(&priv->mtx); + free(pkt); + return (pktlen); + } +} + +static void +_slirp_cleanup(struct net_backend *be) +{ + struct slirp_priv *priv = NET_BE_PRIV(be); + + slirp_priv_cleanup(priv); +} + +static ssize_t +slirp_peek_recvlen(struct net_backend *be) +{ + struct slirp_priv *priv = NET_BE_PRIV(be); + ssize_t n; + + n = recv(priv->pipe[0], NULL, 0, MSG_PEEK | MSG_DONTWAIT | MSG_TRUNC); + if (n < 0) + return (errno == EWOULDBLOCK ? 0 : -1); + assert((size_t)n <= SLIRP_MTU); + 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); + ssize_t n; + + n = readv(priv->pipe[0], iov, iovcnt); + if (n < 0) + err(1, "readv"); + assert(n <= SLIRP_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 __unused) +{ + struct slirp_priv *priv = NET_BE_PRIV(be); + + mevent_enable(priv->mevp); +} + +/* + * Ask the backend for the virtio-net features it is able to + * support. Possible features are TSO, UFO and checksum offloading + * in both rx and tx direction and for both IPv4 and IPv6. + */ +static uint64_t +slirp_get_cap(struct net_backend *be __unused) +{ + return (0); +} + +/* + * Tell the backend to enable/disable the specified virtio-net + * features (capabilities). + */ +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); +#endif + int netbe_legacy_config(nvlist_t *nvl, const char *opts) {