| /* |
| * passt network backend |
| * |
| * Copyright Red Hat |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| */ |
| #include "qemu/osdep.h" |
| #include <glib/gstdio.h> |
| #include "qemu/error-report.h" |
| #include <gio/gio.h> |
| #include "net/net.h" |
| #include "clients.h" |
| #include "qapi/error.h" |
| #include "io/net-listener.h" |
| #include "chardev/char-fe.h" |
| #include "net/vhost_net.h" |
| #include "hw/virtio/vhost.h" |
| #include "hw/virtio/vhost-user.h" |
| #include "standard-headers/linux/virtio_net.h" |
| #include "stream_data.h" |
| |
| #ifdef CONFIG_VHOST_USER |
| static const int user_feature_bits[] = { |
| VIRTIO_F_NOTIFY_ON_EMPTY, |
| VIRTIO_F_NOTIFICATION_DATA, |
| VIRTIO_RING_F_INDIRECT_DESC, |
| VIRTIO_RING_F_EVENT_IDX, |
| |
| VIRTIO_F_ANY_LAYOUT, |
| VIRTIO_F_VERSION_1, |
| VIRTIO_NET_F_CSUM, |
| VIRTIO_NET_F_GUEST_CSUM, |
| VIRTIO_NET_F_GSO, |
| VIRTIO_NET_F_GUEST_TSO4, |
| VIRTIO_NET_F_GUEST_TSO6, |
| VIRTIO_NET_F_GUEST_ECN, |
| VIRTIO_NET_F_GUEST_UFO, |
| VIRTIO_NET_F_HOST_TSO4, |
| VIRTIO_NET_F_HOST_TSO6, |
| VIRTIO_NET_F_HOST_ECN, |
| VIRTIO_NET_F_HOST_UFO, |
| VIRTIO_NET_F_MRG_RXBUF, |
| VIRTIO_NET_F_MTU, |
| VIRTIO_F_IOMMU_PLATFORM, |
| VIRTIO_F_RING_PACKED, |
| VIRTIO_F_RING_RESET, |
| VIRTIO_F_IN_ORDER, |
| VIRTIO_NET_F_RSS, |
| VIRTIO_NET_F_RSC_EXT, |
| VIRTIO_NET_F_HASH_REPORT, |
| VIRTIO_NET_F_GUEST_USO4, |
| VIRTIO_NET_F_GUEST_USO6, |
| VIRTIO_NET_F_HOST_USO, |
| |
| /* This bit implies RARP isn't sent by QEMU out of band */ |
| VIRTIO_NET_F_GUEST_ANNOUNCE, |
| |
| VIRTIO_NET_F_MQ, |
| |
| VHOST_INVALID_FEATURE_BIT |
| }; |
| #endif |
| |
| typedef struct NetPasstState { |
| NetStreamData data; |
| GPtrArray *args; |
| gchar *pidfile; |
| pid_t pid; |
| #ifdef CONFIG_VHOST_USER |
| /* vhost user */ |
| VhostUserState *vhost_user; |
| VHostNetState *vhost_net; |
| CharBackend vhost_chr; |
| guint vhost_watch; |
| uint64_t acked_features; |
| bool started; |
| #endif |
| } NetPasstState; |
| |
| static int net_passt_stream_start(NetPasstState *s, Error **errp); |
| |
| static void net_passt_cleanup(NetClientState *nc) |
| { |
| NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); |
| |
| #ifdef CONFIG_VHOST_USER |
| if (s->vhost_net) { |
| vhost_net_cleanup(s->vhost_net); |
| g_free(s->vhost_net); |
| s->vhost_net = NULL; |
| } |
| if (s->vhost_watch) { |
| g_source_remove(s->vhost_watch); |
| s->vhost_watch = 0; |
| } |
| qemu_chr_fe_deinit(&s->vhost_chr, true); |
| if (s->vhost_user) { |
| vhost_user_cleanup(s->vhost_user); |
| g_free(s->vhost_user); |
| s->vhost_user = NULL; |
| } |
| #endif |
| |
| kill(s->pid, SIGTERM); |
| if (g_remove(s->pidfile) != 0) { |
| warn_report("Failed to remove passt pidfile %s: %s", |
| s->pidfile, strerror(errno)); |
| } |
| g_free(s->pidfile); |
| g_ptr_array_free(s->args, TRUE); |
| } |
| |
| static ssize_t net_passt_receive(NetClientState *nc, const uint8_t *buf, |
| size_t size) |
| { |
| NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc); |
| |
| return net_stream_data_receive(d, buf, size); |
| } |
| |
| static gboolean net_passt_send(QIOChannel *ioc, GIOCondition condition, |
| gpointer data) |
| { |
| if (net_stream_data_send(ioc, condition, data) == G_SOURCE_REMOVE) { |
| NetPasstState *s = DO_UPCAST(NetPasstState, data, data); |
| Error *error = NULL; |
| |
| /* we need to restart passt */ |
| kill(s->pid, SIGTERM); |
| if (net_passt_stream_start(s, &error) == -1) { |
| error_report_err(error); |
| } |
| |
| return G_SOURCE_REMOVE; |
| } |
| |
| return G_SOURCE_CONTINUE; |
| } |
| |
| #ifdef CONFIG_VHOST_USER |
| static int passt_set_vnet_endianness(NetClientState *nc, bool enable) |
| { |
| assert(nc->info->type == NET_CLIENT_DRIVER_PASST); |
| |
| return 0; |
| } |
| |
| static bool passt_has_vnet_hdr(NetClientState *nc) |
| { |
| NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); |
| |
| assert(nc->info->type == NET_CLIENT_DRIVER_PASST); |
| |
| return s->vhost_user != NULL; |
| } |
| |
| static bool passt_has_ufo(NetClientState *nc) |
| { |
| NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); |
| |
| assert(nc->info->type == NET_CLIENT_DRIVER_PASST); |
| |
| return s->vhost_user != NULL; |
| } |
| |
| static bool passt_check_peer_type(NetClientState *nc, ObjectClass *oc, |
| Error **errp) |
| { |
| NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); |
| const char *driver = object_class_get_name(oc); |
| |
| assert(nc->info->type == NET_CLIENT_DRIVER_PASST); |
| |
| if (s->vhost_user == NULL) { |
| return true; |
| } |
| |
| if (!g_str_has_prefix(driver, "virtio-net-")) { |
| error_setg(errp, "vhost-user requires frontend driver virtio-net-*"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static struct vhost_net *passt_get_vhost_net(NetClientState *nc) |
| { |
| NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); |
| |
| assert(nc->info->type == NET_CLIENT_DRIVER_PASST); |
| |
| return s->vhost_net; |
| } |
| |
| static uint64_t passt_get_acked_features(NetClientState *nc) |
| { |
| NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); |
| |
| assert(nc->info->type == NET_CLIENT_DRIVER_PASST); |
| |
| return s->acked_features; |
| } |
| |
| static void passt_save_acked_features(NetClientState *nc) |
| { |
| NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); |
| |
| assert(nc->info->type == NET_CLIENT_DRIVER_PASST); |
| |
| if (s->vhost_net) { |
| uint64_t features = vhost_net_get_acked_features(s->vhost_net); |
| if (features) { |
| s->acked_features = features; |
| } |
| } |
| } |
| #endif |
| |
| static NetClientInfo net_passt_info = { |
| .type = NET_CLIENT_DRIVER_PASST, |
| .size = sizeof(NetPasstState), |
| .receive = net_passt_receive, |
| .cleanup = net_passt_cleanup, |
| #ifdef CONFIG_VHOST_USER |
| .has_vnet_hdr = passt_has_vnet_hdr, |
| .has_ufo = passt_has_ufo, |
| .set_vnet_be = passt_set_vnet_endianness, |
| .set_vnet_le = passt_set_vnet_endianness, |
| .check_peer_type = passt_check_peer_type, |
| .get_vhost_net = passt_get_vhost_net, |
| #endif |
| }; |
| |
| static void net_passt_client_connected(QIOTask *task, gpointer opaque) |
| { |
| NetPasstState *s = opaque; |
| |
| if (net_stream_data_client_connected(task, &s->data) == 0) { |
| qemu_set_info_str(&s->data.nc, "stream,connected to pid %d", s->pid); |
| } |
| } |
| |
| static int net_passt_start_daemon(NetPasstState *s, int sock, Error **errp) |
| { |
| g_autoptr(GSubprocess) daemon = NULL; |
| g_autofree gchar *contents = NULL; |
| g_autoptr(GError) error = NULL; |
| GSubprocessLauncher *launcher; |
| |
| qemu_set_info_str(&s->data.nc, "launching passt"); |
| |
| launcher = g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_NONE); |
| g_subprocess_launcher_take_fd(launcher, sock, 3); |
| |
| daemon = g_subprocess_launcher_spawnv(launcher, |
| (const gchar *const *)s->args->pdata, |
| &error); |
| g_object_unref(launcher); |
| |
| if (!daemon) { |
| error_setg(errp, "Error creating daemon: %s", error->message); |
| return -1; |
| } |
| |
| if (!g_subprocess_wait(daemon, NULL, &error)) { |
| error_setg(errp, "Error waiting for daemon: %s", error->message); |
| return -1; |
| } |
| |
| if (g_subprocess_get_if_exited(daemon) && |
| g_subprocess_get_exit_status(daemon)) { |
| return -1; |
| } |
| |
| if (!g_file_get_contents(s->pidfile, &contents, NULL, &error)) { |
| error_setg(errp, "Cannot read passt pid: %s", error->message); |
| return -1; |
| } |
| |
| s->pid = (pid_t)g_ascii_strtoll(contents, NULL, 10); |
| if (s->pid <= 0) { |
| error_setg(errp, "File '%s' did not contain a valid PID.", s->pidfile); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int net_passt_stream_start(NetPasstState *s, Error **errp) |
| { |
| QIOChannelSocket *sioc; |
| SocketAddress *addr; |
| int sv[2]; |
| |
| if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) { |
| error_setg_errno(errp, errno, "socketpair() failed"); |
| return -1; |
| } |
| |
| /* connect to passt */ |
| qemu_set_info_str(&s->data.nc, "connecting to passt"); |
| |
| /* create socket channel */ |
| sioc = qio_channel_socket_new(); |
| s->data.ioc = QIO_CHANNEL(sioc); |
| s->data.nc.link_down = true; |
| s->data.send = net_passt_send; |
| |
| addr = g_new0(SocketAddress, 1); |
| addr->type = SOCKET_ADDRESS_TYPE_FD; |
| addr->u.fd.str = g_strdup_printf("%d", sv[0]); |
| |
| qio_channel_socket_connect_async(sioc, addr, |
| net_passt_client_connected, s, |
| NULL, NULL); |
| |
| qapi_free_SocketAddress(addr); |
| |
| /* start passt */ |
| if (net_passt_start_daemon(s, sv[1], errp) == -1) { |
| close(sv[0]); |
| close(sv[1]); |
| return -1; |
| } |
| close(sv[1]); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_VHOST_USER |
| static gboolean passt_vhost_user_watch(void *do_not_use, GIOCondition cond, |
| void *opaque) |
| { |
| NetPasstState *s = opaque; |
| |
| qemu_chr_fe_disconnect(&s->vhost_chr); |
| |
| return G_SOURCE_CONTINUE; |
| } |
| |
| static void passt_vhost_user_event(void *opaque, QEMUChrEvent event); |
| |
| static void chr_closed_bh(void *opaque) |
| { |
| NetPasstState *s = opaque; |
| |
| passt_save_acked_features(&s->data.nc); |
| |
| net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, false); |
| |
| qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, passt_vhost_user_event, |
| NULL, s, NULL, true); |
| } |
| |
| static void passt_vhost_user_stop(NetPasstState *s) |
| { |
| passt_save_acked_features(&s->data.nc); |
| vhost_net_cleanup(s->vhost_net); |
| } |
| |
| static int passt_vhost_user_start(NetPasstState *s, VhostUserState *be) |
| { |
| struct vhost_net *net = NULL; |
| VhostNetOptions options; |
| |
| options.backend_type = VHOST_BACKEND_TYPE_USER; |
| options.net_backend = &s->data.nc; |
| options.opaque = be; |
| options.busyloop_timeout = 0; |
| options.nvqs = 2; |
| options.feature_bits = user_feature_bits; |
| options.max_tx_queue_size = VIRTQUEUE_MAX_SIZE; |
| options.get_acked_features = passt_get_acked_features; |
| options.save_acked_features = passt_save_acked_features; |
| options.is_vhost_user = true; |
| |
| net = vhost_net_init(&options); |
| if (!net) { |
| error_report("failed to init passt vhost_net"); |
| passt_vhost_user_stop(s); |
| return -1; |
| } |
| |
| if (s->vhost_net) { |
| vhost_net_cleanup(s->vhost_net); |
| g_free(s->vhost_net); |
| } |
| s->vhost_net = net; |
| |
| return 0; |
| } |
| |
| static void passt_vhost_user_event(void *opaque, QEMUChrEvent event) |
| { |
| NetPasstState *s = opaque; |
| |
| switch (event) { |
| case CHR_EVENT_OPENED: |
| if (passt_vhost_user_start(s, s->vhost_user) < 0) { |
| qemu_chr_fe_disconnect(&s->vhost_chr); |
| return; |
| } |
| s->vhost_watch = qemu_chr_fe_add_watch(&s->vhost_chr, G_IO_HUP, |
| passt_vhost_user_watch, s); |
| net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, true); |
| s->started = true; |
| break; |
| case CHR_EVENT_CLOSED: |
| if (s->vhost_watch) { |
| AioContext *ctx = qemu_get_current_aio_context(); |
| |
| g_source_remove(s->vhost_watch); |
| s->vhost_watch = 0; |
| qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, NULL, NULL, |
| NULL, NULL, false); |
| |
| aio_bh_schedule_oneshot(ctx, chr_closed_bh, s); |
| } |
| break; |
| case CHR_EVENT_BREAK: |
| case CHR_EVENT_MUX_IN: |
| case CHR_EVENT_MUX_OUT: |
| /* Ignore */ |
| break; |
| } |
| } |
| |
| static int net_passt_vhost_user_init(NetPasstState *s, Error **errp) |
| { |
| Chardev *chr; |
| int sv[2]; |
| |
| if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) { |
| error_setg_errno(errp, errno, "socketpair() failed"); |
| return -1; |
| } |
| |
| /* connect to passt */ |
| qemu_set_info_str(&s->data.nc, "connecting to passt"); |
| |
| /* create chardev */ |
| |
| chr = CHARDEV(object_new(TYPE_CHARDEV_SOCKET)); |
| if (!chr || qemu_chr_add_client(chr, sv[0]) == -1) { |
| object_unref(OBJECT(chr)); |
| error_setg(errp, "Failed to make socket chardev"); |
| goto err; |
| } |
| |
| s->vhost_user = g_new0(struct VhostUserState, 1); |
| if (!qemu_chr_fe_init(&s->vhost_chr, chr, errp) || |
| !vhost_user_init(s->vhost_user, &s->vhost_chr, errp)) { |
| goto err; |
| } |
| |
| /* start passt */ |
| if (net_passt_start_daemon(s, sv[1], errp) == -1) { |
| goto err; |
| } |
| |
| do { |
| if (qemu_chr_fe_wait_connected(&s->vhost_chr, errp) < 0) { |
| goto err; |
| } |
| |
| qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, |
| passt_vhost_user_event, NULL, s, NULL, |
| true); |
| } while (!s->started); |
| |
| qemu_set_info_str(&s->data.nc, "vhost-user,connected to pid %d", s->pid); |
| |
| close(sv[1]); |
| return 0; |
| err: |
| close(sv[0]); |
| close(sv[1]); |
| |
| return -1; |
| } |
| #else |
| static int net_passt_vhost_user_init(NetPasstState *s, Error **errp) |
| { |
| error_setg(errp, "vhost-user support has not been built"); |
| |
| return -1; |
| } |
| #endif |
| |
| static GPtrArray *net_passt_decode_args(const NetDevPasstOptions *passt, |
| gchar *pidfile, Error **errp) |
| { |
| GPtrArray *args = g_ptr_array_new_with_free_func(g_free); |
| |
| if (passt->path) { |
| g_ptr_array_add(args, g_strdup(passt->path)); |
| } else { |
| g_ptr_array_add(args, g_strdup("passt")); |
| } |
| |
| if (passt->has_vhost_user && passt->vhost_user) { |
| g_ptr_array_add(args, g_strdup("--vhost-user")); |
| } |
| |
| /* by default, be quiet */ |
| if (!passt->has_quiet || passt->quiet) { |
| g_ptr_array_add(args, g_strdup("--quiet")); |
| } |
| |
| if (passt->has_mtu) { |
| g_ptr_array_add(args, g_strdup("--mtu")); |
| g_ptr_array_add(args, g_strdup_printf("%"PRId64, passt->mtu)); |
| } |
| |
| if (passt->address) { |
| g_ptr_array_add(args, g_strdup("--address")); |
| g_ptr_array_add(args, g_strdup(passt->address)); |
| } |
| |
| if (passt->netmask) { |
| g_ptr_array_add(args, g_strdup("--netmask")); |
| g_ptr_array_add(args, g_strdup(passt->netmask)); |
| } |
| |
| if (passt->mac) { |
| g_ptr_array_add(args, g_strdup("--mac-addr")); |
| g_ptr_array_add(args, g_strdup(passt->mac)); |
| } |
| |
| if (passt->gateway) { |
| g_ptr_array_add(args, g_strdup("--gateway")); |
| g_ptr_array_add(args, g_strdup(passt->gateway)); |
| } |
| |
| if (passt->interface) { |
| g_ptr_array_add(args, g_strdup("--interface")); |
| g_ptr_array_add(args, g_strdup(passt->interface)); |
| } |
| |
| if (passt->outbound) { |
| g_ptr_array_add(args, g_strdup("--outbound")); |
| g_ptr_array_add(args, g_strdup(passt->outbound)); |
| } |
| |
| if (passt->outbound_if4) { |
| g_ptr_array_add(args, g_strdup("--outbound-if4")); |
| g_ptr_array_add(args, g_strdup(passt->outbound_if4)); |
| } |
| |
| if (passt->outbound_if6) { |
| g_ptr_array_add(args, g_strdup("--outbound-if6")); |
| g_ptr_array_add(args, g_strdup(passt->outbound_if6)); |
| } |
| |
| if (passt->dns) { |
| g_ptr_array_add(args, g_strdup("--dns")); |
| g_ptr_array_add(args, g_strdup(passt->dns)); |
| } |
| if (passt->fqdn) { |
| g_ptr_array_add(args, g_strdup("--fqdn")); |
| g_ptr_array_add(args, g_strdup(passt->fqdn)); |
| } |
| |
| if (passt->has_dhcp_dns && !passt->dhcp_dns) { |
| g_ptr_array_add(args, g_strdup("--no-dhcp-dns")); |
| } |
| |
| if (passt->has_dhcp_search && !passt->dhcp_search) { |
| g_ptr_array_add(args, g_strdup("--no-dhcp-search")); |
| } |
| |
| if (passt->map_host_loopback) { |
| g_ptr_array_add(args, g_strdup("--map-host-loopback")); |
| g_ptr_array_add(args, g_strdup(passt->map_host_loopback)); |
| } |
| |
| if (passt->map_guest_addr) { |
| g_ptr_array_add(args, g_strdup("--map-guest-addr")); |
| g_ptr_array_add(args, g_strdup(passt->map_guest_addr)); |
| } |
| |
| if (passt->dns_forward) { |
| g_ptr_array_add(args, g_strdup("--dns-forward")); |
| g_ptr_array_add(args, g_strdup(passt->dns_forward)); |
| } |
| |
| if (passt->dns_host) { |
| g_ptr_array_add(args, g_strdup("--dns-host")); |
| g_ptr_array_add(args, g_strdup(passt->dns_host)); |
| } |
| |
| if (passt->has_tcp && !passt->tcp) { |
| g_ptr_array_add(args, g_strdup("--no-tcp")); |
| } |
| |
| if (passt->has_udp && !passt->udp) { |
| g_ptr_array_add(args, g_strdup("--no-udp")); |
| } |
| |
| if (passt->has_icmp && !passt->icmp) { |
| g_ptr_array_add(args, g_strdup("--no-icmp")); |
| } |
| |
| if (passt->has_dhcp && !passt->dhcp) { |
| g_ptr_array_add(args, g_strdup("--no-dhcp")); |
| } |
| |
| if (passt->has_ndp && !passt->ndp) { |
| g_ptr_array_add(args, g_strdup("--no-ndp")); |
| } |
| if (passt->has_dhcpv6 && !passt->dhcpv6) { |
| g_ptr_array_add(args, g_strdup("--no-dhcpv6")); |
| } |
| |
| if (passt->has_ra && !passt->ra) { |
| g_ptr_array_add(args, g_strdup("--no-ra")); |
| } |
| |
| if (passt->has_freebind && passt->freebind) { |
| g_ptr_array_add(args, g_strdup("--freebind")); |
| } |
| |
| if (passt->has_ipv4 && !passt->ipv4) { |
| g_ptr_array_add(args, g_strdup("--ipv6-only")); |
| } |
| |
| if (passt->has_ipv6 && !passt->ipv6) { |
| g_ptr_array_add(args, g_strdup("--ipv4-only")); |
| } |
| |
| if (passt->has_search && passt->search) { |
| const StringList *list = passt->search; |
| GString *domains = g_string_new(list->value->str); |
| |
| list = list->next; |
| while (list) { |
| g_string_append(domains, " "); |
| g_string_append(domains, list->value->str); |
| list = list->next; |
| } |
| |
| g_ptr_array_add(args, g_strdup("--search")); |
| g_ptr_array_add(args, g_string_free(domains, FALSE)); |
| } |
| |
| if (passt->has_tcp_ports && passt->tcp_ports) { |
| const StringList *list = passt->tcp_ports; |
| GString *tcp_ports = g_string_new(list->value->str); |
| |
| list = list->next; |
| while (list) { |
| g_string_append(tcp_ports, ","); |
| g_string_append(tcp_ports, list->value->str); |
| list = list->next; |
| } |
| |
| g_ptr_array_add(args, g_strdup("--tcp-ports")); |
| g_ptr_array_add(args, g_string_free(tcp_ports, FALSE)); |
| } |
| |
| if (passt->has_udp_ports && passt->udp_ports) { |
| const StringList *list = passt->udp_ports; |
| GString *udp_ports = g_string_new(list->value->str); |
| |
| list = list->next; |
| while (list) { |
| g_string_append(udp_ports, ","); |
| g_string_append(udp_ports, list->value->str); |
| list = list->next; |
| } |
| |
| g_ptr_array_add(args, g_strdup("--udp-ports")); |
| g_ptr_array_add(args, g_string_free(udp_ports, FALSE)); |
| } |
| |
| if (passt->has_param && passt->param) { |
| const StringList *list = passt->param; |
| |
| while (list) { |
| g_ptr_array_add(args, g_strdup(list->value->str)); |
| list = list->next; |
| } |
| } |
| |
| /* provide a pid file to be able to kil passt on exit */ |
| g_ptr_array_add(args, g_strdup("--pid")); |
| g_ptr_array_add(args, g_strdup(pidfile)); |
| |
| /* g_subprocess_launcher_take_fd() will set the socket on fd 3 */ |
| g_ptr_array_add(args, g_strdup("--fd")); |
| g_ptr_array_add(args, g_strdup("3")); |
| |
| g_ptr_array_add(args, NULL); |
| |
| return args; |
| } |
| |
| int net_init_passt(const Netdev *netdev, const char *name, |
| NetClientState *peer, Error **errp) |
| { |
| g_autoptr(GError) error = NULL; |
| NetClientState *nc; |
| NetPasstState *s; |
| GPtrArray *args; |
| gchar *pidfile; |
| int pidfd; |
| |
| assert(netdev->type == NET_CLIENT_DRIVER_PASST); |
| |
| pidfd = g_file_open_tmp("passt-XXXXXX.pid", &pidfile, &error); |
| if (pidfd == -1) { |
| error_setg(errp, "Failed to create temporary file: %s", error->message); |
| return -1; |
| } |
| close(pidfd); |
| |
| args = net_passt_decode_args(&netdev->u.passt, pidfile, errp); |
| if (args == NULL) { |
| g_free(pidfile); |
| return -1; |
| } |
| |
| nc = qemu_new_net_client(&net_passt_info, peer, "passt", name); |
| s = DO_UPCAST(NetPasstState, data.nc, nc); |
| |
| s->args = args; |
| s->pidfile = pidfile; |
| |
| if (netdev->u.passt.has_vhost_user && netdev->u.passt.vhost_user) { |
| if (net_passt_vhost_user_init(s, errp) == -1) { |
| qemu_del_net_client(nc); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| if (net_passt_stream_start(s, errp) == -1) { |
| qemu_del_net_client(nc); |
| return -1; |
| } |
| |
| return 0; |
| } |