xref: /openbmc/qemu/net/passt.c (revision c40ef7243fdbec816242cb0ded569a61270ea7c3)
1854ee02bSLaurent Vivier /*
2854ee02bSLaurent Vivier  * passt network backend
3854ee02bSLaurent Vivier  *
4854ee02bSLaurent Vivier  * Copyright Red Hat
5854ee02bSLaurent Vivier  *
6854ee02bSLaurent Vivier  * SPDX-License-Identifier: GPL-2.0-or-later
7854ee02bSLaurent Vivier  */
8854ee02bSLaurent Vivier #include "qemu/osdep.h"
9854ee02bSLaurent Vivier #include <glib/gstdio.h>
10da703b06SLaurent Vivier #include "qemu/error-report.h"
11854ee02bSLaurent Vivier #include <gio/gio.h>
12854ee02bSLaurent Vivier #include "net/net.h"
13854ee02bSLaurent Vivier #include "clients.h"
14854ee02bSLaurent Vivier #include "qapi/error.h"
15854ee02bSLaurent Vivier #include "io/net-listener.h"
16da703b06SLaurent Vivier #include "chardev/char-fe.h"
17da703b06SLaurent Vivier #include "net/vhost_net.h"
18da703b06SLaurent Vivier #include "hw/virtio/vhost.h"
19da703b06SLaurent Vivier #include "hw/virtio/vhost-user.h"
20da703b06SLaurent Vivier #include "standard-headers/linux/virtio_net.h"
21854ee02bSLaurent Vivier #include "stream_data.h"
22854ee02bSLaurent Vivier 
23da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER
24da703b06SLaurent Vivier static const int user_feature_bits[] = {
25da703b06SLaurent Vivier     VIRTIO_F_NOTIFY_ON_EMPTY,
26da703b06SLaurent Vivier     VIRTIO_F_NOTIFICATION_DATA,
27da703b06SLaurent Vivier     VIRTIO_RING_F_INDIRECT_DESC,
28da703b06SLaurent Vivier     VIRTIO_RING_F_EVENT_IDX,
29da703b06SLaurent Vivier 
30da703b06SLaurent Vivier     VIRTIO_F_ANY_LAYOUT,
31da703b06SLaurent Vivier     VIRTIO_F_VERSION_1,
32da703b06SLaurent Vivier     VIRTIO_NET_F_CSUM,
33da703b06SLaurent Vivier     VIRTIO_NET_F_GUEST_CSUM,
34da703b06SLaurent Vivier     VIRTIO_NET_F_GSO,
35da703b06SLaurent Vivier     VIRTIO_NET_F_GUEST_TSO4,
36da703b06SLaurent Vivier     VIRTIO_NET_F_GUEST_TSO6,
37da703b06SLaurent Vivier     VIRTIO_NET_F_GUEST_ECN,
38da703b06SLaurent Vivier     VIRTIO_NET_F_GUEST_UFO,
39da703b06SLaurent Vivier     VIRTIO_NET_F_HOST_TSO4,
40da703b06SLaurent Vivier     VIRTIO_NET_F_HOST_TSO6,
41da703b06SLaurent Vivier     VIRTIO_NET_F_HOST_ECN,
42da703b06SLaurent Vivier     VIRTIO_NET_F_HOST_UFO,
43da703b06SLaurent Vivier     VIRTIO_NET_F_MRG_RXBUF,
44da703b06SLaurent Vivier     VIRTIO_NET_F_MTU,
45da703b06SLaurent Vivier     VIRTIO_F_IOMMU_PLATFORM,
46da703b06SLaurent Vivier     VIRTIO_F_RING_PACKED,
47da703b06SLaurent Vivier     VIRTIO_F_RING_RESET,
48da703b06SLaurent Vivier     VIRTIO_F_IN_ORDER,
49da703b06SLaurent Vivier     VIRTIO_NET_F_RSS,
50da703b06SLaurent Vivier     VIRTIO_NET_F_RSC_EXT,
51da703b06SLaurent Vivier     VIRTIO_NET_F_HASH_REPORT,
52da703b06SLaurent Vivier     VIRTIO_NET_F_GUEST_USO4,
53da703b06SLaurent Vivier     VIRTIO_NET_F_GUEST_USO6,
54da703b06SLaurent Vivier     VIRTIO_NET_F_HOST_USO,
55da703b06SLaurent Vivier 
56da703b06SLaurent Vivier     /* This bit implies RARP isn't sent by QEMU out of band */
57da703b06SLaurent Vivier     VIRTIO_NET_F_GUEST_ANNOUNCE,
58da703b06SLaurent Vivier 
59da703b06SLaurent Vivier     VIRTIO_NET_F_MQ,
60da703b06SLaurent Vivier 
61da703b06SLaurent Vivier     VHOST_INVALID_FEATURE_BIT
62da703b06SLaurent Vivier };
63da703b06SLaurent Vivier #endif
64da703b06SLaurent Vivier 
65854ee02bSLaurent Vivier typedef struct NetPasstState {
66854ee02bSLaurent Vivier     NetStreamData data;
67854ee02bSLaurent Vivier     GPtrArray *args;
68854ee02bSLaurent Vivier     gchar *pidfile;
69854ee02bSLaurent Vivier     pid_t pid;
70da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER
71da703b06SLaurent Vivier     /* vhost user */
72da703b06SLaurent Vivier     VhostUserState *vhost_user;
73da703b06SLaurent Vivier     VHostNetState *vhost_net;
74da703b06SLaurent Vivier     CharBackend vhost_chr;
75da703b06SLaurent Vivier     guint vhost_watch;
76da703b06SLaurent Vivier     uint64_t acked_features;
77da703b06SLaurent Vivier     bool started;
78da703b06SLaurent Vivier #endif
79854ee02bSLaurent Vivier } NetPasstState;
80854ee02bSLaurent Vivier 
81854ee02bSLaurent Vivier static int net_passt_stream_start(NetPasstState *s, Error **errp);
82854ee02bSLaurent Vivier 
83854ee02bSLaurent Vivier static void net_passt_cleanup(NetClientState *nc)
84854ee02bSLaurent Vivier {
85854ee02bSLaurent Vivier     NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
86854ee02bSLaurent Vivier 
87da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER
88da703b06SLaurent Vivier     if (s->vhost_net) {
89da703b06SLaurent Vivier         vhost_net_cleanup(s->vhost_net);
90da703b06SLaurent Vivier         g_free(s->vhost_net);
91da703b06SLaurent Vivier         s->vhost_net = NULL;
92da703b06SLaurent Vivier     }
93da703b06SLaurent Vivier     if (s->vhost_watch) {
94da703b06SLaurent Vivier         g_source_remove(s->vhost_watch);
95da703b06SLaurent Vivier         s->vhost_watch = 0;
96da703b06SLaurent Vivier     }
97da703b06SLaurent Vivier     qemu_chr_fe_deinit(&s->vhost_chr, true);
98da703b06SLaurent Vivier     if (s->vhost_user) {
99da703b06SLaurent Vivier         vhost_user_cleanup(s->vhost_user);
100da703b06SLaurent Vivier         g_free(s->vhost_user);
101da703b06SLaurent Vivier         s->vhost_user = NULL;
102da703b06SLaurent Vivier     }
103da703b06SLaurent Vivier #endif
104da703b06SLaurent Vivier 
105854ee02bSLaurent Vivier     kill(s->pid, SIGTERM);
106854ee02bSLaurent Vivier     g_remove(s->pidfile);
107854ee02bSLaurent Vivier     g_free(s->pidfile);
108854ee02bSLaurent Vivier     g_ptr_array_free(s->args, TRUE);
109854ee02bSLaurent Vivier }
110854ee02bSLaurent Vivier 
111854ee02bSLaurent Vivier static ssize_t net_passt_receive(NetClientState *nc, const uint8_t *buf,
112854ee02bSLaurent Vivier                                   size_t size)
113854ee02bSLaurent Vivier {
114854ee02bSLaurent Vivier     NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc);
115854ee02bSLaurent Vivier 
116854ee02bSLaurent Vivier     return net_stream_data_receive(d, buf, size);
117854ee02bSLaurent Vivier }
118854ee02bSLaurent Vivier 
119854ee02bSLaurent Vivier static gboolean net_passt_send(QIOChannel *ioc, GIOCondition condition,
120854ee02bSLaurent Vivier                                 gpointer data)
121854ee02bSLaurent Vivier {
122854ee02bSLaurent Vivier     if (net_stream_data_send(ioc, condition, data) == G_SOURCE_REMOVE) {
123854ee02bSLaurent Vivier         NetPasstState *s = DO_UPCAST(NetPasstState, data, data);
124854ee02bSLaurent Vivier         Error *error;
125854ee02bSLaurent Vivier 
126854ee02bSLaurent Vivier         /* we need to restart passt */
127854ee02bSLaurent Vivier         kill(s->pid, SIGTERM);
128854ee02bSLaurent Vivier         if (net_passt_stream_start(s, &error) == -1) {
129854ee02bSLaurent Vivier             error_report_err(error);
130854ee02bSLaurent Vivier         }
131854ee02bSLaurent Vivier 
132854ee02bSLaurent Vivier         return G_SOURCE_REMOVE;
133854ee02bSLaurent Vivier     }
134854ee02bSLaurent Vivier 
135854ee02bSLaurent Vivier     return G_SOURCE_CONTINUE;
136854ee02bSLaurent Vivier }
137854ee02bSLaurent Vivier 
138da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER
139da703b06SLaurent Vivier static int passt_set_vnet_endianness(NetClientState *nc, bool enable)
140da703b06SLaurent Vivier {
141da703b06SLaurent Vivier     assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
142da703b06SLaurent Vivier 
143da703b06SLaurent Vivier     return 0;
144da703b06SLaurent Vivier }
145da703b06SLaurent Vivier 
146da703b06SLaurent Vivier static bool passt_has_vnet_hdr(NetClientState *nc)
147da703b06SLaurent Vivier {
148da703b06SLaurent Vivier     NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
149da703b06SLaurent Vivier 
150da703b06SLaurent Vivier     assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
151da703b06SLaurent Vivier 
152da703b06SLaurent Vivier     return s->vhost_user != NULL;
153da703b06SLaurent Vivier }
154da703b06SLaurent Vivier 
155da703b06SLaurent Vivier static bool passt_has_ufo(NetClientState *nc)
156da703b06SLaurent Vivier {
157da703b06SLaurent Vivier     NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
158da703b06SLaurent Vivier 
159da703b06SLaurent Vivier     assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
160da703b06SLaurent Vivier 
161da703b06SLaurent Vivier     return s->vhost_user != NULL;
162da703b06SLaurent Vivier }
163da703b06SLaurent Vivier 
164da703b06SLaurent Vivier static bool passt_check_peer_type(NetClientState *nc, ObjectClass *oc,
165da703b06SLaurent Vivier                                              Error **errp)
166da703b06SLaurent Vivier {
167da703b06SLaurent Vivier     NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
168da703b06SLaurent Vivier     const char *driver = object_class_get_name(oc);
169da703b06SLaurent Vivier 
170da703b06SLaurent Vivier     assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
171da703b06SLaurent Vivier 
172da703b06SLaurent Vivier     if (s->vhost_user == NULL) {
173da703b06SLaurent Vivier         return true;
174da703b06SLaurent Vivier     }
175da703b06SLaurent Vivier 
176da703b06SLaurent Vivier     if (!g_str_has_prefix(driver, "virtio-net-")) {
177da703b06SLaurent Vivier         error_setg(errp, "vhost-user requires frontend driver virtio-net-*");
178da703b06SLaurent Vivier         return false;
179da703b06SLaurent Vivier     }
180da703b06SLaurent Vivier 
181da703b06SLaurent Vivier     return true;
182da703b06SLaurent Vivier }
183da703b06SLaurent Vivier 
184da703b06SLaurent Vivier static struct vhost_net *passt_get_vhost_net(NetClientState *nc)
185da703b06SLaurent Vivier {
186da703b06SLaurent Vivier     NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
187da703b06SLaurent Vivier 
188da703b06SLaurent Vivier     assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
189da703b06SLaurent Vivier 
190da703b06SLaurent Vivier     return s->vhost_net;
191da703b06SLaurent Vivier }
192da703b06SLaurent Vivier 
193da703b06SLaurent Vivier static uint64_t passt_get_acked_features(NetClientState *nc)
194da703b06SLaurent Vivier {
195da703b06SLaurent Vivier     NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
196da703b06SLaurent Vivier 
197da703b06SLaurent Vivier     assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
198da703b06SLaurent Vivier 
199da703b06SLaurent Vivier     return s->acked_features;
200da703b06SLaurent Vivier }
201da703b06SLaurent Vivier 
202da703b06SLaurent Vivier static void passt_save_acked_features(NetClientState *nc)
203da703b06SLaurent Vivier {
204da703b06SLaurent Vivier     NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
205da703b06SLaurent Vivier 
206da703b06SLaurent Vivier     assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
207da703b06SLaurent Vivier 
208da703b06SLaurent Vivier     if (s->vhost_net) {
209da703b06SLaurent Vivier         uint64_t features = vhost_net_get_acked_features(s->vhost_net);
210da703b06SLaurent Vivier         if (features) {
211da703b06SLaurent Vivier             s->acked_features = features;
212da703b06SLaurent Vivier         }
213da703b06SLaurent Vivier     }
214da703b06SLaurent Vivier }
215da703b06SLaurent Vivier #endif
216da703b06SLaurent Vivier 
217854ee02bSLaurent Vivier static NetClientInfo net_passt_info = {
218854ee02bSLaurent Vivier     .type = NET_CLIENT_DRIVER_PASST,
219854ee02bSLaurent Vivier     .size = sizeof(NetPasstState),
220854ee02bSLaurent Vivier     .receive = net_passt_receive,
221854ee02bSLaurent Vivier     .cleanup = net_passt_cleanup,
222da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER
223da703b06SLaurent Vivier     .has_vnet_hdr = passt_has_vnet_hdr,
224da703b06SLaurent Vivier     .has_ufo = passt_has_ufo,
225da703b06SLaurent Vivier     .set_vnet_be = passt_set_vnet_endianness,
226da703b06SLaurent Vivier     .set_vnet_le = passt_set_vnet_endianness,
227da703b06SLaurent Vivier     .check_peer_type = passt_check_peer_type,
228da703b06SLaurent Vivier     .get_vhost_net = passt_get_vhost_net,
229da703b06SLaurent Vivier #endif
230854ee02bSLaurent Vivier };
231854ee02bSLaurent Vivier 
232854ee02bSLaurent Vivier static void net_passt_client_connected(QIOTask *task, gpointer opaque)
233854ee02bSLaurent Vivier {
234854ee02bSLaurent Vivier     NetPasstState *s = opaque;
235854ee02bSLaurent Vivier 
236854ee02bSLaurent Vivier     if (net_stream_data_client_connected(task, &s->data) == 0) {
237854ee02bSLaurent Vivier         qemu_set_info_str(&s->data.nc, "stream,connected to pid %d", s->pid);
238854ee02bSLaurent Vivier     }
239854ee02bSLaurent Vivier }
240854ee02bSLaurent Vivier 
241854ee02bSLaurent Vivier static int net_passt_start_daemon(NetPasstState *s, int sock, Error **errp)
242854ee02bSLaurent Vivier {
243854ee02bSLaurent Vivier     g_autoptr(GSubprocess) daemon = NULL;
244854ee02bSLaurent Vivier     g_autofree gchar *contents = NULL;
245854ee02bSLaurent Vivier     g_autoptr(GError) error = NULL;
246854ee02bSLaurent Vivier     GSubprocessLauncher *launcher;
247854ee02bSLaurent Vivier 
248854ee02bSLaurent Vivier     qemu_set_info_str(&s->data.nc, "launching passt");
249854ee02bSLaurent Vivier 
250854ee02bSLaurent Vivier     launcher = g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_NONE);
251854ee02bSLaurent Vivier     g_subprocess_launcher_take_fd(launcher, sock, 3);
252854ee02bSLaurent Vivier 
253854ee02bSLaurent Vivier     daemon =  g_subprocess_launcher_spawnv(launcher,
254854ee02bSLaurent Vivier                                            (const gchar *const *)s->args->pdata,
255854ee02bSLaurent Vivier                                            &error);
256854ee02bSLaurent Vivier     g_object_unref(launcher);
257854ee02bSLaurent Vivier 
258854ee02bSLaurent Vivier     if (!daemon) {
259854ee02bSLaurent Vivier         error_setg(errp, "Error creating daemon: %s", error->message);
260854ee02bSLaurent Vivier         return -1;
261854ee02bSLaurent Vivier     }
262854ee02bSLaurent Vivier 
263854ee02bSLaurent Vivier     if (!g_subprocess_wait(daemon, NULL, &error)) {
264854ee02bSLaurent Vivier         error_setg(errp, "Error waiting for daemon: %s", error->message);
265854ee02bSLaurent Vivier         return -1;
266854ee02bSLaurent Vivier     }
267854ee02bSLaurent Vivier 
268854ee02bSLaurent Vivier     if (g_subprocess_get_if_exited(daemon) &&
269854ee02bSLaurent Vivier         g_subprocess_get_exit_status(daemon)) {
270854ee02bSLaurent Vivier         return -1;
271854ee02bSLaurent Vivier     }
272854ee02bSLaurent Vivier 
273854ee02bSLaurent Vivier     if (!g_file_get_contents(s->pidfile, &contents, NULL, &error)) {
274854ee02bSLaurent Vivier         error_setg(errp, "Cannot read passt pid: %s", error->message);
275854ee02bSLaurent Vivier         return -1;
276854ee02bSLaurent Vivier     }
277854ee02bSLaurent Vivier 
278854ee02bSLaurent Vivier     s->pid = (pid_t)g_ascii_strtoll(contents, NULL, 10);
279854ee02bSLaurent Vivier     if (s->pid <= 0) {
280854ee02bSLaurent Vivier         error_setg(errp, "File '%s' did not contain a valid PID.", s->pidfile);
281854ee02bSLaurent Vivier         return -1;
282854ee02bSLaurent Vivier     }
283854ee02bSLaurent Vivier 
284854ee02bSLaurent Vivier     return 0;
285854ee02bSLaurent Vivier }
286854ee02bSLaurent Vivier 
287854ee02bSLaurent Vivier static int net_passt_stream_start(NetPasstState *s, Error **errp)
288854ee02bSLaurent Vivier {
289854ee02bSLaurent Vivier     QIOChannelSocket *sioc;
290854ee02bSLaurent Vivier     SocketAddress *addr;
291854ee02bSLaurent Vivier     int sv[2];
292854ee02bSLaurent Vivier 
293854ee02bSLaurent Vivier     if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
294854ee02bSLaurent Vivier         error_setg_errno(errp, errno, "socketpair() failed");
295854ee02bSLaurent Vivier         return -1;
296854ee02bSLaurent Vivier     }
297854ee02bSLaurent Vivier 
298854ee02bSLaurent Vivier     /* connect to passt */
299854ee02bSLaurent Vivier     qemu_set_info_str(&s->data.nc, "connecting to passt");
300854ee02bSLaurent Vivier 
301854ee02bSLaurent Vivier     /* create socket channel */
302854ee02bSLaurent Vivier     sioc = qio_channel_socket_new();
303854ee02bSLaurent Vivier     s->data.ioc = QIO_CHANNEL(sioc);
304854ee02bSLaurent Vivier     s->data.nc.link_down = true;
305854ee02bSLaurent Vivier     s->data.send = net_passt_send;
306854ee02bSLaurent Vivier 
307854ee02bSLaurent Vivier     addr = g_new0(SocketAddress, 1);
308854ee02bSLaurent Vivier     addr->type = SOCKET_ADDRESS_TYPE_FD;
309854ee02bSLaurent Vivier     addr->u.fd.str = g_strdup_printf("%d", sv[0]);
310854ee02bSLaurent Vivier 
311854ee02bSLaurent Vivier     qio_channel_socket_connect_async(sioc, addr,
312854ee02bSLaurent Vivier                                      net_passt_client_connected, s,
313854ee02bSLaurent Vivier                                      NULL, NULL);
314854ee02bSLaurent Vivier 
315854ee02bSLaurent Vivier     qapi_free_SocketAddress(addr);
316854ee02bSLaurent Vivier 
317854ee02bSLaurent Vivier     /* start passt */
318854ee02bSLaurent Vivier     if (net_passt_start_daemon(s, sv[1], errp) == -1) {
319854ee02bSLaurent Vivier         close(sv[0]);
320854ee02bSLaurent Vivier         close(sv[1]);
321854ee02bSLaurent Vivier         return -1;
322854ee02bSLaurent Vivier     }
323854ee02bSLaurent Vivier     close(sv[1]);
324854ee02bSLaurent Vivier 
325854ee02bSLaurent Vivier     return 0;
326854ee02bSLaurent Vivier }
327854ee02bSLaurent Vivier 
328da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER
329da703b06SLaurent Vivier static gboolean passt_vhost_user_watch(void *do_not_use, GIOCondition cond,
330da703b06SLaurent Vivier                                        void *opaque)
331da703b06SLaurent Vivier {
332da703b06SLaurent Vivier     NetPasstState *s = opaque;
333da703b06SLaurent Vivier 
334da703b06SLaurent Vivier     qemu_chr_fe_disconnect(&s->vhost_chr);
335da703b06SLaurent Vivier 
336da703b06SLaurent Vivier     return G_SOURCE_CONTINUE;
337da703b06SLaurent Vivier }
338da703b06SLaurent Vivier 
339da703b06SLaurent Vivier static void passt_vhost_user_event(void *opaque, QEMUChrEvent event);
340da703b06SLaurent Vivier 
341da703b06SLaurent Vivier static void chr_closed_bh(void *opaque)
342da703b06SLaurent Vivier {
343da703b06SLaurent Vivier     NetPasstState *s = opaque;
344da703b06SLaurent Vivier 
345da703b06SLaurent Vivier     passt_save_acked_features(&s->data.nc);
346da703b06SLaurent Vivier 
347da703b06SLaurent Vivier     net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, false);
348da703b06SLaurent Vivier 
349da703b06SLaurent Vivier     qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, passt_vhost_user_event,
350da703b06SLaurent Vivier                              NULL, s, NULL, true);
351da703b06SLaurent Vivier }
352da703b06SLaurent Vivier 
353da703b06SLaurent Vivier static void passt_vhost_user_stop(NetPasstState *s)
354da703b06SLaurent Vivier {
355da703b06SLaurent Vivier     passt_save_acked_features(&s->data.nc);
356da703b06SLaurent Vivier     vhost_net_cleanup(s->vhost_net);
357da703b06SLaurent Vivier }
358da703b06SLaurent Vivier 
359da703b06SLaurent Vivier static int passt_vhost_user_start(NetPasstState *s, VhostUserState *be)
360da703b06SLaurent Vivier {
361da703b06SLaurent Vivier     struct vhost_net *net = NULL;
362da703b06SLaurent Vivier     VhostNetOptions options;
363da703b06SLaurent Vivier 
364da703b06SLaurent Vivier     options.backend_type = VHOST_BACKEND_TYPE_USER;
365da703b06SLaurent Vivier     options.net_backend = &s->data.nc;
366da703b06SLaurent Vivier     options.opaque = be;
367da703b06SLaurent Vivier     options.busyloop_timeout = 0;
368da703b06SLaurent Vivier     options.nvqs = 2;
369da703b06SLaurent Vivier     options.feature_bits = user_feature_bits;
370da703b06SLaurent Vivier     options.max_tx_queue_size = VIRTQUEUE_MAX_SIZE;
371da703b06SLaurent Vivier     options.get_acked_features = passt_get_acked_features;
372da703b06SLaurent Vivier     options.save_acked_features = passt_save_acked_features;
373da703b06SLaurent Vivier     options.is_vhost_user = true;
374da703b06SLaurent Vivier 
375da703b06SLaurent Vivier     net = vhost_net_init(&options);
376da703b06SLaurent Vivier     if (!net) {
377da703b06SLaurent Vivier         error_report("failed to init passt vhost_net");
378*c40ef724SLaurent Vivier         passt_vhost_user_stop(s);
379*c40ef724SLaurent Vivier         return -1;
380da703b06SLaurent Vivier     }
381da703b06SLaurent Vivier 
382da703b06SLaurent Vivier     if (s->vhost_net) {
383da703b06SLaurent Vivier         vhost_net_cleanup(s->vhost_net);
384da703b06SLaurent Vivier         g_free(s->vhost_net);
385da703b06SLaurent Vivier     }
386da703b06SLaurent Vivier     s->vhost_net = net;
387da703b06SLaurent Vivier 
388da703b06SLaurent Vivier     return 0;
389da703b06SLaurent Vivier }
390da703b06SLaurent Vivier 
391da703b06SLaurent Vivier static void passt_vhost_user_event(void *opaque, QEMUChrEvent event)
392da703b06SLaurent Vivier {
393da703b06SLaurent Vivier     NetPasstState *s = opaque;
394da703b06SLaurent Vivier 
395da703b06SLaurent Vivier     switch (event) {
396da703b06SLaurent Vivier     case CHR_EVENT_OPENED:
397da703b06SLaurent Vivier         if (passt_vhost_user_start(s, s->vhost_user) < 0) {
398da703b06SLaurent Vivier             qemu_chr_fe_disconnect(&s->vhost_chr);
399da703b06SLaurent Vivier             return;
400da703b06SLaurent Vivier         }
401da703b06SLaurent Vivier         s->vhost_watch = qemu_chr_fe_add_watch(&s->vhost_chr, G_IO_HUP,
402da703b06SLaurent Vivier                                                passt_vhost_user_watch, s);
403da703b06SLaurent Vivier         net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, true);
404da703b06SLaurent Vivier         s->started = true;
405da703b06SLaurent Vivier         break;
406da703b06SLaurent Vivier     case CHR_EVENT_CLOSED:
407da703b06SLaurent Vivier         if (s->vhost_watch) {
408da703b06SLaurent Vivier             AioContext *ctx = qemu_get_current_aio_context();
409da703b06SLaurent Vivier 
410da703b06SLaurent Vivier             g_source_remove(s->vhost_watch);
411da703b06SLaurent Vivier             s->vhost_watch = 0;
412da703b06SLaurent Vivier             qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL,  NULL, NULL,
413da703b06SLaurent Vivier                                      NULL, NULL, false);
414da703b06SLaurent Vivier 
415da703b06SLaurent Vivier             aio_bh_schedule_oneshot(ctx, chr_closed_bh, s);
416da703b06SLaurent Vivier         }
417da703b06SLaurent Vivier         break;
418da703b06SLaurent Vivier     case CHR_EVENT_BREAK:
419da703b06SLaurent Vivier     case CHR_EVENT_MUX_IN:
420da703b06SLaurent Vivier     case CHR_EVENT_MUX_OUT:
421da703b06SLaurent Vivier         /* Ignore */
422da703b06SLaurent Vivier         break;
423da703b06SLaurent Vivier     }
424da703b06SLaurent Vivier }
425da703b06SLaurent Vivier 
426da703b06SLaurent Vivier static int net_passt_vhost_user_init(NetPasstState *s, Error **errp)
427da703b06SLaurent Vivier {
428da703b06SLaurent Vivier     Chardev *chr;
429da703b06SLaurent Vivier     int sv[2];
430da703b06SLaurent Vivier 
431da703b06SLaurent Vivier     if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
432da703b06SLaurent Vivier         error_setg_errno(errp, errno, "socketpair() failed");
433da703b06SLaurent Vivier         return -1;
434da703b06SLaurent Vivier     }
435da703b06SLaurent Vivier 
436da703b06SLaurent Vivier     /* connect to passt */
437da703b06SLaurent Vivier     qemu_set_info_str(&s->data.nc, "connecting to passt");
438da703b06SLaurent Vivier 
439da703b06SLaurent Vivier     /* create chardev */
440da703b06SLaurent Vivier 
441da703b06SLaurent Vivier     chr = CHARDEV(object_new(TYPE_CHARDEV_SOCKET));
442da703b06SLaurent Vivier     if (!chr || qemu_chr_add_client(chr, sv[0]) == -1) {
443da703b06SLaurent Vivier         object_unref(OBJECT(chr));
444da703b06SLaurent Vivier         error_setg(errp, "Failed to make socket chardev");
445da703b06SLaurent Vivier         goto err;
446da703b06SLaurent Vivier     }
447da703b06SLaurent Vivier 
448da703b06SLaurent Vivier     s->vhost_user = g_new0(struct VhostUserState, 1);
449da703b06SLaurent Vivier     if (!qemu_chr_fe_init(&s->vhost_chr, chr, errp) ||
450da703b06SLaurent Vivier         !vhost_user_init(s->vhost_user, &s->vhost_chr, errp)) {
451da703b06SLaurent Vivier         goto err;
452da703b06SLaurent Vivier     }
453da703b06SLaurent Vivier 
454da703b06SLaurent Vivier     /* start passt */
455da703b06SLaurent Vivier     if (net_passt_start_daemon(s, sv[1], errp) == -1) {
456da703b06SLaurent Vivier         goto err;
457da703b06SLaurent Vivier     }
458da703b06SLaurent Vivier 
459da703b06SLaurent Vivier     do {
460da703b06SLaurent Vivier         if (qemu_chr_fe_wait_connected(&s->vhost_chr, errp) < 0) {
461da703b06SLaurent Vivier             goto err;
462da703b06SLaurent Vivier         }
463da703b06SLaurent Vivier 
464da703b06SLaurent Vivier         qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL,
465da703b06SLaurent Vivier                                  passt_vhost_user_event, NULL, s, NULL,
466da703b06SLaurent Vivier                                  true);
467da703b06SLaurent Vivier     } while (!s->started);
468da703b06SLaurent Vivier 
469da703b06SLaurent Vivier     qemu_set_info_str(&s->data.nc, "vhost-user,connected to pid %d", s->pid);
470da703b06SLaurent Vivier 
471da703b06SLaurent Vivier     close(sv[1]);
472da703b06SLaurent Vivier     return 0;
473da703b06SLaurent Vivier err:
474da703b06SLaurent Vivier     close(sv[0]);
475da703b06SLaurent Vivier     close(sv[1]);
476da703b06SLaurent Vivier 
477da703b06SLaurent Vivier     return -1;
478da703b06SLaurent Vivier }
479da703b06SLaurent Vivier #else
480da703b06SLaurent Vivier static int net_passt_vhost_user_init(NetPasstState *s, Error **errp)
481da703b06SLaurent Vivier {
482da703b06SLaurent Vivier     error_setg(errp, "vhost-user support has not been built");
483da703b06SLaurent Vivier 
484da703b06SLaurent Vivier     return -1;
485da703b06SLaurent Vivier }
486da703b06SLaurent Vivier #endif
487da703b06SLaurent Vivier 
488854ee02bSLaurent Vivier static GPtrArray *net_passt_decode_args(const NetDevPasstOptions *passt,
489854ee02bSLaurent Vivier                                         gchar *pidfile, Error **errp)
490854ee02bSLaurent Vivier {
491854ee02bSLaurent Vivier     GPtrArray *args = g_ptr_array_new_with_free_func(g_free);
492854ee02bSLaurent Vivier 
493854ee02bSLaurent Vivier     if (passt->path) {
494854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->path));
495854ee02bSLaurent Vivier     } else {
496854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("passt"));
497854ee02bSLaurent Vivier     }
498854ee02bSLaurent Vivier 
499da703b06SLaurent Vivier     if (passt->has_vhost_user && passt->vhost_user) {
500da703b06SLaurent Vivier         g_ptr_array_add(args, g_strdup("--vhost-user"));
501da703b06SLaurent Vivier     }
502da703b06SLaurent Vivier 
503854ee02bSLaurent Vivier     /* by default, be quiet */
504854ee02bSLaurent Vivier     if (!passt->has_quiet || passt->quiet) {
505854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--quiet"));
506854ee02bSLaurent Vivier     }
507854ee02bSLaurent Vivier 
508854ee02bSLaurent Vivier     if (passt->has_mtu) {
509854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--mtu"));
510854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup_printf("%"PRId64, passt->mtu));
511854ee02bSLaurent Vivier     }
512854ee02bSLaurent Vivier 
513854ee02bSLaurent Vivier     if (passt->address) {
514854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--address"));
515854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->address));
516854ee02bSLaurent Vivier     }
517854ee02bSLaurent Vivier 
518854ee02bSLaurent Vivier     if (passt->netmask) {
519854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--netmask"));
520854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->netmask));
521854ee02bSLaurent Vivier     }
522854ee02bSLaurent Vivier 
523854ee02bSLaurent Vivier     if (passt->mac) {
524854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--mac-addr"));
525854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->mac));
526854ee02bSLaurent Vivier     }
527854ee02bSLaurent Vivier 
528854ee02bSLaurent Vivier     if (passt->gateway) {
529854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--gateway"));
530854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->gateway));
531854ee02bSLaurent Vivier     }
532854ee02bSLaurent Vivier 
533854ee02bSLaurent Vivier     if (passt->interface) {
534854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--interface"));
535854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->interface));
536854ee02bSLaurent Vivier     }
537854ee02bSLaurent Vivier 
538854ee02bSLaurent Vivier     if (passt->outbound) {
539854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--outbound"));
540854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->outbound));
541854ee02bSLaurent Vivier     }
542854ee02bSLaurent Vivier 
543854ee02bSLaurent Vivier     if (passt->outbound_if4) {
544854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--outbound-if4"));
545854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->outbound_if4));
546854ee02bSLaurent Vivier     }
547854ee02bSLaurent Vivier 
548854ee02bSLaurent Vivier     if (passt->outbound_if6) {
549854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--outbound-if6"));
550854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->outbound_if6));
551854ee02bSLaurent Vivier     }
552854ee02bSLaurent Vivier 
553854ee02bSLaurent Vivier     if (passt->dns) {
554854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--dns"));
555854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->dns));
556854ee02bSLaurent Vivier     }
557854ee02bSLaurent Vivier     if (passt->fqdn) {
558854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--fqdn"));
559854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->fqdn));
560854ee02bSLaurent Vivier     }
561854ee02bSLaurent Vivier 
562854ee02bSLaurent Vivier     if (passt->has_dhcp_dns && !passt->dhcp_dns) {
563854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--no-dhcp-dns"));
564854ee02bSLaurent Vivier     }
565854ee02bSLaurent Vivier 
566854ee02bSLaurent Vivier     if (passt->has_dhcp_search && !passt->dhcp_search) {
567854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--no-dhcp-search"));
568854ee02bSLaurent Vivier     }
569854ee02bSLaurent Vivier 
570854ee02bSLaurent Vivier     if (passt->map_host_loopback) {
571854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--map-host-loopback"));
572854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->map_host_loopback));
573854ee02bSLaurent Vivier     }
574854ee02bSLaurent Vivier 
575854ee02bSLaurent Vivier     if (passt->map_guest_addr) {
576854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--map-guest-addr"));
577854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->map_guest_addr));
578854ee02bSLaurent Vivier     }
579854ee02bSLaurent Vivier 
580854ee02bSLaurent Vivier     if (passt->dns_forward) {
581854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--dns-forward"));
582854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->dns_forward));
583854ee02bSLaurent Vivier     }
584854ee02bSLaurent Vivier 
585854ee02bSLaurent Vivier     if (passt->dns_host) {
586854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--dns-host"));
587854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup(passt->dns_host));
588854ee02bSLaurent Vivier     }
589854ee02bSLaurent Vivier 
590854ee02bSLaurent Vivier     if (passt->has_tcp && !passt->tcp) {
591854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--no-tcp"));
592854ee02bSLaurent Vivier     }
593854ee02bSLaurent Vivier 
594854ee02bSLaurent Vivier     if (passt->has_udp && !passt->udp) {
595854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--no-udp"));
596854ee02bSLaurent Vivier     }
597854ee02bSLaurent Vivier 
598854ee02bSLaurent Vivier     if (passt->has_icmp && !passt->icmp) {
599854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--no-icmp"));
600854ee02bSLaurent Vivier     }
601854ee02bSLaurent Vivier 
602854ee02bSLaurent Vivier     if (passt->has_dhcp && !passt->dhcp) {
603854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--no-dhcp"));
604854ee02bSLaurent Vivier     }
605854ee02bSLaurent Vivier 
606854ee02bSLaurent Vivier     if (passt->has_ndp && !passt->ndp) {
607854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--no-ndp"));
608854ee02bSLaurent Vivier     }
609854ee02bSLaurent Vivier     if (passt->has_dhcpv6 && !passt->dhcpv6) {
610854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--no-dhcpv6"));
611854ee02bSLaurent Vivier     }
612854ee02bSLaurent Vivier 
613854ee02bSLaurent Vivier     if (passt->has_ra && !passt->ra) {
614854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--no-ra"));
615854ee02bSLaurent Vivier     }
616854ee02bSLaurent Vivier 
617854ee02bSLaurent Vivier     if (passt->has_freebind && passt->freebind) {
618854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--freebind"));
619854ee02bSLaurent Vivier     }
620854ee02bSLaurent Vivier 
621854ee02bSLaurent Vivier     if (passt->has_ipv4 && !passt->ipv4) {
622854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--ipv6-only"));
623854ee02bSLaurent Vivier     }
624854ee02bSLaurent Vivier 
625854ee02bSLaurent Vivier     if (passt->has_ipv6 && !passt->ipv6) {
626854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--ipv4-only"));
627854ee02bSLaurent Vivier     }
628854ee02bSLaurent Vivier 
629854ee02bSLaurent Vivier     if (passt->has_search && passt->search) {
630854ee02bSLaurent Vivier         const StringList *list = passt->search;
631854ee02bSLaurent Vivier         GString *domains = g_string_new(list->value->str);
632854ee02bSLaurent Vivier 
633854ee02bSLaurent Vivier         list = list->next;
634854ee02bSLaurent Vivier         while (list) {
635854ee02bSLaurent Vivier             g_string_append(domains, " ");
636854ee02bSLaurent Vivier             g_string_append(domains, list->value->str);
637854ee02bSLaurent Vivier             list = list->next;
638854ee02bSLaurent Vivier         }
639854ee02bSLaurent Vivier 
640854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--search"));
641854ee02bSLaurent Vivier         g_ptr_array_add(args, g_string_free(domains, FALSE));
642854ee02bSLaurent Vivier     }
643854ee02bSLaurent Vivier 
644854ee02bSLaurent Vivier     if (passt->has_tcp_ports && passt->tcp_ports) {
645854ee02bSLaurent Vivier         const StringList *list = passt->tcp_ports;
646854ee02bSLaurent Vivier         GString *tcp_ports = g_string_new(list->value->str);
647854ee02bSLaurent Vivier 
648854ee02bSLaurent Vivier         list = list->next;
649854ee02bSLaurent Vivier         while (list) {
650854ee02bSLaurent Vivier             g_string_append(tcp_ports, ",");
651854ee02bSLaurent Vivier             g_string_append(tcp_ports, list->value->str);
652854ee02bSLaurent Vivier             list = list->next;
653854ee02bSLaurent Vivier         }
654854ee02bSLaurent Vivier 
655854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--tcp-ports"));
656854ee02bSLaurent Vivier         g_ptr_array_add(args, g_string_free(tcp_ports, FALSE));
657854ee02bSLaurent Vivier     }
658854ee02bSLaurent Vivier 
659854ee02bSLaurent Vivier     if (passt->has_udp_ports && passt->udp_ports) {
660854ee02bSLaurent Vivier         const StringList *list = passt->udp_ports;
661854ee02bSLaurent Vivier         GString *udp_ports = g_string_new(list->value->str);
662854ee02bSLaurent Vivier 
663854ee02bSLaurent Vivier         list = list->next;
664854ee02bSLaurent Vivier         while (list) {
665854ee02bSLaurent Vivier             g_string_append(udp_ports, ",");
666854ee02bSLaurent Vivier             g_string_append(udp_ports, list->value->str);
667854ee02bSLaurent Vivier             list = list->next;
668854ee02bSLaurent Vivier         }
669854ee02bSLaurent Vivier 
670854ee02bSLaurent Vivier         g_ptr_array_add(args, g_strdup("--udp-ports"));
671854ee02bSLaurent Vivier         g_ptr_array_add(args, g_string_free(udp_ports, FALSE));
672854ee02bSLaurent Vivier     }
673854ee02bSLaurent Vivier 
674854ee02bSLaurent Vivier     if (passt->has_param && passt->param) {
675854ee02bSLaurent Vivier         const StringList *list = passt->param;
676854ee02bSLaurent Vivier 
677854ee02bSLaurent Vivier         while (list) {
678854ee02bSLaurent Vivier             g_ptr_array_add(args, g_strdup(list->value->str));
679854ee02bSLaurent Vivier             list = list->next;
680854ee02bSLaurent Vivier         }
681854ee02bSLaurent Vivier     }
682854ee02bSLaurent Vivier 
683854ee02bSLaurent Vivier     /* provide a pid file to be able to kil passt on exit */
684854ee02bSLaurent Vivier     g_ptr_array_add(args, g_strdup("--pid"));
685854ee02bSLaurent Vivier     g_ptr_array_add(args, g_strdup(pidfile));
686854ee02bSLaurent Vivier 
687854ee02bSLaurent Vivier     /* g_subprocess_launcher_take_fd() will set the socket on fd 3 */
688854ee02bSLaurent Vivier     g_ptr_array_add(args, g_strdup("--fd"));
689854ee02bSLaurent Vivier     g_ptr_array_add(args, g_strdup("3"));
690854ee02bSLaurent Vivier 
691854ee02bSLaurent Vivier     g_ptr_array_add(args, NULL);
692854ee02bSLaurent Vivier 
693854ee02bSLaurent Vivier     return args;
694854ee02bSLaurent Vivier }
695854ee02bSLaurent Vivier 
696854ee02bSLaurent Vivier int net_init_passt(const Netdev *netdev, const char *name,
697854ee02bSLaurent Vivier                    NetClientState *peer, Error **errp)
698854ee02bSLaurent Vivier {
699854ee02bSLaurent Vivier     g_autoptr(GError) error = NULL;
700854ee02bSLaurent Vivier     NetClientState *nc;
701854ee02bSLaurent Vivier     NetPasstState *s;
702854ee02bSLaurent Vivier     GPtrArray *args;
703854ee02bSLaurent Vivier     gchar *pidfile;
704854ee02bSLaurent Vivier     int pidfd;
705854ee02bSLaurent Vivier 
706854ee02bSLaurent Vivier     assert(netdev->type == NET_CLIENT_DRIVER_PASST);
707854ee02bSLaurent Vivier 
708854ee02bSLaurent Vivier     pidfd = g_file_open_tmp("passt-XXXXXX.pid", &pidfile, &error);
709854ee02bSLaurent Vivier     if (pidfd == -1) {
710854ee02bSLaurent Vivier         error_setg(errp, "Failed to create temporary file: %s", error->message);
711854ee02bSLaurent Vivier         return -1;
712854ee02bSLaurent Vivier     }
713854ee02bSLaurent Vivier     close(pidfd);
714854ee02bSLaurent Vivier 
715854ee02bSLaurent Vivier     args = net_passt_decode_args(&netdev->u.passt, pidfile, errp);
716854ee02bSLaurent Vivier     if (args == NULL) {
717854ee02bSLaurent Vivier         g_free(pidfile);
718854ee02bSLaurent Vivier         return -1;
719854ee02bSLaurent Vivier     }
720854ee02bSLaurent Vivier 
721854ee02bSLaurent Vivier     nc = qemu_new_net_client(&net_passt_info, peer, "passt", name);
722854ee02bSLaurent Vivier     s = DO_UPCAST(NetPasstState, data.nc, nc);
723854ee02bSLaurent Vivier 
724854ee02bSLaurent Vivier     s->args = args;
725854ee02bSLaurent Vivier     s->pidfile = pidfile;
726854ee02bSLaurent Vivier 
727da703b06SLaurent Vivier     if (netdev->u.passt.has_vhost_user && netdev->u.passt.vhost_user) {
728da703b06SLaurent Vivier         if (net_passt_vhost_user_init(s, errp) == -1) {
729da703b06SLaurent Vivier             qemu_del_net_client(nc);
730da703b06SLaurent Vivier             return -1;
731da703b06SLaurent Vivier         }
732da703b06SLaurent Vivier 
733da703b06SLaurent Vivier         return 0;
734da703b06SLaurent Vivier     }
735da703b06SLaurent Vivier 
736854ee02bSLaurent Vivier     if (net_passt_stream_start(s, errp) == -1) {
737854ee02bSLaurent Vivier         qemu_del_net_client(nc);
738854ee02bSLaurent Vivier         return -1;
739854ee02bSLaurent Vivier     }
740854ee02bSLaurent Vivier 
741854ee02bSLaurent Vivier     return 0;
742854ee02bSLaurent Vivier }
743