xref: /freebsd/usr.sbin/bhyve/net_backend_slirp.c (revision bac572b2b1c9aec0ebfd3ae1fd02b314984cb716)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2023, 2025 Mark Johnston <markj@FreeBSD.org>
5  *
6  * This software was developed by the University of Cambridge Computer
7  * Laboratory (Department of Computer Science and Technology) under Innovate
8  * UK project 105694, "Digital Security by Design (DSbD) Technology Platform
9  * Prototype".
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 /*
34  * The slirp backend enables unprivileged userspace networking via libslirp,
35  * which must be installed on the host system via pkg or the ports tree.
36  * libslirp.so is dlopen()ed into a helper process with which this backend
37  * communicates.
38  *
39  * Packets received from the guest (i.e., transmitted by the frontend, such as a
40  * virtio NIC device model) are injected into the slirp backend via slirp_send(),
41  * which sends the packet to the helper process.
42  *
43  * Packets to be transmitted to the guest (i.e., inserted into the frontend's
44  * receive buffers) are buffered in a per-interface socket pair and read by the
45  * mevent loop.  Sockets instantiated by libslirp are monitored by a thread
46  * which uses poll() and slirp_pollfds_poll() to drive libslirp events; this
47  * thread also handles timeout events from the libslirp context.
48  */
49 
50 #include <sys/socket.h>
51 #include <sys/wait.h>
52 
53 #include <assert.h>
54 #include <errno.h>
55 #include <signal.h>
56 #include <spawn.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <unistd.h>
61 
62 #include "config.h"
63 #include "debug.h"
64 #include "mevent.h"
65 #include "net_backends.h"
66 #include "net_backends_priv.h"
67 
68 #define	SLIRP_MTU	2048
69 
70 struct slirp_priv {
71 	int s;
72 	pid_t helper;
73 	struct mevent *mevp;
74 };
75 
76 extern char **environ;
77 
78 static int
slirp_init(struct net_backend * be,const char * devname __unused,nvlist_t * nvl,net_be_rxeof_t cb,void * param)79 slirp_init(struct net_backend *be, const char *devname __unused,
80     nvlist_t *nvl, net_be_rxeof_t cb, void *param)
81 {
82 	struct slirp_priv *priv = NET_BE_PRIV(be);
83 	nvlist_t *config;
84 	posix_spawn_file_actions_t fa;
85 	pid_t child;
86 	const char **argv;
87 	char sockname[32];
88 	int error, s[2];
89 
90 	if (socketpair(PF_LOCAL, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, s) != 0) {
91 		EPRINTLN("socketpair");
92 		return (-1);
93 	}
94 
95 	/*
96 	 * The child will exit once its connection goes away, so make sure only
97 	 * one end is inherited by the child.
98 	 */
99 	if (posix_spawn_file_actions_init(&fa) != 0) {
100 		EPRINTLN("posix_spawn_file_actions_init");
101 		goto err;
102 	}
103 	if (posix_spawn_file_actions_addclose(&fa, s[0]) != 0) {
104 		EPRINTLN("posix_spawn_file_actions_addclose");
105 		posix_spawn_file_actions_destroy(&fa);
106 		goto err;
107 	}
108 
109 	(void)snprintf(sockname, sizeof(sockname), "%d", s[1]);
110 	argv = (const char *[]){
111 	    "/usr/libexec/bhyve-slirp-helper", "-S", sockname, NULL
112 	};
113 	error = posix_spawn(&child, "/usr/libexec/bhyve-slirp-helper",
114 	    &fa, NULL, __DECONST(char **, argv), environ);
115 	posix_spawn_file_actions_destroy(&fa);
116 	if (error != 0) {
117 		EPRINTLN("posix_spawn(bhyve-slirp-helper): %s",
118 		    strerror(error));
119 		goto err;
120 	}
121 
122 	config = nvlist_clone(nvl);
123 	if (config == NULL) {
124 		EPRINTLN("nvlist_clone");
125 		goto err;
126 	}
127 	nvlist_add_string(config, "vmname", get_config_value("name"));
128 	error = nvlist_send(s[0], config);
129 	nvlist_destroy(config);
130 	if (error != 0) {
131 		EPRINTLN("nvlist_send");
132 		goto err;
133 	}
134 
135 	be->fd = s[0];
136 	priv->mevp = mevent_add_disabled(be->fd, EVF_READ, cb, param);
137 	if (priv->mevp == NULL) {
138 		EPRINTLN("Could not register event");
139 		goto err;
140 	}
141 
142 	priv->helper = child;
143 	priv->s = s[0];
144 	(void)close(s[1]);
145 
146 	return (0);
147 
148 err:
149 	(void)close(s[0]);
150 	(void)close(s[1]);
151 	return (-1);
152 }
153 
154 static ssize_t
slirp_send(struct net_backend * be,const struct iovec * iov,int iovcnt)155 slirp_send(struct net_backend *be, const struct iovec *iov, int iovcnt)
156 {
157 	struct slirp_priv *priv = NET_BE_PRIV(be);
158 	struct msghdr hdr;
159 
160 	memset(&hdr, 0, sizeof(hdr));
161 	hdr.msg_iov = __DECONST(struct iovec *, iov);
162 	hdr.msg_iovlen = iovcnt;
163 	return (sendmsg(priv->s, &hdr, MSG_EOR));
164 }
165 
166 static void
slirp_cleanup(struct net_backend * be)167 slirp_cleanup(struct net_backend *be)
168 {
169 	struct slirp_priv *priv = NET_BE_PRIV(be);
170 
171 	if (priv->helper > 0) {
172 		int status;
173 
174 		if (kill(priv->helper, SIGKILL) != 0) {
175 			EPRINTLN("kill(bhyve-slirp-helper): %s",
176 			    strerror(errno));
177 			return;
178 		}
179 		(void)waitpid(priv->helper, &status, 0);
180 	}
181 }
182 
183 static ssize_t
slirp_peek_recvlen(struct net_backend * be)184 slirp_peek_recvlen(struct net_backend *be)
185 {
186 	struct slirp_priv *priv = NET_BE_PRIV(be);
187 	uint8_t buf[SLIRP_MTU];
188 	ssize_t n;
189 
190 	/*
191 	 * Copying into the buffer is totally unnecessary, but we don't
192 	 * implement MSG_TRUNC for SEQPACKET sockets.
193 	 */
194 	n = recv(priv->s, buf, sizeof(buf), MSG_PEEK | MSG_DONTWAIT);
195 	if (n < 0)
196 		return (errno == EWOULDBLOCK ? 0 : -1);
197 	assert((size_t)n <= SLIRP_MTU);
198 	return (n);
199 }
200 
201 static ssize_t
slirp_recv(struct net_backend * be,const struct iovec * iov,int iovcnt)202 slirp_recv(struct net_backend *be, const struct iovec *iov, int iovcnt)
203 {
204 	struct slirp_priv *priv = NET_BE_PRIV(be);
205 	struct msghdr hdr;
206 	ssize_t n;
207 
208 	hdr.msg_name = NULL;
209 	hdr.msg_namelen = 0;
210 	hdr.msg_iov = __DECONST(struct iovec *, iov);
211 	hdr.msg_iovlen = iovcnt;
212 	hdr.msg_control = NULL;
213 	hdr.msg_controllen = 0;
214 	hdr.msg_flags = 0;
215 	n = recvmsg(priv->s, &hdr, MSG_DONTWAIT);
216 	if (n < 0) {
217 		if (errno == EWOULDBLOCK)
218 			return (0);
219 		return (-1);
220 	}
221 	assert(n <= SLIRP_MTU);
222 	return (n);
223 }
224 
225 static void
slirp_recv_enable(struct net_backend * be)226 slirp_recv_enable(struct net_backend *be)
227 {
228 	struct slirp_priv *priv = NET_BE_PRIV(be);
229 
230 	mevent_enable(priv->mevp);
231 }
232 
233 static void
slirp_recv_disable(struct net_backend * be)234 slirp_recv_disable(struct net_backend *be)
235 {
236 	struct slirp_priv *priv = NET_BE_PRIV(be);
237 
238 	mevent_disable(priv->mevp);
239 }
240 
241 static uint64_t
slirp_get_cap(struct net_backend * be __unused)242 slirp_get_cap(struct net_backend *be __unused)
243 {
244 	return (0);
245 }
246 
247 static int
slirp_set_cap(struct net_backend * be __unused,uint64_t features __unused,unsigned int vnet_hdr_len __unused)248 slirp_set_cap(struct net_backend *be __unused, uint64_t features __unused,
249     unsigned int vnet_hdr_len __unused)
250 {
251 	return ((features || vnet_hdr_len) ? -1 : 0);
252 }
253 
254 static struct net_backend slirp_backend = {
255 	.prefix = "slirp",
256 	.priv_size = sizeof(struct slirp_priv),
257 	.init = slirp_init,
258 	.cleanup = slirp_cleanup,
259 	.send = slirp_send,
260 	.peek_recvlen = slirp_peek_recvlen,
261 	.recv = slirp_recv,
262 	.recv_enable = slirp_recv_enable,
263 	.recv_disable = slirp_recv_disable,
264 	.get_cap = slirp_get_cap,
265 	.set_cap = slirp_set_cap,
266 };
267 
268 DATA_SET(net_backend_set, slirp_backend);
269