xref: /freebsd/usr.sbin/bhyve/net_backend_slirp.c (revision 90b9a77ebeb8019fbd22b3cf992370cd9b0004a2)
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_utils.h"
66 #include "net_backends.h"
67 #include "net_backends_priv.h"
68 
69 #define	DEFAULT_MTU	2048
70 
71 struct slirp_priv {
72 	int s;
73 	pid_t helper;
74 	struct mevent *mevp;
75 	size_t mtu;
76 	uint8_t *buf;
77 };
78 
79 extern char **environ;
80 
81 static int
slirp_init(struct net_backend * be,const char * devname __unused,nvlist_t * nvl,net_be_rxeof_t cb,void * param)82 slirp_init(struct net_backend *be, const char *devname __unused,
83     nvlist_t *nvl, net_be_rxeof_t cb, void *param)
84 {
85 	struct slirp_priv *priv = NET_BE_PRIV(be);
86 	nvlist_t *config;
87 	posix_spawn_file_actions_t fa;
88 	pid_t child;
89 	const char **argv;
90 	char sockname[32];
91 	int error, s[2];
92 	const char *mtu_value;
93 	size_t mtu;
94 
95 	if (socketpair(PF_LOCAL, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, s) != 0) {
96 		EPRINTLN("socketpair");
97 		return (-1);
98 	}
99 
100 	/*
101 	 * The child will exit once its connection goes away, so make sure only
102 	 * one end is inherited by the child.
103 	 */
104 	if (posix_spawn_file_actions_init(&fa) != 0) {
105 		EPRINTLN("posix_spawn_file_actions_init");
106 		goto err;
107 	}
108 	if (posix_spawn_file_actions_addclose(&fa, s[0]) != 0) {
109 		EPRINTLN("posix_spawn_file_actions_addclose");
110 		posix_spawn_file_actions_destroy(&fa);
111 		goto err;
112 	}
113 
114 	(void)snprintf(sockname, sizeof(sockname), "%d", s[1]);
115 	argv = (const char *[]){
116 	    "/usr/libexec/bhyve-slirp-helper", "-S", sockname, NULL
117 	};
118 	error = posix_spawn(&child, "/usr/libexec/bhyve-slirp-helper",
119 	    &fa, NULL, __DECONST(char **, argv), environ);
120 	posix_spawn_file_actions_destroy(&fa);
121 	if (error != 0) {
122 		EPRINTLN("posix_spawn(bhyve-slirp-helper): %s",
123 		    strerror(error));
124 		goto err;
125 	}
126 
127 	config = nvlist_clone(nvl);
128 	if (config == NULL) {
129 		EPRINTLN("nvlist_clone");
130 		goto err;
131 	}
132 
133 	mtu_value = get_config_value_node(config, "mtu");
134 	if (mtu_value != NULL) {
135 		if (net_parsemtu(mtu_value, &mtu)) {
136 		    	EPRINTLN("Could not parse MTU");
137 		    	goto err;
138 		}
139 	} else {
140 		mtu = DEFAULT_MTU;
141 	}
142 	nvlist_add_number(config, "mtui", mtu);
143 
144 	priv->mtu = mtu;
145 	priv->buf = malloc(mtu);
146 	if (priv->buf == NULL) {
147 		EPRINTLN("Could not allocate buffer");
148 		goto err;
149 	}
150 
151 	nvlist_add_string(config, "vmname", get_config_value("name"));
152 	error = nvlist_send(s[0], config);
153 	nvlist_destroy(config);
154 	if (error != 0) {
155 		EPRINTLN("nvlist_send");
156 		goto err;
157 	}
158 
159 	be->fd = s[0];
160 	priv->mevp = mevent_add_disabled(be->fd, EVF_READ, cb, param);
161 	if (priv->mevp == NULL) {
162 		EPRINTLN("Could not register event");
163 		goto err;
164 	}
165 
166 	priv->helper = child;
167 	priv->s = s[0];
168 	(void)close(s[1]);
169 
170 	return (0);
171 
172 err:
173 	free(priv->buf);
174 	(void)close(s[0]);
175 	(void)close(s[1]);
176 	return (-1);
177 }
178 
179 static ssize_t
slirp_send(struct net_backend * be,const struct iovec * iov,int iovcnt)180 slirp_send(struct net_backend *be, const struct iovec *iov, int iovcnt)
181 {
182 	struct slirp_priv *priv = NET_BE_PRIV(be);
183 	struct msghdr hdr;
184 
185 	memset(&hdr, 0, sizeof(hdr));
186 	hdr.msg_iov = __DECONST(struct iovec *, iov);
187 	hdr.msg_iovlen = iovcnt;
188 	return (sendmsg(priv->s, &hdr, MSG_EOR));
189 }
190 
191 static void
slirp_cleanup(struct net_backend * be)192 slirp_cleanup(struct net_backend *be)
193 {
194 	struct slirp_priv *priv = NET_BE_PRIV(be);
195 
196 	free(priv->buf);
197 
198 	if (priv->helper > 0) {
199 		int status;
200 
201 		if (kill(priv->helper, SIGKILL) != 0) {
202 			EPRINTLN("kill(bhyve-slirp-helper): %s",
203 			    strerror(errno));
204 			return;
205 		}
206 		(void)waitpid(priv->helper, &status, 0);
207 	}
208 }
209 
210 static ssize_t
slirp_peek_recvlen(struct net_backend * be)211 slirp_peek_recvlen(struct net_backend *be)
212 {
213 	struct slirp_priv *priv = NET_BE_PRIV(be);
214 	ssize_t n;
215 
216 	/*
217 	 * Copying into the buffer is totally unnecessary, but we don't
218 	 * implement MSG_TRUNC for SEQPACKET sockets.
219 	 */
220 	n = recv(priv->s, priv->buf, priv->mtu, MSG_PEEK | MSG_DONTWAIT);
221 	if (n < 0)
222 		return (errno == EWOULDBLOCK ? 0 : -1);
223 	return (n);
224 }
225 
226 static ssize_t
slirp_recv(struct net_backend * be,const struct iovec * iov,int iovcnt)227 slirp_recv(struct net_backend *be, const struct iovec *iov, int iovcnt)
228 {
229 	struct slirp_priv *priv = NET_BE_PRIV(be);
230 	struct msghdr hdr;
231 	ssize_t n;
232 
233 	hdr.msg_name = NULL;
234 	hdr.msg_namelen = 0;
235 	hdr.msg_iov = __DECONST(struct iovec *, iov);
236 	hdr.msg_iovlen = iovcnt;
237 	hdr.msg_control = NULL;
238 	hdr.msg_controllen = 0;
239 	hdr.msg_flags = 0;
240 	n = recvmsg(priv->s, &hdr, MSG_DONTWAIT);
241 	if (n < 0) {
242 		if (errno == EWOULDBLOCK)
243 			return (0);
244 		return (-1);
245 	}
246 	assert((size_t)n <= priv->mtu);
247 	return (n);
248 }
249 
250 static void
slirp_recv_enable(struct net_backend * be)251 slirp_recv_enable(struct net_backend *be)
252 {
253 	struct slirp_priv *priv = NET_BE_PRIV(be);
254 
255 	mevent_enable(priv->mevp);
256 }
257 
258 static void
slirp_recv_disable(struct net_backend * be)259 slirp_recv_disable(struct net_backend *be)
260 {
261 	struct slirp_priv *priv = NET_BE_PRIV(be);
262 
263 	mevent_disable(priv->mevp);
264 }
265 
266 static uint64_t
slirp_get_cap(struct net_backend * be __unused)267 slirp_get_cap(struct net_backend *be __unused)
268 {
269 	return (0);
270 }
271 
272 static int
slirp_set_cap(struct net_backend * be __unused,uint64_t features __unused,unsigned int vnet_hdr_len __unused)273 slirp_set_cap(struct net_backend *be __unused, uint64_t features __unused,
274     unsigned int vnet_hdr_len __unused)
275 {
276 	return ((features || vnet_hdr_len) ? -1 : 0);
277 }
278 
279 static struct net_backend slirp_backend = {
280 	.prefix = "slirp",
281 	.priv_size = sizeof(struct slirp_priv),
282 	.init = slirp_init,
283 	.cleanup = slirp_cleanup,
284 	.send = slirp_send,
285 	.peek_recvlen = slirp_peek_recvlen,
286 	.recv = slirp_recv,
287 	.recv_enable = slirp_recv_enable,
288 	.recv_disable = slirp_recv_disable,
289 	.get_cap = slirp_get_cap,
290 	.set_cap = slirp_set_cap,
291 };
292 
293 DATA_SET(net_backend_set, slirp_backend);
294