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> 10*da703b06SLaurent 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" 16*da703b06SLaurent Vivier #include "chardev/char-fe.h" 17*da703b06SLaurent Vivier #include "net/vhost_net.h" 18*da703b06SLaurent Vivier #include "hw/virtio/vhost.h" 19*da703b06SLaurent Vivier #include "hw/virtio/vhost-user.h" 20*da703b06SLaurent Vivier #include "standard-headers/linux/virtio_net.h" 21854ee02bSLaurent Vivier #include "stream_data.h" 22854ee02bSLaurent Vivier 23*da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER 24*da703b06SLaurent Vivier static const int user_feature_bits[] = { 25*da703b06SLaurent Vivier VIRTIO_F_NOTIFY_ON_EMPTY, 26*da703b06SLaurent Vivier VIRTIO_F_NOTIFICATION_DATA, 27*da703b06SLaurent Vivier VIRTIO_RING_F_INDIRECT_DESC, 28*da703b06SLaurent Vivier VIRTIO_RING_F_EVENT_IDX, 29*da703b06SLaurent Vivier 30*da703b06SLaurent Vivier VIRTIO_F_ANY_LAYOUT, 31*da703b06SLaurent Vivier VIRTIO_F_VERSION_1, 32*da703b06SLaurent Vivier VIRTIO_NET_F_CSUM, 33*da703b06SLaurent Vivier VIRTIO_NET_F_GUEST_CSUM, 34*da703b06SLaurent Vivier VIRTIO_NET_F_GSO, 35*da703b06SLaurent Vivier VIRTIO_NET_F_GUEST_TSO4, 36*da703b06SLaurent Vivier VIRTIO_NET_F_GUEST_TSO6, 37*da703b06SLaurent Vivier VIRTIO_NET_F_GUEST_ECN, 38*da703b06SLaurent Vivier VIRTIO_NET_F_GUEST_UFO, 39*da703b06SLaurent Vivier VIRTIO_NET_F_HOST_TSO4, 40*da703b06SLaurent Vivier VIRTIO_NET_F_HOST_TSO6, 41*da703b06SLaurent Vivier VIRTIO_NET_F_HOST_ECN, 42*da703b06SLaurent Vivier VIRTIO_NET_F_HOST_UFO, 43*da703b06SLaurent Vivier VIRTIO_NET_F_MRG_RXBUF, 44*da703b06SLaurent Vivier VIRTIO_NET_F_MTU, 45*da703b06SLaurent Vivier VIRTIO_F_IOMMU_PLATFORM, 46*da703b06SLaurent Vivier VIRTIO_F_RING_PACKED, 47*da703b06SLaurent Vivier VIRTIO_F_RING_RESET, 48*da703b06SLaurent Vivier VIRTIO_F_IN_ORDER, 49*da703b06SLaurent Vivier VIRTIO_NET_F_RSS, 50*da703b06SLaurent Vivier VIRTIO_NET_F_RSC_EXT, 51*da703b06SLaurent Vivier VIRTIO_NET_F_HASH_REPORT, 52*da703b06SLaurent Vivier VIRTIO_NET_F_GUEST_USO4, 53*da703b06SLaurent Vivier VIRTIO_NET_F_GUEST_USO6, 54*da703b06SLaurent Vivier VIRTIO_NET_F_HOST_USO, 55*da703b06SLaurent Vivier 56*da703b06SLaurent Vivier /* This bit implies RARP isn't sent by QEMU out of band */ 57*da703b06SLaurent Vivier VIRTIO_NET_F_GUEST_ANNOUNCE, 58*da703b06SLaurent Vivier 59*da703b06SLaurent Vivier VIRTIO_NET_F_MQ, 60*da703b06SLaurent Vivier 61*da703b06SLaurent Vivier VHOST_INVALID_FEATURE_BIT 62*da703b06SLaurent Vivier }; 63*da703b06SLaurent Vivier #endif 64*da703b06SLaurent Vivier 65854ee02bSLaurent Vivier typedef struct NetPasstState { 66854ee02bSLaurent Vivier NetStreamData data; 67854ee02bSLaurent Vivier GPtrArray *args; 68854ee02bSLaurent Vivier gchar *pidfile; 69854ee02bSLaurent Vivier pid_t pid; 70*da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER 71*da703b06SLaurent Vivier /* vhost user */ 72*da703b06SLaurent Vivier VhostUserState *vhost_user; 73*da703b06SLaurent Vivier VHostNetState *vhost_net; 74*da703b06SLaurent Vivier CharBackend vhost_chr; 75*da703b06SLaurent Vivier guint vhost_watch; 76*da703b06SLaurent Vivier uint64_t acked_features; 77*da703b06SLaurent Vivier bool started; 78*da703b06SLaurent 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 87*da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER 88*da703b06SLaurent Vivier if (s->vhost_net) { 89*da703b06SLaurent Vivier vhost_net_cleanup(s->vhost_net); 90*da703b06SLaurent Vivier g_free(s->vhost_net); 91*da703b06SLaurent Vivier s->vhost_net = NULL; 92*da703b06SLaurent Vivier } 93*da703b06SLaurent Vivier if (s->vhost_watch) { 94*da703b06SLaurent Vivier g_source_remove(s->vhost_watch); 95*da703b06SLaurent Vivier s->vhost_watch = 0; 96*da703b06SLaurent Vivier } 97*da703b06SLaurent Vivier qemu_chr_fe_deinit(&s->vhost_chr, true); 98*da703b06SLaurent Vivier if (s->vhost_user) { 99*da703b06SLaurent Vivier vhost_user_cleanup(s->vhost_user); 100*da703b06SLaurent Vivier g_free(s->vhost_user); 101*da703b06SLaurent Vivier s->vhost_user = NULL; 102*da703b06SLaurent Vivier } 103*da703b06SLaurent Vivier #endif 104*da703b06SLaurent 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 138*da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER 139*da703b06SLaurent Vivier static int passt_set_vnet_endianness(NetClientState *nc, bool enable) 140*da703b06SLaurent Vivier { 141*da703b06SLaurent Vivier assert(nc->info->type == NET_CLIENT_DRIVER_PASST); 142*da703b06SLaurent Vivier 143*da703b06SLaurent Vivier return 0; 144*da703b06SLaurent Vivier } 145*da703b06SLaurent Vivier 146*da703b06SLaurent Vivier static bool passt_has_vnet_hdr(NetClientState *nc) 147*da703b06SLaurent Vivier { 148*da703b06SLaurent Vivier NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); 149*da703b06SLaurent Vivier 150*da703b06SLaurent Vivier assert(nc->info->type == NET_CLIENT_DRIVER_PASST); 151*da703b06SLaurent Vivier 152*da703b06SLaurent Vivier return s->vhost_user != NULL; 153*da703b06SLaurent Vivier } 154*da703b06SLaurent Vivier 155*da703b06SLaurent Vivier static bool passt_has_ufo(NetClientState *nc) 156*da703b06SLaurent Vivier { 157*da703b06SLaurent Vivier NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); 158*da703b06SLaurent Vivier 159*da703b06SLaurent Vivier assert(nc->info->type == NET_CLIENT_DRIVER_PASST); 160*da703b06SLaurent Vivier 161*da703b06SLaurent Vivier return s->vhost_user != NULL; 162*da703b06SLaurent Vivier } 163*da703b06SLaurent Vivier 164*da703b06SLaurent Vivier static bool passt_check_peer_type(NetClientState *nc, ObjectClass *oc, 165*da703b06SLaurent Vivier Error **errp) 166*da703b06SLaurent Vivier { 167*da703b06SLaurent Vivier NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); 168*da703b06SLaurent Vivier const char *driver = object_class_get_name(oc); 169*da703b06SLaurent Vivier 170*da703b06SLaurent Vivier assert(nc->info->type == NET_CLIENT_DRIVER_PASST); 171*da703b06SLaurent Vivier 172*da703b06SLaurent Vivier if (s->vhost_user == NULL) { 173*da703b06SLaurent Vivier return true; 174*da703b06SLaurent Vivier } 175*da703b06SLaurent Vivier 176*da703b06SLaurent Vivier if (!g_str_has_prefix(driver, "virtio-net-")) { 177*da703b06SLaurent Vivier error_setg(errp, "vhost-user requires frontend driver virtio-net-*"); 178*da703b06SLaurent Vivier return false; 179*da703b06SLaurent Vivier } 180*da703b06SLaurent Vivier 181*da703b06SLaurent Vivier return true; 182*da703b06SLaurent Vivier } 183*da703b06SLaurent Vivier 184*da703b06SLaurent Vivier static struct vhost_net *passt_get_vhost_net(NetClientState *nc) 185*da703b06SLaurent Vivier { 186*da703b06SLaurent Vivier NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); 187*da703b06SLaurent Vivier 188*da703b06SLaurent Vivier assert(nc->info->type == NET_CLIENT_DRIVER_PASST); 189*da703b06SLaurent Vivier 190*da703b06SLaurent Vivier return s->vhost_net; 191*da703b06SLaurent Vivier } 192*da703b06SLaurent Vivier 193*da703b06SLaurent Vivier static uint64_t passt_get_acked_features(NetClientState *nc) 194*da703b06SLaurent Vivier { 195*da703b06SLaurent Vivier NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); 196*da703b06SLaurent Vivier 197*da703b06SLaurent Vivier assert(nc->info->type == NET_CLIENT_DRIVER_PASST); 198*da703b06SLaurent Vivier 199*da703b06SLaurent Vivier return s->acked_features; 200*da703b06SLaurent Vivier } 201*da703b06SLaurent Vivier 202*da703b06SLaurent Vivier static void passt_save_acked_features(NetClientState *nc) 203*da703b06SLaurent Vivier { 204*da703b06SLaurent Vivier NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); 205*da703b06SLaurent Vivier 206*da703b06SLaurent Vivier assert(nc->info->type == NET_CLIENT_DRIVER_PASST); 207*da703b06SLaurent Vivier 208*da703b06SLaurent Vivier if (s->vhost_net) { 209*da703b06SLaurent Vivier uint64_t features = vhost_net_get_acked_features(s->vhost_net); 210*da703b06SLaurent Vivier if (features) { 211*da703b06SLaurent Vivier s->acked_features = features; 212*da703b06SLaurent Vivier } 213*da703b06SLaurent Vivier } 214*da703b06SLaurent Vivier } 215*da703b06SLaurent Vivier #endif 216*da703b06SLaurent 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, 222*da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER 223*da703b06SLaurent Vivier .has_vnet_hdr = passt_has_vnet_hdr, 224*da703b06SLaurent Vivier .has_ufo = passt_has_ufo, 225*da703b06SLaurent Vivier .set_vnet_be = passt_set_vnet_endianness, 226*da703b06SLaurent Vivier .set_vnet_le = passt_set_vnet_endianness, 227*da703b06SLaurent Vivier .check_peer_type = passt_check_peer_type, 228*da703b06SLaurent Vivier .get_vhost_net = passt_get_vhost_net, 229*da703b06SLaurent 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 328*da703b06SLaurent Vivier #ifdef CONFIG_VHOST_USER 329*da703b06SLaurent Vivier static gboolean passt_vhost_user_watch(void *do_not_use, GIOCondition cond, 330*da703b06SLaurent Vivier void *opaque) 331*da703b06SLaurent Vivier { 332*da703b06SLaurent Vivier NetPasstState *s = opaque; 333*da703b06SLaurent Vivier 334*da703b06SLaurent Vivier qemu_chr_fe_disconnect(&s->vhost_chr); 335*da703b06SLaurent Vivier 336*da703b06SLaurent Vivier return G_SOURCE_CONTINUE; 337*da703b06SLaurent Vivier } 338*da703b06SLaurent Vivier 339*da703b06SLaurent Vivier static void passt_vhost_user_event(void *opaque, QEMUChrEvent event); 340*da703b06SLaurent Vivier 341*da703b06SLaurent Vivier static void chr_closed_bh(void *opaque) 342*da703b06SLaurent Vivier { 343*da703b06SLaurent Vivier NetPasstState *s = opaque; 344*da703b06SLaurent Vivier 345*da703b06SLaurent Vivier passt_save_acked_features(&s->data.nc); 346*da703b06SLaurent Vivier 347*da703b06SLaurent Vivier net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, false); 348*da703b06SLaurent Vivier 349*da703b06SLaurent Vivier qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, passt_vhost_user_event, 350*da703b06SLaurent Vivier NULL, s, NULL, true); 351*da703b06SLaurent Vivier } 352*da703b06SLaurent Vivier 353*da703b06SLaurent Vivier static void passt_vhost_user_stop(NetPasstState *s) 354*da703b06SLaurent Vivier { 355*da703b06SLaurent Vivier passt_save_acked_features(&s->data.nc); 356*da703b06SLaurent Vivier vhost_net_cleanup(s->vhost_net); 357*da703b06SLaurent Vivier } 358*da703b06SLaurent Vivier 359*da703b06SLaurent Vivier static int passt_vhost_user_start(NetPasstState *s, VhostUserState *be) 360*da703b06SLaurent Vivier { 361*da703b06SLaurent Vivier struct vhost_net *net = NULL; 362*da703b06SLaurent Vivier VhostNetOptions options; 363*da703b06SLaurent Vivier 364*da703b06SLaurent Vivier options.backend_type = VHOST_BACKEND_TYPE_USER; 365*da703b06SLaurent Vivier options.net_backend = &s->data.nc; 366*da703b06SLaurent Vivier options.opaque = be; 367*da703b06SLaurent Vivier options.busyloop_timeout = 0; 368*da703b06SLaurent Vivier options.nvqs = 2; 369*da703b06SLaurent Vivier options.feature_bits = user_feature_bits; 370*da703b06SLaurent Vivier options.max_tx_queue_size = VIRTQUEUE_MAX_SIZE; 371*da703b06SLaurent Vivier options.get_acked_features = passt_get_acked_features; 372*da703b06SLaurent Vivier options.save_acked_features = passt_save_acked_features; 373*da703b06SLaurent Vivier options.is_vhost_user = true; 374*da703b06SLaurent Vivier 375*da703b06SLaurent Vivier net = vhost_net_init(&options); 376*da703b06SLaurent Vivier if (!net) { 377*da703b06SLaurent Vivier error_report("failed to init passt vhost_net"); 378*da703b06SLaurent Vivier goto err; 379*da703b06SLaurent Vivier } 380*da703b06SLaurent Vivier 381*da703b06SLaurent Vivier if (s->vhost_net) { 382*da703b06SLaurent Vivier vhost_net_cleanup(s->vhost_net); 383*da703b06SLaurent Vivier g_free(s->vhost_net); 384*da703b06SLaurent Vivier } 385*da703b06SLaurent Vivier s->vhost_net = net; 386*da703b06SLaurent Vivier 387*da703b06SLaurent Vivier return 0; 388*da703b06SLaurent Vivier err: 389*da703b06SLaurent Vivier if (net) { 390*da703b06SLaurent Vivier vhost_net_cleanup(net); 391*da703b06SLaurent Vivier g_free(net); 392*da703b06SLaurent Vivier } 393*da703b06SLaurent Vivier passt_vhost_user_stop(s); 394*da703b06SLaurent Vivier return -1; 395*da703b06SLaurent Vivier } 396*da703b06SLaurent Vivier 397*da703b06SLaurent Vivier static void passt_vhost_user_event(void *opaque, QEMUChrEvent event) 398*da703b06SLaurent Vivier { 399*da703b06SLaurent Vivier NetPasstState *s = opaque; 400*da703b06SLaurent Vivier Error *err = NULL; 401*da703b06SLaurent Vivier 402*da703b06SLaurent Vivier switch (event) { 403*da703b06SLaurent Vivier case CHR_EVENT_OPENED: 404*da703b06SLaurent Vivier if (passt_vhost_user_start(s, s->vhost_user) < 0) { 405*da703b06SLaurent Vivier qemu_chr_fe_disconnect(&s->vhost_chr); 406*da703b06SLaurent Vivier return; 407*da703b06SLaurent Vivier } 408*da703b06SLaurent Vivier s->vhost_watch = qemu_chr_fe_add_watch(&s->vhost_chr, G_IO_HUP, 409*da703b06SLaurent Vivier passt_vhost_user_watch, s); 410*da703b06SLaurent Vivier net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, true); 411*da703b06SLaurent Vivier s->started = true; 412*da703b06SLaurent Vivier break; 413*da703b06SLaurent Vivier case CHR_EVENT_CLOSED: 414*da703b06SLaurent Vivier if (s->vhost_watch) { 415*da703b06SLaurent Vivier AioContext *ctx = qemu_get_current_aio_context(); 416*da703b06SLaurent Vivier 417*da703b06SLaurent Vivier g_source_remove(s->vhost_watch); 418*da703b06SLaurent Vivier s->vhost_watch = 0; 419*da703b06SLaurent Vivier qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, NULL, NULL, 420*da703b06SLaurent Vivier NULL, NULL, false); 421*da703b06SLaurent Vivier 422*da703b06SLaurent Vivier aio_bh_schedule_oneshot(ctx, chr_closed_bh, s); 423*da703b06SLaurent Vivier } 424*da703b06SLaurent Vivier break; 425*da703b06SLaurent Vivier case CHR_EVENT_BREAK: 426*da703b06SLaurent Vivier case CHR_EVENT_MUX_IN: 427*da703b06SLaurent Vivier case CHR_EVENT_MUX_OUT: 428*da703b06SLaurent Vivier /* Ignore */ 429*da703b06SLaurent Vivier break; 430*da703b06SLaurent Vivier } 431*da703b06SLaurent Vivier 432*da703b06SLaurent Vivier if (err) { 433*da703b06SLaurent Vivier error_report_err(err); 434*da703b06SLaurent Vivier } 435*da703b06SLaurent Vivier } 436*da703b06SLaurent Vivier 437*da703b06SLaurent Vivier static int net_passt_vhost_user_init(NetPasstState *s, Error **errp) 438*da703b06SLaurent Vivier { 439*da703b06SLaurent Vivier Chardev *chr; 440*da703b06SLaurent Vivier int sv[2]; 441*da703b06SLaurent Vivier 442*da703b06SLaurent Vivier if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) { 443*da703b06SLaurent Vivier error_setg_errno(errp, errno, "socketpair() failed"); 444*da703b06SLaurent Vivier return -1; 445*da703b06SLaurent Vivier } 446*da703b06SLaurent Vivier 447*da703b06SLaurent Vivier /* connect to passt */ 448*da703b06SLaurent Vivier qemu_set_info_str(&s->data.nc, "connecting to passt"); 449*da703b06SLaurent Vivier 450*da703b06SLaurent Vivier /* create chardev */ 451*da703b06SLaurent Vivier 452*da703b06SLaurent Vivier chr = CHARDEV(object_new(TYPE_CHARDEV_SOCKET)); 453*da703b06SLaurent Vivier if (!chr || qemu_chr_add_client(chr, sv[0]) == -1) { 454*da703b06SLaurent Vivier object_unref(OBJECT(chr)); 455*da703b06SLaurent Vivier error_setg(errp, "Failed to make socket chardev"); 456*da703b06SLaurent Vivier goto err; 457*da703b06SLaurent Vivier } 458*da703b06SLaurent Vivier 459*da703b06SLaurent Vivier s->vhost_user = g_new0(struct VhostUserState, 1); 460*da703b06SLaurent Vivier if (!qemu_chr_fe_init(&s->vhost_chr, chr, errp) || 461*da703b06SLaurent Vivier !vhost_user_init(s->vhost_user, &s->vhost_chr, errp)) { 462*da703b06SLaurent Vivier goto err; 463*da703b06SLaurent Vivier } 464*da703b06SLaurent Vivier 465*da703b06SLaurent Vivier /* start passt */ 466*da703b06SLaurent Vivier if (net_passt_start_daemon(s, sv[1], errp) == -1) { 467*da703b06SLaurent Vivier goto err; 468*da703b06SLaurent Vivier } 469*da703b06SLaurent Vivier 470*da703b06SLaurent Vivier do { 471*da703b06SLaurent Vivier if (qemu_chr_fe_wait_connected(&s->vhost_chr, errp) < 0) { 472*da703b06SLaurent Vivier goto err; 473*da703b06SLaurent Vivier } 474*da703b06SLaurent Vivier 475*da703b06SLaurent Vivier qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, 476*da703b06SLaurent Vivier passt_vhost_user_event, NULL, s, NULL, 477*da703b06SLaurent Vivier true); 478*da703b06SLaurent Vivier } while (!s->started); 479*da703b06SLaurent Vivier 480*da703b06SLaurent Vivier qemu_set_info_str(&s->data.nc, "vhost-user,connected to pid %d", s->pid); 481*da703b06SLaurent Vivier 482*da703b06SLaurent Vivier close(sv[1]); 483*da703b06SLaurent Vivier return 0; 484*da703b06SLaurent Vivier err: 485*da703b06SLaurent Vivier close(sv[0]); 486*da703b06SLaurent Vivier close(sv[1]); 487*da703b06SLaurent Vivier 488*da703b06SLaurent Vivier return -1; 489*da703b06SLaurent Vivier } 490*da703b06SLaurent Vivier #else 491*da703b06SLaurent Vivier static int net_passt_vhost_user_init(NetPasstState *s, Error **errp) 492*da703b06SLaurent Vivier { 493*da703b06SLaurent Vivier error_setg(errp, "vhost-user support has not been built"); 494*da703b06SLaurent Vivier 495*da703b06SLaurent Vivier return -1; 496*da703b06SLaurent Vivier } 497*da703b06SLaurent Vivier #endif 498*da703b06SLaurent Vivier 499854ee02bSLaurent Vivier static GPtrArray *net_passt_decode_args(const NetDevPasstOptions *passt, 500854ee02bSLaurent Vivier gchar *pidfile, Error **errp) 501854ee02bSLaurent Vivier { 502854ee02bSLaurent Vivier GPtrArray *args = g_ptr_array_new_with_free_func(g_free); 503854ee02bSLaurent Vivier 504854ee02bSLaurent Vivier if (passt->path) { 505854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->path)); 506854ee02bSLaurent Vivier } else { 507854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("passt")); 508854ee02bSLaurent Vivier } 509854ee02bSLaurent Vivier 510*da703b06SLaurent Vivier if (passt->has_vhost_user && passt->vhost_user) { 511*da703b06SLaurent Vivier g_ptr_array_add(args, g_strdup("--vhost-user")); 512*da703b06SLaurent Vivier } 513*da703b06SLaurent Vivier 514854ee02bSLaurent Vivier /* by default, be quiet */ 515854ee02bSLaurent Vivier if (!passt->has_quiet || passt->quiet) { 516854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--quiet")); 517854ee02bSLaurent Vivier } 518854ee02bSLaurent Vivier 519854ee02bSLaurent Vivier if (passt->has_mtu) { 520854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--mtu")); 521854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup_printf("%"PRId64, passt->mtu)); 522854ee02bSLaurent Vivier } 523854ee02bSLaurent Vivier 524854ee02bSLaurent Vivier if (passt->address) { 525854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--address")); 526854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->address)); 527854ee02bSLaurent Vivier } 528854ee02bSLaurent Vivier 529854ee02bSLaurent Vivier if (passt->netmask) { 530854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--netmask")); 531854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->netmask)); 532854ee02bSLaurent Vivier } 533854ee02bSLaurent Vivier 534854ee02bSLaurent Vivier if (passt->mac) { 535854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--mac-addr")); 536854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->mac)); 537854ee02bSLaurent Vivier } 538854ee02bSLaurent Vivier 539854ee02bSLaurent Vivier if (passt->gateway) { 540854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--gateway")); 541854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->gateway)); 542854ee02bSLaurent Vivier } 543854ee02bSLaurent Vivier 544854ee02bSLaurent Vivier if (passt->interface) { 545854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--interface")); 546854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->interface)); 547854ee02bSLaurent Vivier } 548854ee02bSLaurent Vivier 549854ee02bSLaurent Vivier if (passt->outbound) { 550854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--outbound")); 551854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->outbound)); 552854ee02bSLaurent Vivier } 553854ee02bSLaurent Vivier 554854ee02bSLaurent Vivier if (passt->outbound_if4) { 555854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--outbound-if4")); 556854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->outbound_if4)); 557854ee02bSLaurent Vivier } 558854ee02bSLaurent Vivier 559854ee02bSLaurent Vivier if (passt->outbound_if6) { 560854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--outbound-if6")); 561854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->outbound_if6)); 562854ee02bSLaurent Vivier } 563854ee02bSLaurent Vivier 564854ee02bSLaurent Vivier if (passt->dns) { 565854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--dns")); 566854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->dns)); 567854ee02bSLaurent Vivier } 568854ee02bSLaurent Vivier if (passt->fqdn) { 569854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--fqdn")); 570854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->fqdn)); 571854ee02bSLaurent Vivier } 572854ee02bSLaurent Vivier 573854ee02bSLaurent Vivier if (passt->has_dhcp_dns && !passt->dhcp_dns) { 574854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--no-dhcp-dns")); 575854ee02bSLaurent Vivier } 576854ee02bSLaurent Vivier 577854ee02bSLaurent Vivier if (passt->has_dhcp_search && !passt->dhcp_search) { 578854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--no-dhcp-search")); 579854ee02bSLaurent Vivier } 580854ee02bSLaurent Vivier 581854ee02bSLaurent Vivier if (passt->map_host_loopback) { 582854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--map-host-loopback")); 583854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->map_host_loopback)); 584854ee02bSLaurent Vivier } 585854ee02bSLaurent Vivier 586854ee02bSLaurent Vivier if (passt->map_guest_addr) { 587854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--map-guest-addr")); 588854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->map_guest_addr)); 589854ee02bSLaurent Vivier } 590854ee02bSLaurent Vivier 591854ee02bSLaurent Vivier if (passt->dns_forward) { 592854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--dns-forward")); 593854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->dns_forward)); 594854ee02bSLaurent Vivier } 595854ee02bSLaurent Vivier 596854ee02bSLaurent Vivier if (passt->dns_host) { 597854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--dns-host")); 598854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(passt->dns_host)); 599854ee02bSLaurent Vivier } 600854ee02bSLaurent Vivier 601854ee02bSLaurent Vivier if (passt->has_tcp && !passt->tcp) { 602854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--no-tcp")); 603854ee02bSLaurent Vivier } 604854ee02bSLaurent Vivier 605854ee02bSLaurent Vivier if (passt->has_udp && !passt->udp) { 606854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--no-udp")); 607854ee02bSLaurent Vivier } 608854ee02bSLaurent Vivier 609854ee02bSLaurent Vivier if (passt->has_icmp && !passt->icmp) { 610854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--no-icmp")); 611854ee02bSLaurent Vivier } 612854ee02bSLaurent Vivier 613854ee02bSLaurent Vivier if (passt->has_dhcp && !passt->dhcp) { 614854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--no-dhcp")); 615854ee02bSLaurent Vivier } 616854ee02bSLaurent Vivier 617854ee02bSLaurent Vivier if (passt->has_ndp && !passt->ndp) { 618854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--no-ndp")); 619854ee02bSLaurent Vivier } 620854ee02bSLaurent Vivier if (passt->has_dhcpv6 && !passt->dhcpv6) { 621854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--no-dhcpv6")); 622854ee02bSLaurent Vivier } 623854ee02bSLaurent Vivier 624854ee02bSLaurent Vivier if (passt->has_ra && !passt->ra) { 625854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--no-ra")); 626854ee02bSLaurent Vivier } 627854ee02bSLaurent Vivier 628854ee02bSLaurent Vivier if (passt->has_freebind && passt->freebind) { 629854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--freebind")); 630854ee02bSLaurent Vivier } 631854ee02bSLaurent Vivier 632854ee02bSLaurent Vivier if (passt->has_ipv4 && !passt->ipv4) { 633854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--ipv6-only")); 634854ee02bSLaurent Vivier } 635854ee02bSLaurent Vivier 636854ee02bSLaurent Vivier if (passt->has_ipv6 && !passt->ipv6) { 637854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--ipv4-only")); 638854ee02bSLaurent Vivier } 639854ee02bSLaurent Vivier 640854ee02bSLaurent Vivier if (passt->has_search && passt->search) { 641854ee02bSLaurent Vivier const StringList *list = passt->search; 642854ee02bSLaurent Vivier GString *domains = g_string_new(list->value->str); 643854ee02bSLaurent Vivier 644854ee02bSLaurent Vivier list = list->next; 645854ee02bSLaurent Vivier while (list) { 646854ee02bSLaurent Vivier g_string_append(domains, " "); 647854ee02bSLaurent Vivier g_string_append(domains, list->value->str); 648854ee02bSLaurent Vivier list = list->next; 649854ee02bSLaurent Vivier } 650854ee02bSLaurent Vivier 651854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--search")); 652854ee02bSLaurent Vivier g_ptr_array_add(args, g_string_free(domains, FALSE)); 653854ee02bSLaurent Vivier } 654854ee02bSLaurent Vivier 655854ee02bSLaurent Vivier if (passt->has_tcp_ports && passt->tcp_ports) { 656854ee02bSLaurent Vivier const StringList *list = passt->tcp_ports; 657854ee02bSLaurent Vivier GString *tcp_ports = g_string_new(list->value->str); 658854ee02bSLaurent Vivier 659854ee02bSLaurent Vivier list = list->next; 660854ee02bSLaurent Vivier while (list) { 661854ee02bSLaurent Vivier g_string_append(tcp_ports, ","); 662854ee02bSLaurent Vivier g_string_append(tcp_ports, list->value->str); 663854ee02bSLaurent Vivier list = list->next; 664854ee02bSLaurent Vivier } 665854ee02bSLaurent Vivier 666854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--tcp-ports")); 667854ee02bSLaurent Vivier g_ptr_array_add(args, g_string_free(tcp_ports, FALSE)); 668854ee02bSLaurent Vivier } 669854ee02bSLaurent Vivier 670854ee02bSLaurent Vivier if (passt->has_udp_ports && passt->udp_ports) { 671854ee02bSLaurent Vivier const StringList *list = passt->udp_ports; 672854ee02bSLaurent Vivier GString *udp_ports = g_string_new(list->value->str); 673854ee02bSLaurent Vivier 674854ee02bSLaurent Vivier list = list->next; 675854ee02bSLaurent Vivier while (list) { 676854ee02bSLaurent Vivier g_string_append(udp_ports, ","); 677854ee02bSLaurent Vivier g_string_append(udp_ports, list->value->str); 678854ee02bSLaurent Vivier list = list->next; 679854ee02bSLaurent Vivier } 680854ee02bSLaurent Vivier 681854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--udp-ports")); 682854ee02bSLaurent Vivier g_ptr_array_add(args, g_string_free(udp_ports, FALSE)); 683854ee02bSLaurent Vivier } 684854ee02bSLaurent Vivier 685854ee02bSLaurent Vivier if (passt->has_param && passt->param) { 686854ee02bSLaurent Vivier const StringList *list = passt->param; 687854ee02bSLaurent Vivier 688854ee02bSLaurent Vivier while (list) { 689854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(list->value->str)); 690854ee02bSLaurent Vivier list = list->next; 691854ee02bSLaurent Vivier } 692854ee02bSLaurent Vivier } 693854ee02bSLaurent Vivier 694854ee02bSLaurent Vivier /* provide a pid file to be able to kil passt on exit */ 695854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--pid")); 696854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup(pidfile)); 697854ee02bSLaurent Vivier 698854ee02bSLaurent Vivier /* g_subprocess_launcher_take_fd() will set the socket on fd 3 */ 699854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("--fd")); 700854ee02bSLaurent Vivier g_ptr_array_add(args, g_strdup("3")); 701854ee02bSLaurent Vivier 702854ee02bSLaurent Vivier g_ptr_array_add(args, NULL); 703854ee02bSLaurent Vivier 704854ee02bSLaurent Vivier return args; 705854ee02bSLaurent Vivier } 706854ee02bSLaurent Vivier 707854ee02bSLaurent Vivier int net_init_passt(const Netdev *netdev, const char *name, 708854ee02bSLaurent Vivier NetClientState *peer, Error **errp) 709854ee02bSLaurent Vivier { 710854ee02bSLaurent Vivier g_autoptr(GError) error = NULL; 711854ee02bSLaurent Vivier NetClientState *nc; 712854ee02bSLaurent Vivier NetPasstState *s; 713854ee02bSLaurent Vivier GPtrArray *args; 714854ee02bSLaurent Vivier gchar *pidfile; 715854ee02bSLaurent Vivier int pidfd; 716854ee02bSLaurent Vivier 717854ee02bSLaurent Vivier assert(netdev->type == NET_CLIENT_DRIVER_PASST); 718854ee02bSLaurent Vivier 719854ee02bSLaurent Vivier pidfd = g_file_open_tmp("passt-XXXXXX.pid", &pidfile, &error); 720854ee02bSLaurent Vivier if (pidfd == -1) { 721854ee02bSLaurent Vivier error_setg(errp, "Failed to create temporary file: %s", error->message); 722854ee02bSLaurent Vivier return -1; 723854ee02bSLaurent Vivier } 724854ee02bSLaurent Vivier close(pidfd); 725854ee02bSLaurent Vivier 726854ee02bSLaurent Vivier args = net_passt_decode_args(&netdev->u.passt, pidfile, errp); 727854ee02bSLaurent Vivier if (args == NULL) { 728854ee02bSLaurent Vivier g_free(pidfile); 729854ee02bSLaurent Vivier return -1; 730854ee02bSLaurent Vivier } 731854ee02bSLaurent Vivier 732854ee02bSLaurent Vivier nc = qemu_new_net_client(&net_passt_info, peer, "passt", name); 733854ee02bSLaurent Vivier s = DO_UPCAST(NetPasstState, data.nc, nc); 734854ee02bSLaurent Vivier 735854ee02bSLaurent Vivier s->args = args; 736854ee02bSLaurent Vivier s->pidfile = pidfile; 737854ee02bSLaurent Vivier 738*da703b06SLaurent Vivier if (netdev->u.passt.has_vhost_user && netdev->u.passt.vhost_user) { 739*da703b06SLaurent Vivier if (net_passt_vhost_user_init(s, errp) == -1) { 740*da703b06SLaurent Vivier qemu_del_net_client(nc); 741*da703b06SLaurent Vivier return -1; 742*da703b06SLaurent Vivier } 743*da703b06SLaurent Vivier 744*da703b06SLaurent Vivier return 0; 745*da703b06SLaurent Vivier } 746*da703b06SLaurent Vivier 747854ee02bSLaurent Vivier if (net_passt_stream_start(s, errp) == -1) { 748854ee02bSLaurent Vivier qemu_del_net_client(nc); 749854ee02bSLaurent Vivier return -1; 750854ee02bSLaurent Vivier } 751854ee02bSLaurent Vivier 752854ee02bSLaurent Vivier return 0; 753854ee02bSLaurent Vivier } 754