Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 26 Dec 2023 02:08:23 GMT
From:      Mark Johnston <markj@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org
Subject:   git: 8b6d3fb0a79c - stable/14 - bhyve: Add a slirp network backend
Message-ID:  <202312260208.3BQ28NPK004586@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch stable/14 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=8b6d3fb0a79cb1d0fa89afca47e0839dfd1572d1

commit 8b6d3fb0a79cb1d0fa89afca47e0839dfd1572d1
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2023-11-22 19:11:03 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2023-12-25 16:57:14 +0000

    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
    
    (cherry picked from commit c5359e2af5ab582f9a0b862ce90ad3962f9f1d03)
---
 usr.sbin/bhyve/Makefile            |   1 +
 usr.sbin/bhyve/bhyve.8             |  20 +-
 usr.sbin/bhyve/bhyve_config.5      |  22 +-
 usr.sbin/bhyve/libslirp.h          | 365 ++++++++++++++++++++
 usr.sbin/bhyve/net_backend_slirp.c | 662 +++++++++++++++++++++++++++++++++++++
 5 files changed, 1068 insertions(+), 2 deletions(-)

diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile
index 6ce7f6c7ba62..b6cad38a6c39 100644
--- a/usr.sbin/bhyve/Makefile
+++ b/usr.sbin/bhyve/Makefile
@@ -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		\
diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8
index c6720af46dfb..ffdaf71d1d8a 100644
--- 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
@@ -417,6 +417,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
@@ -460,6 +464,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
diff --git a/usr.sbin/bhyve/bhyve_config.5 b/usr.sbin/bhyve/bhyve_config.5
index 6904ad096c0d..469ae06f36f4 100644
--- 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 @@ 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
diff --git a/usr.sbin/bhyve/libslirp.h b/usr.sbin/bhyve/libslirp.h
new file mode 100644
index 000000000000..a679c4db7913
--- /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
index 000000000000..1c414f87084c
--- /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);
*** 194 LINES SKIPPED ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202312260208.3BQ28NPK004586>