Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F102645163
D42510.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
32 KB
Referenced Files
None
Subscribers
None
D42510.diff
View Options
diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile
--- a/usr.sbin/bhyve/Makefile
+++ b/usr.sbin/bhyve/Makefile
@@ -34,6 +34,7 @@
mem.c \
mevent.c \
net_backend_netmap.c \
+ net_backend_slirp.c \
net_backends.c \
net_utils.c \
pci_emul.c \
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 20, 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,20 @@
.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
diff --git a/usr.sbin/bhyve/bhyve_config.5 b/usr.sbin/bhyve/bhyve_config.5
--- a/usr.sbin/bhyve/bhyve_config.5
+++ b/usr.sbin/bhyve/bhyve_config.5
@@ -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 @@
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
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,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 */
diff --git a/usr.sbin/bhyve/net_backend_slirp.c b/usr.sbin/bhyve/net_backend_slirp.c
new file mode 100644
--- /dev/null
+++ b/usr.sbin/bhyve/net_backend_slirp.c
@@ -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);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 16, 6:53 AM (21 h, 2 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
14654811
Default Alt Text
D42510.diff (32 KB)
Attached To
Mode
D42510: bhyve: Add a slirp network backend
Attached
Detach File
Event Timeline
Log In to Comment