From c03ba0eab910a887e3c6973dce2f376ddb6923cf Mon Sep 17 00:00:00 2001 From: Andy Fiddaman Date: Mon, 28 Nov 2022 14:51:42 +0000 Subject: Add support for VNICs This work originated in SmartOS as part of https://github.com/TritonDataCenter/illumos-kvm-cmd --- meson.build | 3 +- net/clients.h | 2 + net/hub.c | 1 + net/meson.build | 2 +- net/net.c | 1 + net/vnic.c | 274 ++++++++++++++++++++++++++++++++++++++++++++++++ qapi/net.json | 14 ++- 7 files changed, 294 insertions(+), 3 deletions(-) create mode 100644 net/vnic.c diff --git a/meson.build b/meson.build index 41f68d3806..44a5c542d1 100644 --- a/meson.build +++ b/meson.build @@ -850,7 +850,8 @@ elif host_os == 'darwin' elif host_os == 'sunos' socket = [cc.find_library('socket'), cc.find_library('nsl'), - cc.find_library('resolv')] + cc.find_library('resolv'), + cc.find_library('dlpi')] elif host_os == 'haiku' socket = [cc.find_library('posix_error_mapper'), cc.find_library('network'), diff --git a/net/clients.h b/net/clients.h index be53794582..07e413c3de 100644 --- a/net/clients.h +++ b/net/clients.h @@ -58,6 +58,8 @@ int net_init_l2tpv3(const Netdev *netdev, const char *name, int net_init_vde(const Netdev *netdev, const char *name, NetClientState *peer, Error **errp); #endif +int net_init_vnic(const Netdev *netdev, const char *name, + NetClientState *peer, Error **errp); #ifdef CONFIG_NETMAP int net_init_netmap(const Netdev *netdev, const char *name, diff --git a/net/hub.c b/net/hub.c index cba20ebd87..9bfb3cef95 100644 --- a/net/hub.c +++ b/net/hub.c @@ -291,6 +291,7 @@ void net_hub_check_clients(void) case NET_CLIENT_DRIVER_STREAM: case NET_CLIENT_DRIVER_DGRAM: case NET_CLIENT_DRIVER_VDE: + case NET_CLIENT_DRIVER_VNIC: case NET_CLIENT_DRIVER_VHOST_USER: has_host_dev = 1; break; diff --git a/net/meson.build b/net/meson.build index bb97b4dcbe..a396d91d3e 100644 --- a/net/meson.build +++ b/net/meson.build @@ -52,7 +52,7 @@ elif host_os == 'linux' elif host_os in bsd_oses system_ss.add(files('tap.c', 'tap-bsd.c')) elif host_os == 'sunos' - system_ss.add(files('tap.c', 'tap-solaris.c')) + system_ss.add(files('tap.c', 'tap-solaris.c', 'vnic.c')) else system_ss.add(files('tap.c', 'tap-stub.c')) endif diff --git a/net/net.c b/net/net.c index 39d6f28158..d808c9c8b9 100644 --- a/net/net.c +++ b/net/net.c @@ -1258,6 +1258,7 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])( #ifdef CONFIG_VDE [NET_CLIENT_DRIVER_VDE] = net_init_vde, #endif + [NET_CLIENT_DRIVER_VNIC] = net_init_vnic, #ifdef CONFIG_NETMAP [NET_CLIENT_DRIVER_NETMAP] = net_init_netmap, #endif diff --git a/net/vnic.c b/net/vnic.c new file mode 100644 index 0000000000..481661b583 --- /dev/null +++ b/net/vnic.c @@ -0,0 +1,274 @@ +/* + * QEMU System Emulator Solaris VNIC support + * + * Copyright 2016 Joyent, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "tap_int.h" +#include "qemu/ctype.h" +#include "qemu/cutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "net/net.h" +#include "clients.h" +#include "qemu/option.h" +#include "qemu/main-loop.h" +#include "qemu/error-report.h" + +#define VNIC_BUFSIZE 65536 + +typedef struct VNICState { + NetClientState vns_nc; + int vns_fd; + bool vns_rpoll; + bool vns_wpoll; + uint8_t vns_buf[VNIC_BUFSIZE]; + uint_t vns_sap; + dlpi_handle_t vns_hdl; +} VNICState; + +static void vnic_update_fd_handler(VNICState *); + +static void +vnic_read_poll(VNICState *vsp, bool enable) +{ + vsp->vns_rpoll = enable; + vnic_update_fd_handler(vsp); +} + +static void +vnic_write_poll(VNICState *vsp, bool enable) +{ + vsp->vns_wpoll = enable; + vnic_update_fd_handler(vsp); +} + +static void +vnic_poll(NetClientState *ncp, bool enable) +{ + VNICState *vsp = DO_UPCAST(VNICState, vns_nc, ncp); + vnic_read_poll(vsp, true); + vnic_write_poll(vsp, true); +} + +static int +vnic_read_packet(VNICState *vsp, uint8_t *buf, int len) +{ + struct strbuf sbuf; + int flags, ret; + + flags = 0; + sbuf.maxlen = len; + sbuf.buf = (char *)buf; + + do { + ret = getmsg(vsp->vns_fd, NULL, &sbuf, &flags); + } while (ret == -1 && errno == EINTR); + + if (ret == -1 && errno == EAGAIN) { + vnic_write_poll(vsp, true); + return (0); + } + + if (ret == -1) + return (-1); + + return (sbuf.len); +} + +static int +vnic_write_packet(VNICState *vsp, const uint8_t *buf, int len) +{ + struct strbuf sbuf; + int flags, ret; + + flags = 0; + sbuf.len = len; + sbuf.buf = (char *)buf; + + do { + ret = putmsg(vsp->vns_fd, NULL, &sbuf, flags); + } while (ret == -1 && errno == EINTR); + + if (ret == -1 && errno == EAGAIN) { + vnic_write_poll(vsp, true); + return (0); + } + + if (ret == -1) + return (-1); + + return (len); +} + +static void +vnic_send_completed(NetClientState *nc, ssize_t len) +{ + VNICState *vsp = DO_UPCAST(VNICState, vns_nc, nc); + + vnic_read_poll(vsp, true); +} + +/* outside world -> VM */ +static void +vnic_send(void *opaque) +{ + VNICState *vsp = opaque; + int ret; + + do { + ret = vnic_read_packet(vsp, vsp->vns_buf, + sizeof (vsp->vns_buf)); + if (ret <= 0) + break; + + ret = qemu_send_packet_async(&vsp->vns_nc, vsp->vns_buf, ret, + vnic_send_completed); + + if (ret == 0) + vnic_read_poll(vsp, false); + + } while (ret > 0 && qemu_can_send_packet(&vsp->vns_nc)); +} + +static void +vnic_writable(void *opaque) +{ + VNICState *vsp = opaque; + vnic_write_poll(vsp, false); + qemu_flush_queued_packets(&vsp->vns_nc); +} + +/* VM -> outside world */ +static ssize_t +vnic_receive(NetClientState *ncp, const uint8_t *buf, size_t size) +{ + VNICState *vsp = DO_UPCAST(VNICState, vns_nc, ncp); + + return (vnic_write_packet(vsp, buf, size)); +} + + +static void +vnic_cleanup(NetClientState *ncp) +{ + VNICState *vsp = DO_UPCAST(VNICState, vns_nc, ncp); + + qemu_purge_queued_packets(ncp); + + dlpi_close(vsp->vns_hdl); +} + +static void +vnic_update_fd_handler(VNICState *vsp) +{ + qemu_set_fd_handler(vsp->vns_fd, + vsp->vns_rpoll ? vnic_send : NULL, + vsp->vns_wpoll ? vnic_writable : NULL, + vsp); +} + +static NetClientInfo net_vnic_info = { + .type = NET_CLIENT_DRIVER_VNIC, + .size = sizeof(VNICState), + .receive = vnic_receive, + .poll = vnic_poll, + .cleanup = vnic_cleanup, +}; + +int net_init_vnic(const Netdev *netdev, const char *name, + NetClientState *peer, Error **errp) +{ + const NetdevVNICOptions *vnic; + NetClientState *ncp; + VNICState *vsp; + int fd, ret; + + assert(netdev->type == NET_CLIENT_DRIVER_VNIC); + vnic = &netdev->u.vnic; + + ncp = qemu_new_net_client(&net_vnic_info, peer, "vnic", name); + vsp = DO_UPCAST(VNICState, vns_nc, ncp); + + ret = dlpi_open(vnic->ifname, &vsp->vns_hdl, DLPI_RAW); + if (ret != DLPI_SUCCESS) { + error_report("vnic: failed to open interface %s, err %d", + vnic->ifname, ret); + return (-1); + } + + ret = dlpi_bind(vsp->vns_hdl, DLPI_ANY_SAP, &vsp->vns_sap); + if (ret != DLPI_SUCCESS) { + error_report("vnic: failed to bind interface %s, err %d", + vnic->ifname, ret); + return (-1); + } + + /* + * We are enabling support for two different kinds of promiscuous modes. + * The first is getting us the basics of the unicast traffic that we + * care about. The latter is going to ensure that we also get other + * types of physical traffic such as multicast and broadcast. + */ + ret = dlpi_promiscon(vsp->vns_hdl, DL_PROMISC_SAP); + if (ret != DLPI_SUCCESS) { + error_report( + "vnic: failed to be promiscous with interface %s, err %d", + vnic->ifname, ret); + return (-1); + } + + ret = dlpi_promiscon(vsp->vns_hdl, DL_PROMISC_PHYS); + if (ret != DLPI_SUCCESS) { + error_report( + "vnic: failed to be promiscous with interface %s, err %d", + vnic-> ifname, ret); + return (-1); + } + + fd = dlpi_fd(vsp->vns_hdl); + + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { + error_report("vnic: failed to set fd on interface %s to " + "non-blocking: %s", vnic->ifname, strerror(errno)); + return (-1); + } + + vsp->vns_fd = fd; + + snprintf(vsp->vns_nc.info_str, sizeof (vsp->vns_nc.info_str), + "ifname=%s", vnic->ifname); + + /* We have to manually intialize the polling for read */ + vnic_read_poll(vsp, true); + + return (0); +} diff --git a/qapi/net.json b/qapi/net.json index 310cc4fd19..e779194065 100644 --- a/qapi/net.json +++ b/qapi/net.json @@ -364,6 +364,17 @@ '*group': 'str', '*mode': 'uint16' } } +## +# @NetdevVNICOptions: +# +# Connect to a VNIC on the host. +# +# @ifname: VNIC interface name +## +{ 'struct': 'NetdevVNICOptions', + 'data': { + 'ifname': 'str' } } + ## # @NetdevBridgeOptions: # @@ -734,7 +745,7 @@ { 'enum': 'NetClientDriver', 'data': [ 'none', 'nic', 'user', 'tap', 'l2tpv3', 'socket', 'stream', 'dgram', 'vde', 'bridge', 'hubport', 'netmap', 'vhost-user', - 'vhost-vdpa', + 'vhost-vdpa', 'vnic', { 'name': 'passt', 'if': 'CONFIG_PASST' }, { 'name': 'af-xdp', 'if': 'CONFIG_AF_XDP' }, { 'name': 'vmnet-host', 'if': 'CONFIG_VMNET' }, @@ -763,6 +774,7 @@ 'stream': 'NetdevStreamOptions', 'dgram': 'NetdevDgramOptions', 'vde': 'NetdevVdeOptions', + 'vnic': 'NetdevVNICOptions', 'bridge': 'NetdevBridgeOptions', 'hubport': 'NetdevHubPortOptions', 'netmap': 'NetdevNetmapOptions',