bhyve: Add a slirp network backend

This enables a subset of the functionality provided by QEMU's user
networking implementation.  In particular, it uses net/libslirp, the
same library as QEMU.

libslirp is permissively licensed but has some dependencies which make
it impractical to bring into the base system (glib in particular).  I
thus opted to make bhyve dlopen the libslirp.so, which can be installed
via pkg.  The library header is imported into bhyve.

The slirp backend takes a "hostfwd" which is identical to QEMU's
hostfwd.  When configured, bhyve opens a host socket and listens for
connections, which get forwarded to the guest.  For instance,
"hostfwd=tcp::1234-:22" allows one to ssh into the guest by ssh'ing to
port 1234 on the host, e.g., via 127.0.0.1.  I didn't try to hook up
guestfwd support since I don't personally have a use-case for it yet,
and I think it won't interact nicely with the capsicum sandbox.

Reviewed by:	jhb
Tested by:	rew
MFC after:	1 month
Sponsored by:	Innovate UK
Differential Revision:	https://reviews.freebsd.org/D42510
This commit is contained in:
Mark Johnston 2023-11-22 14:11:03 -05:00
parent be74aede49
commit c5359e2af5
5 changed files with 1068 additions and 2 deletions

View file

@ -34,6 +34,7 @@ SRCS= \
mem.c \
mevent.c \
net_backend_netmap.c \
net_backend_slirp.c \
net_backends.c \
net_utils.c \
pci_emul.c \

View file

@ -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 20, 2023
.Dt BHYVE 8
.Os
.Sh NAME
@ -426,6 +426,10 @@ Network device backends:
.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,20 @@ must comply with
.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.
Multiple rules can be specified, separated by semicolons.
Note that semicolons must be escaped or quoted to prevent the shell from
interpreting them.
.Pp
Block storage device backends:
.Sm off
.Bl -bullet

View file

@ -23,7 +23,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd August 19, 2022
.Dd November 20, 2023
.Dt BHYVE_CONFIG 5
.Os
.Sh NAME
@ -401,6 +401,26 @@ The value of
is passed to
.Xr nm_open
to connect to a netmap port.
.It slirp
Use the slirp backend to provide a userspace network stack.
The
.Va hostfwd
variable is used to configure how packets from the host are translated
before being sent to the guest.
.Bl -column "peerhook" "Format" "Default"
.It Sy Name Ta Sy Format Ta Sy Default Ta Sy Description
.It Va hostfwd Ta string Ta Ta
A semicolon-separated list of host forwarding rules, each of the form
.Ar proto:haddr:hport-gaddr:gport ,
where
.Ar proto
is either
.Ql tcp
or
.Ql udp .
If the guest address is equal to the empty string, packets will be
forwarded to the first DHCP-assigned address in the guest.
.El
.El
.Pp
If

365
usr.sbin/bhyve/libslirp.h Normal file
View file

@ -0,0 +1,365 @@
/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1995,1996 Danny Gasparovski. All rights reserved.
*
* 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.
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``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
* DANNY GASPAROVSKI 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.
*/
#ifndef LIBSLIRP_H
#define LIBSLIRP_H
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <in6addr.h>
#include <basetsd.h>
typedef SSIZE_T slirp_ssize_t;
#ifdef BUILDING_LIBSLIRP
# define SLIRP_EXPORT __declspec(dllexport)
#else
# define SLIRP_EXPORT __declspec(dllimport)
#endif
#else
#include <sys/types.h>
typedef ssize_t slirp_ssize_t;
#include <netinet/in.h>
#include <arpa/inet.h>
#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 */

View file

@ -0,0 +1,662 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2023 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".
*
* 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 networking via libslirp, which must be
* installed on the host system via pkg or the ports tree. bhyve dlopen()s
* libslirp.so upon instantiating the slirp backend. Various network parameters
* are hard-coded in _slirp_init().
*
* 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().
* 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 <sys/socket.h>
#include <assert.h>
#include <capsicum_helpers.h>
#include <dlfcn.h>
#include <errno.h>
#include <poll.h>
#include <pthread.h>
#include <pthread_np.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "config.h"
#include "debug.h"
#include "libslirp.h"
#include "mevent.h"
#include "net_backends.h"
#include "net_backends_priv.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_init_once(). */
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_init_once(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 (-1);
}
#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
/*
* libslirp uses glib, which uses tzdata to format log messages. Help
* it out.
*
* XXX-MJ glib will also look for charset files, not sure what we can do
* about that...
*/
caph_cache_tzdata();
return (0);
err:
dlclose(handle);
handle = NULL;
return (-1);
}
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_notify(void *param __unused)
{
}
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) {
EPRINTLN("slirp_cb_send_packet: send: %s", strerror(errno));
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_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)
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;
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) {
EPRINTLN("poll: %s", strerror(errno));
exit(1);
}
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_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 *)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
config_one_hostfwd(struct slirp_priv *priv, const char *rule)
{
struct sockaddr hostaddr, guestaddr;
int error, is_udp;
error = parse_hostfwd_rule(rule, &is_udp, &hostaddr, &guestaddr);
if (error != 0) {
EPRINTLN("Unable to parse hostfwd rule '%s': %s",
rule, strerror(error));
return (error);
}
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': %s",
rule, strerror(errno));
return (error);
}
return (0);
}
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, sndbuf;
error = slirp_init_once();
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;
}
hostfwd = get_config_value_node(nvl, "hostfwd");
if (hostfwd != NULL) {
char *rules, *tofree;
const char *rule;
tofree = rules = strdup(hostfwd);
if (rules == NULL)
goto err;
while ((rule = strsep(&rules, ";")) != NULL) {
error = config_one_hostfwd(priv, rule);
if (error != 0)
goto err;
}
free(tofree);
}
error = socketpair(PF_LOCAL, SOCK_DGRAM, 0, priv->pipe);
if (error != 0) {
EPRINTLN("Unable to create pipe: %s", strerror(errno));
goto err;
}
/*
* Try to avoid dropping buffered packets in slirp_cb_send_packet().
*/
sndbuf = 1024 * 1024;
error = setsockopt(priv->pipe[1], SOL_SOCKET, SO_SNDBUF, &sndbuf,
sizeof(sndbuf));
if (error != 0) {
EPRINTLN("Could not set socket buffer size: %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)
return (-1);
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);
}
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);