xref: /openbmc/qemu/net/passt.c (revision 854ee02b22220377f3fa3806adf7e0718c3a5c5a)
1 /*
2  * passt network backend
3  *
4  * Copyright Red Hat
5  *
6  * SPDX-License-Identifier: GPL-2.0-or-later
7  */
8 #include "qemu/osdep.h"
9 #include <glib/gstdio.h>
10 #include <gio/gio.h>
11 #include "net/net.h"
12 #include "clients.h"
13 #include "qapi/error.h"
14 #include "io/net-listener.h"
15 #include "stream_data.h"
16 
17 typedef struct NetPasstState {
18     NetStreamData data;
19     GPtrArray *args;
20     gchar *pidfile;
21     pid_t pid;
22 } NetPasstState;
23 
24 static int net_passt_stream_start(NetPasstState *s, Error **errp);
25 
26 static void net_passt_cleanup(NetClientState *nc)
27 {
28     NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
29 
30     kill(s->pid, SIGTERM);
31     g_remove(s->pidfile);
32     g_free(s->pidfile);
33     g_ptr_array_free(s->args, TRUE);
34 }
35 
36 static ssize_t net_passt_receive(NetClientState *nc, const uint8_t *buf,
37                                   size_t size)
38 {
39     NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc);
40 
41     return net_stream_data_receive(d, buf, size);
42 }
43 
44 static gboolean net_passt_send(QIOChannel *ioc, GIOCondition condition,
45                                 gpointer data)
46 {
47     if (net_stream_data_send(ioc, condition, data) == G_SOURCE_REMOVE) {
48         NetPasstState *s = DO_UPCAST(NetPasstState, data, data);
49         Error *error;
50 
51         /* we need to restart passt */
52         kill(s->pid, SIGTERM);
53         if (net_passt_stream_start(s, &error) == -1) {
54             error_report_err(error);
55         }
56 
57         return G_SOURCE_REMOVE;
58     }
59 
60     return G_SOURCE_CONTINUE;
61 }
62 
63 static NetClientInfo net_passt_info = {
64     .type = NET_CLIENT_DRIVER_PASST,
65     .size = sizeof(NetPasstState),
66     .receive = net_passt_receive,
67     .cleanup = net_passt_cleanup,
68 };
69 
70 static void net_passt_client_connected(QIOTask *task, gpointer opaque)
71 {
72     NetPasstState *s = opaque;
73 
74     if (net_stream_data_client_connected(task, &s->data) == 0) {
75         qemu_set_info_str(&s->data.nc, "stream,connected to pid %d", s->pid);
76     }
77 }
78 
79 static int net_passt_start_daemon(NetPasstState *s, int sock, Error **errp)
80 {
81     g_autoptr(GSubprocess) daemon = NULL;
82     g_autofree gchar *contents = NULL;
83     g_autoptr(GError) error = NULL;
84     GSubprocessLauncher *launcher;
85 
86     qemu_set_info_str(&s->data.nc, "launching passt");
87 
88     launcher = g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_NONE);
89     g_subprocess_launcher_take_fd(launcher, sock, 3);
90 
91     daemon =  g_subprocess_launcher_spawnv(launcher,
92                                            (const gchar *const *)s->args->pdata,
93                                            &error);
94     g_object_unref(launcher);
95 
96     if (!daemon) {
97         error_setg(errp, "Error creating daemon: %s", error->message);
98         return -1;
99     }
100 
101     if (!g_subprocess_wait(daemon, NULL, &error)) {
102         error_setg(errp, "Error waiting for daemon: %s", error->message);
103         return -1;
104     }
105 
106     if (g_subprocess_get_if_exited(daemon) &&
107         g_subprocess_get_exit_status(daemon)) {
108         return -1;
109     }
110 
111     if (!g_file_get_contents(s->pidfile, &contents, NULL, &error)) {
112         error_setg(errp, "Cannot read passt pid: %s", error->message);
113         return -1;
114     }
115 
116     s->pid = (pid_t)g_ascii_strtoll(contents, NULL, 10);
117     if (s->pid <= 0) {
118         error_setg(errp, "File '%s' did not contain a valid PID.", s->pidfile);
119         return -1;
120     }
121 
122     return 0;
123 }
124 
125 static int net_passt_stream_start(NetPasstState *s, Error **errp)
126 {
127     QIOChannelSocket *sioc;
128     SocketAddress *addr;
129     int sv[2];
130 
131     if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
132         error_setg_errno(errp, errno, "socketpair() failed");
133         return -1;
134     }
135 
136     /* connect to passt */
137     qemu_set_info_str(&s->data.nc, "connecting to passt");
138 
139     /* create socket channel */
140     sioc = qio_channel_socket_new();
141     s->data.ioc = QIO_CHANNEL(sioc);
142     s->data.nc.link_down = true;
143     s->data.send = net_passt_send;
144 
145     addr = g_new0(SocketAddress, 1);
146     addr->type = SOCKET_ADDRESS_TYPE_FD;
147     addr->u.fd.str = g_strdup_printf("%d", sv[0]);
148 
149     qio_channel_socket_connect_async(sioc, addr,
150                                      net_passt_client_connected, s,
151                                      NULL, NULL);
152 
153     qapi_free_SocketAddress(addr);
154 
155     /* start passt */
156     if (net_passt_start_daemon(s, sv[1], errp) == -1) {
157         close(sv[0]);
158         close(sv[1]);
159         return -1;
160     }
161     close(sv[1]);
162 
163     return 0;
164 }
165 
166 static GPtrArray *net_passt_decode_args(const NetDevPasstOptions *passt,
167                                         gchar *pidfile, Error **errp)
168 {
169     GPtrArray *args = g_ptr_array_new_with_free_func(g_free);
170 
171     if (passt->path) {
172         g_ptr_array_add(args, g_strdup(passt->path));
173     } else {
174         g_ptr_array_add(args, g_strdup("passt"));
175     }
176 
177     /* by default, be quiet */
178     if (!passt->has_quiet || passt->quiet) {
179         g_ptr_array_add(args, g_strdup("--quiet"));
180     }
181 
182     if (passt->has_mtu) {
183         g_ptr_array_add(args, g_strdup("--mtu"));
184         g_ptr_array_add(args, g_strdup_printf("%"PRId64, passt->mtu));
185     }
186 
187     if (passt->address) {
188         g_ptr_array_add(args, g_strdup("--address"));
189         g_ptr_array_add(args, g_strdup(passt->address));
190     }
191 
192     if (passt->netmask) {
193         g_ptr_array_add(args, g_strdup("--netmask"));
194         g_ptr_array_add(args, g_strdup(passt->netmask));
195     }
196 
197     if (passt->mac) {
198         g_ptr_array_add(args, g_strdup("--mac-addr"));
199         g_ptr_array_add(args, g_strdup(passt->mac));
200     }
201 
202     if (passt->gateway) {
203         g_ptr_array_add(args, g_strdup("--gateway"));
204         g_ptr_array_add(args, g_strdup(passt->gateway));
205     }
206 
207     if (passt->interface) {
208         g_ptr_array_add(args, g_strdup("--interface"));
209         g_ptr_array_add(args, g_strdup(passt->interface));
210     }
211 
212     if (passt->outbound) {
213         g_ptr_array_add(args, g_strdup("--outbound"));
214         g_ptr_array_add(args, g_strdup(passt->outbound));
215     }
216 
217     if (passt->outbound_if4) {
218         g_ptr_array_add(args, g_strdup("--outbound-if4"));
219         g_ptr_array_add(args, g_strdup(passt->outbound_if4));
220     }
221 
222     if (passt->outbound_if6) {
223         g_ptr_array_add(args, g_strdup("--outbound-if6"));
224         g_ptr_array_add(args, g_strdup(passt->outbound_if6));
225     }
226 
227     if (passt->dns) {
228         g_ptr_array_add(args, g_strdup("--dns"));
229         g_ptr_array_add(args, g_strdup(passt->dns));
230     }
231     if (passt->fqdn) {
232         g_ptr_array_add(args, g_strdup("--fqdn"));
233         g_ptr_array_add(args, g_strdup(passt->fqdn));
234     }
235 
236     if (passt->has_dhcp_dns && !passt->dhcp_dns) {
237         g_ptr_array_add(args, g_strdup("--no-dhcp-dns"));
238     }
239 
240     if (passt->has_dhcp_search && !passt->dhcp_search) {
241         g_ptr_array_add(args, g_strdup("--no-dhcp-search"));
242     }
243 
244     if (passt->map_host_loopback) {
245         g_ptr_array_add(args, g_strdup("--map-host-loopback"));
246         g_ptr_array_add(args, g_strdup(passt->map_host_loopback));
247     }
248 
249     if (passt->map_guest_addr) {
250         g_ptr_array_add(args, g_strdup("--map-guest-addr"));
251         g_ptr_array_add(args, g_strdup(passt->map_guest_addr));
252     }
253 
254     if (passt->dns_forward) {
255         g_ptr_array_add(args, g_strdup("--dns-forward"));
256         g_ptr_array_add(args, g_strdup(passt->dns_forward));
257     }
258 
259     if (passt->dns_host) {
260         g_ptr_array_add(args, g_strdup("--dns-host"));
261         g_ptr_array_add(args, g_strdup(passt->dns_host));
262     }
263 
264     if (passt->has_tcp && !passt->tcp) {
265         g_ptr_array_add(args, g_strdup("--no-tcp"));
266     }
267 
268     if (passt->has_udp && !passt->udp) {
269         g_ptr_array_add(args, g_strdup("--no-udp"));
270     }
271 
272     if (passt->has_icmp && !passt->icmp) {
273         g_ptr_array_add(args, g_strdup("--no-icmp"));
274     }
275 
276     if (passt->has_dhcp && !passt->dhcp) {
277         g_ptr_array_add(args, g_strdup("--no-dhcp"));
278     }
279 
280     if (passt->has_ndp && !passt->ndp) {
281         g_ptr_array_add(args, g_strdup("--no-ndp"));
282     }
283     if (passt->has_dhcpv6 && !passt->dhcpv6) {
284         g_ptr_array_add(args, g_strdup("--no-dhcpv6"));
285     }
286 
287     if (passt->has_ra && !passt->ra) {
288         g_ptr_array_add(args, g_strdup("--no-ra"));
289     }
290 
291     if (passt->has_freebind && passt->freebind) {
292         g_ptr_array_add(args, g_strdup("--freebind"));
293     }
294 
295     if (passt->has_ipv4 && !passt->ipv4) {
296         g_ptr_array_add(args, g_strdup("--ipv6-only"));
297     }
298 
299     if (passt->has_ipv6 && !passt->ipv6) {
300         g_ptr_array_add(args, g_strdup("--ipv4-only"));
301     }
302 
303     if (passt->has_search && passt->search) {
304         const StringList *list = passt->search;
305         GString *domains = g_string_new(list->value->str);
306 
307         list = list->next;
308         while (list) {
309             g_string_append(domains, " ");
310             g_string_append(domains, list->value->str);
311             list = list->next;
312         }
313 
314         g_ptr_array_add(args, g_strdup("--search"));
315         g_ptr_array_add(args, g_string_free(domains, FALSE));
316     }
317 
318     if (passt->has_tcp_ports && passt->tcp_ports) {
319         const StringList *list = passt->tcp_ports;
320         GString *tcp_ports = g_string_new(list->value->str);
321 
322         list = list->next;
323         while (list) {
324             g_string_append(tcp_ports, ",");
325             g_string_append(tcp_ports, list->value->str);
326             list = list->next;
327         }
328 
329         g_ptr_array_add(args, g_strdup("--tcp-ports"));
330         g_ptr_array_add(args, g_string_free(tcp_ports, FALSE));
331     }
332 
333     if (passt->has_udp_ports && passt->udp_ports) {
334         const StringList *list = passt->udp_ports;
335         GString *udp_ports = g_string_new(list->value->str);
336 
337         list = list->next;
338         while (list) {
339             g_string_append(udp_ports, ",");
340             g_string_append(udp_ports, list->value->str);
341             list = list->next;
342         }
343 
344         g_ptr_array_add(args, g_strdup("--udp-ports"));
345         g_ptr_array_add(args, g_string_free(udp_ports, FALSE));
346     }
347 
348     if (passt->has_param && passt->param) {
349         const StringList *list = passt->param;
350 
351         while (list) {
352             g_ptr_array_add(args, g_strdup(list->value->str));
353             list = list->next;
354         }
355     }
356 
357     /* provide a pid file to be able to kil passt on exit */
358     g_ptr_array_add(args, g_strdup("--pid"));
359     g_ptr_array_add(args, g_strdup(pidfile));
360 
361     /* g_subprocess_launcher_take_fd() will set the socket on fd 3 */
362     g_ptr_array_add(args, g_strdup("--fd"));
363     g_ptr_array_add(args, g_strdup("3"));
364 
365     g_ptr_array_add(args, NULL);
366 
367     return args;
368 }
369 
370 int net_init_passt(const Netdev *netdev, const char *name,
371                    NetClientState *peer, Error **errp)
372 {
373     g_autoptr(GError) error = NULL;
374     NetClientState *nc;
375     NetPasstState *s;
376     GPtrArray *args;
377     gchar *pidfile;
378     int pidfd;
379 
380     assert(netdev->type == NET_CLIENT_DRIVER_PASST);
381 
382     pidfd = g_file_open_tmp("passt-XXXXXX.pid", &pidfile, &error);
383     if (pidfd == -1) {
384         error_setg(errp, "Failed to create temporary file: %s", error->message);
385         return -1;
386     }
387     close(pidfd);
388 
389     args = net_passt_decode_args(&netdev->u.passt, pidfile, errp);
390     if (args == NULL) {
391         g_free(pidfile);
392         return -1;
393     }
394 
395     nc = qemu_new_net_client(&net_passt_info, peer, "passt", name);
396     s = DO_UPCAST(NetPasstState, data.nc, nc);
397 
398     s->args = args;
399     s->pidfile = pidfile;
400 
401     if (net_passt_stream_start(s, errp) == -1) {
402         qemu_del_net_client(nc);
403         return -1;
404     }
405 
406     return 0;
407 }
408