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