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.
One limitation of the current interface is that the hostfwd option can
be specified only once, since bhyve config is done using nvlists which
don't allow duplicate keys by default. Suggestions for how to address
this would be welcome; I'm not sure yet whether it's easy to make bhyve
use dnv(3) for this case.
The data path is kind of complicated because there's quite a lot of
callbacks involved. When the backend receives a packet to be sent to
the guest, libslirp calls slirp_cb_send_packet(), which puts the packet
into a socket buffer, to be read by the mevent thread in slirp_recv().
Packets transmitted by the guest are handed to slirp_send(), which calls
slirp_input() to perform library processing.
We use a dedicated thread to handle events; the slirp interface is
really designed for a thread which calls poll(), so it was cleaner to
add a new event loop rather than try to translate between pollfds and
mevent. This loop polls for incoming connection requests and handles
libslirp timeout events (retransmits, etc.). A mutex is used to
serialize calls into libslirp after initialization is done.