diff --git a/configure b/configure index 27d84d9fdb..d23ded555d 100755 --- a/configure +++ b/configure @@ -4515,6 +4515,9 @@ fi if test "$vhost_scsi" = "yes" ; then echo "CONFIG_VHOST_SCSI=y" >> $config_host_mak fi +if test "$vhost_net" = "yes" ; then + echo "CONFIG_VHOST_NET_USED=y" >> $config_host_mak +fi if test "$blobs" = "yes" ; then echo "INSTALL_BLOBS=yes" >> $config_host_mak fi diff --git a/hmp-commands.hx b/hmp-commands.hx index 5f1a677b85..d0943b1ff3 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -1211,7 +1211,7 @@ ETEXI { .name = "host_net_add", .args_type = "device:s,opts:s?", - .params = "tap|user|socket|vde|netmap|bridge|dump [options]", + .params = "tap|user|socket|vde|netmap|bridge|vhost-user|dump [options]", .help = "add host VLAN client", .mhandler.cmd = net_host_device_add, .command_completion = host_net_add_completion, @@ -1241,7 +1241,7 @@ ETEXI { .name = "netdev_add", .args_type = "netdev:O", - .params = "[user|tap|socket|vde|bridge|hubport|netmap],id=str[,prop=value][,...]", + .params = "[user|tap|socket|vde|bridge|hubport|netmap|vhost-user],id=str[,prop=value][,...]", .help = "add host network device", .mhandler.cmd = hmp_netdev_add, .command_completion = netdev_add_completion, diff --git a/hw/net/vhost_net.c b/hw/net/vhost_net.c index 5f06736a8c..7ac7c21bdb 100644 --- a/hw/net/vhost_net.c +++ b/hw/net/vhost_net.c @@ -15,6 +15,7 @@ #include "net/net.h" #include "net/tap.h" +#include "net/vhost-user.h" #include "hw/virtio/virtio-net.h" #include "net/vhost_net.h" @@ -360,6 +361,9 @@ VHostNetState *get_vhost_net(NetClientState *nc) case NET_CLIENT_OPTIONS_KIND_TAP: vhost_net = tap_get_vhost_net(nc); break; + case NET_CLIENT_OPTIONS_KIND_VHOST_USER: + vhost_net = vhost_user_get_vhost_net(nc); + break; default: break; } diff --git a/net/hub.c b/net/hub.c index 33a99c99ef..7e0f2d6f0d 100644 --- a/net/hub.c +++ b/net/hub.c @@ -322,6 +322,7 @@ void net_hub_check_clients(void) case NET_CLIENT_OPTIONS_KIND_TAP: case NET_CLIENT_OPTIONS_KIND_SOCKET: case NET_CLIENT_OPTIONS_KIND_VDE: + case NET_CLIENT_OPTIONS_KIND_VHOST_USER: has_host_dev = 1; break; default: diff --git a/net/net.c b/net/net.c index 6344160403..3dac29b844 100644 --- a/net/net.c +++ b/net/net.c @@ -62,6 +62,7 @@ const char *host_net_devices[] = { #ifdef CONFIG_VDE "vde", #endif + "vhost-user", NULL, }; @@ -802,6 +803,9 @@ static int (* const net_client_init_fun[NET_CLIENT_OPTIONS_KIND_MAX])( [NET_CLIENT_OPTIONS_KIND_BRIDGE] = net_init_bridge, #endif [NET_CLIENT_OPTIONS_KIND_HUBPORT] = net_init_hubport, +#ifdef CONFIG_VHOST_NET_USED + [NET_CLIENT_OPTIONS_KIND_VHOST_USER] = net_init_vhost_user, +#endif }; @@ -835,6 +839,9 @@ static int net_client_init1(const void *object, int is_netdev, Error **errp) case NET_CLIENT_OPTIONS_KIND_BRIDGE: #endif case NET_CLIENT_OPTIONS_KIND_HUBPORT: +#ifdef CONFIG_VHOST_NET_USED + case NET_CLIENT_OPTIONS_KIND_VHOST_USER: +#endif break; default: diff --git a/net/vhost-user.c b/net/vhost-user.c index 4bdd19d8e6..24e050c772 100644 --- a/net/vhost-user.c +++ b/net/vhost-user.c @@ -12,6 +12,7 @@ #include "net/vhost_net.h" #include "net/vhost-user.h" #include "sysemu/char.h" +#include "qemu/config-file.h" #include "qemu/error-report.h" typedef struct VhostUserState { @@ -21,9 +22,16 @@ typedef struct VhostUserState { VHostNetState *vhost_net; } VhostUserState; +typedef struct VhostUserChardevProps { + bool is_socket; + bool is_unix; + bool is_server; +} VhostUserChardevProps; + VHostNetState *vhost_user_get_vhost_net(NetClientState *nc) { VhostUserState *s = DO_UPCAST(VhostUserState, nc, nc); + assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER); return s->vhost_net; } @@ -82,7 +90,7 @@ static bool vhost_user_has_ufo(NetClientState *nc) } static NetClientInfo net_vhost_user_info = { - .type = 0, + .type = NET_CLIENT_OPTIONS_KIND_VHOST_USER, .size = sizeof(VhostUserState), .cleanup = vhost_user_cleanup, .has_vnet_hdr = vhost_user_has_vnet_hdr, @@ -148,8 +156,103 @@ static int net_vhost_user_init(NetClientState *peer, const char *device, return 0; } -int net_init_vhost_user(const NetClientOptions *opts, const char *name, - NetClientState *peer) +static int net_vhost_chardev_opts(const char *name, const char *value, + void *opaque) { - return net_vhost_user_init(peer, "vhost_user", 0, 0, 0); + VhostUserChardevProps *props = opaque; + + if (strcmp(name, "backend") == 0 && strcmp(value, "socket") == 0) { + props->is_socket = true; + } else if (strcmp(name, "path") == 0) { + props->is_unix = true; + } else if (strcmp(name, "server") == 0) { + props->is_server = true; + } else { + error_report("vhost-user does not support a chardev" + " with the following option:\n %s = %s", + name, value); + return -1; + } + return 0; +} + +static CharDriverState *net_vhost_parse_chardev(const NetdevVhostUserOptions *opts) +{ + CharDriverState *chr = qemu_chr_find(opts->chardev); + VhostUserChardevProps props; + + if (chr == NULL) { + error_report("chardev \"%s\" not found", opts->chardev); + return NULL; + } + + /* inspect chardev opts */ + memset(&props, 0, sizeof(props)); + if (qemu_opt_foreach(chr->opts, net_vhost_chardev_opts, &props, true) != 0) { + return NULL; + } + + if (!props.is_socket || !props.is_unix) { + error_report("chardev \"%s\" is not a unix socket", + opts->chardev); + return NULL; + } + + qemu_chr_fe_claim_no_fail(chr); + + return chr; +} + +static int net_vhost_check_net(QemuOpts *opts, void *opaque) +{ + const char *name = opaque; + const char *driver, *netdev; + const char virtio_name[] = "virtio-net-"; + + driver = qemu_opt_get(opts, "driver"); + netdev = qemu_opt_get(opts, "netdev"); + + if (!driver || !netdev) { + return 0; + } + + if (strcmp(netdev, name) == 0 && + strncmp(driver, virtio_name, strlen(virtio_name)) != 0) { + error_report("vhost-user requires frontend driver virtio-net-*"); + return -1; + } + + return 0; +} + +int net_init_vhost_user(const NetClientOptions *opts, const char *name, + NetClientState *peer) +{ + const NetdevVhostUserOptions *vhost_user_opts; + CharDriverState *chr; + bool vhostforce; + + assert(opts->kind == NET_CLIENT_OPTIONS_KIND_VHOST_USER); + vhost_user_opts = opts->vhost_user; + + chr = net_vhost_parse_chardev(vhost_user_opts); + if (!chr) { + error_report("No suitable chardev found"); + return -1; + } + + /* verify net frontend */ + if (qemu_opts_foreach(qemu_find_opts("device"), net_vhost_check_net, + (char *)name, true) == -1) { + return -1; + } + + /* vhostforce for non-MSIX */ + if (vhost_user_opts->has_vhostforce) { + vhostforce = vhost_user_opts->vhostforce; + } else { + vhostforce = false; + } + + return net_vhost_user_init(peer, "vhost_user", name, chr, vhostforce); } diff --git a/qapi-schema.json b/qapi-schema.json index dc2abe479e..f5d89b024c 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -2068,6 +2068,22 @@ 'ifname': 'str', '*devname': 'str' } } +## +# @NetdevVhostUserOptions +# +# Vhost-user network backend +# +# @chardev: name of a unix socket chardev +# +# @vhostforce: #optional vhost on for non-MSIX virtio guests (default: false). +# +# Since 2.1 +## +{ 'type': 'NetdevVhostUserOptions', + 'data': { + 'chardev': 'str', + '*vhostforce': 'bool' } } + ## # @NetClientOptions # @@ -2086,7 +2102,8 @@ 'dump': 'NetdevDumpOptions', 'bridge': 'NetdevBridgeOptions', 'hubport': 'NetdevHubPortOptions', - 'netmap': 'NetdevNetmapOptions' } } + 'netmap': 'NetdevNetmapOptions', + 'vhost-user': 'NetdevVhostUserOptions' } } ## # @NetLegacy diff --git a/qemu-options.hx b/qemu-options.hx index 5a4eff9aa4..ab06df1e01 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1460,6 +1460,7 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev, #ifdef CONFIG_NETMAP "netmap|" #endif + "vhost-user|" "socket|" "hubport],id=str[,option][,option][,...]\n", QEMU_ARCH_ALL) STEXI @@ -1791,6 +1792,23 @@ The hubport netdev lets you connect a NIC to a QEMU "vlan" instead of a single netdev. @code{-net} and @code{-device} with parameter @option{vlan} create the required hub automatically. +@item -netdev vhost-user,chardev=@var{id}[,vhostforce=on|off] + +Establish a vhost-user netdev, backed by a chardev @var{id}. The chardev should +be a unix domain socket backed one. The vhost-user uses a specifically defined +protocol to pass vhost ioctl replacement messages to an application on the other +end of the socket. On non-MSIX guests, the feature can be forced with +@var{vhostforce}. + +Example: +@example +qemu -m 512 -object memory-backend-file,id=mem,size=512M,mem-path=/hugetlbfs,share=on \ + -numa node,memdev=mem \ + -chardev socket,path=/path/to/socket \ + -netdev type=vhost-user,id=net0,chardev=chr0 \ + -device virtio-net-pci,netdev=net0 +@end example + @item -net dump[,vlan=@var{n}][,file=@var{file}][,len=@var{len}] Dump network traffic on VLAN @var{n} to file @var{file} (@file{qemu-vlan0.pcap} by default). At most @var{len} bytes (64k by default) per packet are stored. The file format is