From c03ba0eab910a887e3c6973dce2f376ddb6923cf Mon Sep 17 00:00:00 2001
From: Andy Fiddaman <illumos@fiddaman.net>
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 <fcntl.h>
+#include <libdlpi.h>
+#include <stdbool.h>
+#include <sys/dlpi.h>
+#include <sys/ethernet.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <stropts.h>
+
+#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',
