PSARC/2015/311 Open vSwitch (OVS)
authorMark Haywood <Mark.Haywood@Oracle.COM>
Mon, 16 Nov 2015 16:49:19 -0500
changeset 5090 5f131162e136
parent 5089 8d5767cc3ddc
child 5091 81e5d6b75c3c
PSARC/2015/311 Open vSwitch (OVS) 21653217 Integrate OpenvSwitch into Userland
components/openvswitch/Makefile
components/openvswitch/files/include/solaris/automake.mk
components/openvswitch/files/include/solaris/solarisdefs.h
components/openvswitch/files/lib/dpif-solaris.c
components/openvswitch/files/lib/dpif-solaris.h
components/openvswitch/files/lib/netdev-solaris.c
components/openvswitch/files/lib/netdev-solaris.h
components/openvswitch/files/lib/route-table-solaris.c
components/openvswitch/files/lib/util-solaris.c
components/openvswitch/files/lib/util-solaris.h
components/openvswitch/files/ovs-clean.py
components/openvswitch/files/ovs-svc
components/openvswitch/files/ovs.auth_attr
components/openvswitch/files/ovs.exec_attr
components/openvswitch/files/ovs.prof_attr
components/openvswitch/files/ovs.user_attr
components/openvswitch/files/ovsdb.xml
components/openvswitch/files/vswitch.xml
components/openvswitch/openvswitch.license
components/openvswitch/openvswitch.p5m
components/openvswitch/patches/01-solaris-port.patch
components/openvswitch/patches/02-bridge.patch
components/openvswitch/patches/03-dpif-provider.patch
components/openvswitch/patches/04-netdev-provider.patch
components/openvswitch/patches/05-usage.patch
components/openvswitch/patches/06-controller-fix.patch
components/openvswitch/patches/07-ovsthread_key_destruct-fix.patch
components/openvswitch/patches/08-self-test-fix.patch
components/openvswitch/test/results-64.master
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/Makefile	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,105 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+
+#
+# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+#
+
+include ../../make-rules/shared-macros.mk
+
+COMPONENT_NAME=		openvswitch
+COMPONENT_VERSION=	2.3.1
+COMPONENT_SRC=		$(COMPONENT_NAME)-$(COMPONENT_VERSION)
+COMPONENT_ARCHIVE=	$(COMPONENT_SRC).tar.gz
+COMPONENT_ARCHIVE_HASH=	\
+    sha256:d6d96e45fd9c070cc2696a4a09b4cc4b48dd7fc367c0455725d00f7daa343bf0
+COMPONENT_PROJECT_URL=  http://openvswitch.org/
+COMPONENT_ARCHIVE_URL=  $(COMPONENT_PROJECT_URL)/releases/$(COMPONENT_ARCHIVE)
+COMPONENT_BUGDB=	service/openvswitch
+IPS_COMPONENT_VERSION=	$(COMPONENT_VERSION) 
+
+TPNO=			21407
+
+include ../../make-rules/prep.mk
+include ../../make-rules/configure.mk
+include ../../make-rules/ips.mk
+include ../../make-rules/lint-libraries.mk
+
+PKG_PROTO_DIRS += $(COMPONENT_DIR)/files
+PKG_PROTO_DIRS += $(BUILD_DIR_64)
+
+CONFIGURE_ENV += CFLAGS="$(CFLAGS)"
+
+COMPONENT_PRE_CONFIGURE_ACTION += \
+    ($(LN) -fs $(COMPONENT_DIR)/files/include/solaris $(SOURCE_DIR)/include/solaris; \
+    $(LN) -fs $(COMPONENT_DIR)/files/lib/* $(SOURCE_DIR)/lib; \
+    cd $(SOURCE_DIR); $(SOURCE_DIR)/boot.sh)
+
+COMPILER =	gcc
+
+CONFIGURE_PREFIX	= /usr/lib/ovs
+
+CONFIGURE_OPTIONS	+= --mandir=/usr/share/man
+CONFIGURE_OPTIONS	+= --localstatedir=/var/run/ovs
+CONFIGURE_OPTIONS	+= --bindir=/usr/sbin
+CONFIGURE_OPTIONS	+= --sbindir=/usr/lib/ovs
+CONFIGURE_OPTIONS	+= --with-logdir=/var/log/ovs
+CONFIGURE_OPTIONS	+= --with-rundir=/var/run/ovs/
+CONFIGURE_OPTIONS	+= --with-dbdir=/var/lib/ovs/etc
+CONFIGURE_OPTIONS	+= --datarootdir=/usr/lib/ovs/share
+
+# Enable aslr for this component
+ASLR_MODE = $(ASLR_ENABLE)
+
+COMPONENT_PRE_BUILD_ACTION = \
+    ($(LN) -fs $(COMPONENT_DIR)/files/include/solaris $(SOURCE_DIR)/include/solaris)
+
+COMPONENT_TEST_TRANSFORMS += \
+    '-e "s|^tests/pki/test-req.pem.*||g" ' \
+    '-e "s|^tests/pki/test2-req.pem.*||g" ' \
+    '-e "s|^.*fingerprint.*||g" '
+
+COMPONENT_TEST_ENV += PATH="$(GNUBIN):$(PATH)"
+
+# common targets
+configure:	$(CONFIGURE_64)
+
+build:		$(BUILD_64)
+
+install:	$(INSTALL_64)
+
+test:		$(TEST_64)
+
+system-test:	$(SYSTEM_TESTS_NOT_IMPLEMENTED)
+
+BUILD_PKG_DEPENDENCIES =	$(BUILD_TOOLS)
+
+REQUIRED_PACKAGES += library/security/openssl
+REQUIRED_PACKAGES += runtime/python-27
+REQUIRED_PACKAGES += shell/ksh93
+REQUIRED_PACKAGES += system/core-os
+REQUIRED_PACKAGES += system/library
+REQUIRED_PACKAGES += system/library/math
+REQUIRED_PACKAGES += system/management/rad
+REQUIRED_PACKAGES += system/management/rad/client/rad-c
+REQUIRED_PACKAGES += system/management/rad/client/rad-python
+REQUIRED_PACKAGES += system/network
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/include/solaris/automake.mk	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,28 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+
+#
+# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+#
+
+noinst_HEADERS += \
+	include/solaris/solarisdefs.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/include/solaris/solarisdefs.h	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,40 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#ifndef	SOLARISDEFS_H
+#define	SOLARISDEFS_H
+
+#include <sys/types.h>
+
+#define	u_int8_t uint8_t
+#define	u_int16_t uint16_t
+#define	u_int32_t uint32_t
+#define	u_int64_t uint64_t
+
+struct ip6_ext {
+	uint8_t  ip6e_nxt;
+	uint8_t  ip6e_len;
+};
+
+#endif /* SOLARISDEFS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/lib/dpif-solaris.c	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,2465 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ */
+
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "dpif-solaris.h"
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include "classifier.h"
+#include "dpif-provider.h"
+#include "dynamic-string.h"
+#include "netdev-solaris.h"
+#include "netdev-vport.h"
+#include "netlink.h"
+#include "odp-execute.h"
+#include "poll-loop.h"
+#include "shash.h"
+#include "socket-util.h"
+#include "sset.h"
+#include "vlog.h"
+#include <netpacket/packet.h>
+#include <zone.h>
+#include <libdllink.h>
+
+VLOG_DEFINE_THIS_MODULE(dpif_solaris);
+
+static kstat2_handle_t	dpif_khandle;
+static boolean_t	kstat2_handle_initialized = B_FALSE;
+static struct ovs_mutex	kstat_mutex = OVS_MUTEX_INITIALIZER;
+
+/* Datapath interface for the openvswitch Solaris kernel module. */
+struct dpif_solaris {
+	struct dpif dpif;
+	dladm_handle_t dh;
+	int dp_ifindex;	/* datapath id */
+	const struct dpif_class *class;
+	char *name;
+	atomic_flag destroyed;
+	struct ovs_refcount ref_cnt;
+
+	/* BRIDGES */
+	struct ovs_rwlock bridge_rwlock;
+	struct hmap bridges;
+
+	/* PORT */
+	struct ovs_rwlock port_rwlock;
+	struct hmap ports;
+
+	/* FLOW */
+	struct ovs_rwlock flow_rwlock;
+	struct hmap flows OVS_GUARDED;
+	struct classifier cls;		/* rule, flow & mask */
+
+	/* Upcall messages. */
+	struct ovs_rwlock upcall_lock;
+	bool recv_set;
+	int event_rfd;
+	int event_wfd;
+};
+
+static struct ovs_mutex dp_solaris_mutex = OVS_MUTEX_INITIALIZER;
+
+/* Contains all 'struct dpif_solaris's. */
+static struct shash dp_all_solaris OVS_GUARDED_BY(dp_solaris_mutex) =
+    SHASH_INITIALIZER(&dp_all_solaris);
+
+static struct vlog_rate_limit error_rl = VLOG_RATE_LIMIT_INIT(9999, 5);
+
+struct dpif_solaris_bridge {
+	char *physname;
+	struct hmap_node node;	/* Node in dpif_solaris's 'bridges'. */
+	struct hmap ports;
+	struct dpif_solaris_port *uplink_port;
+};
+
+struct dpif_solaris_port {
+	struct hmap_node node;		/* Node in dpif_solaris's 'ports'. */
+	struct hmap_node brnode;	/* Node in dpif_bridge's 'ports'. */
+	odp_port_t port_no;		/* OF port no of this port */
+	odp_port_t pf_port_no;		/* OF port no of the PF_PACKET socket */
+	char *type;			/* Port type as requested by user. */
+	char *name;
+	char *linkname;
+	enum ovs_vport_type vtype;
+	struct netdev *netdev;
+	struct dpif_solaris_bridge *bridge;
+	boolean_t is_uplink;
+
+	/* Receive the upcalls */
+	int upcall_fd;			/* PF_PACKET fd for MISS event */
+
+	/* Send the packet */
+	int xfd;			/* PF_PACKET to execute output action */
+};
+
+struct dpif_solaris_flow {
+	/* Packet classification. */
+	struct cls_rule cr;	/* Node in dpif_solaris's 'cls'. */
+
+	/* Hash table index by unmasked flow. */
+	struct hmap_node node;	/* Node in dpif_solaris's 'flows'. */
+	struct flow flow;	/* The flow that created this entry. */
+	char *physname;
+	char flowname[MAXUSERFLOWNAMELEN];
+};
+
+static void dpif_solaris_port_del__(struct dpif_solaris *dpif,
+    struct dpif_solaris_port *port)
+    OVS_REQ_WRLOCK(dpif->port_rwlock);
+static void dpif_solaris_destroy_channels(struct dpif_solaris *dpif)
+    OVS_REQ_WRLOCK(dpif->upcall_lock);
+static int dpif_solaris_refresh_port_channel(struct dpif_solaris *dpif,
+    struct dpif_solaris_port *port, const char *physname, boolean_t notify)
+    OVS_REQ_WRLOCK(dpif->port_rwlock);
+static int dpif_solaris_get_port_by_number(struct dpif_solaris *dpif,
+    odp_port_t port_no, struct dpif_solaris_port **portp)
+    OVS_REQ_RDLOCK(dpif->port_rwlock);
+static int dpif_solaris_get_uplink_port(struct dpif_solaris *dpif,
+    struct dpif_solaris_port **portp)
+    OVS_REQ_RDLOCK(dpif->port_rwlock);
+static void dpif_solaris_flow_remove(struct dpif_solaris *dpif,
+    struct dpif_solaris_flow *flow)
+    OVS_REQ_WRLOCK(dpif->flow_rwlock);
+static int dpif_solaris_flow_flush__(struct dpif_solaris *dpif);
+
+static struct dpif_solaris *
+dpif_solaris_cast(const struct dpif *dpif)
+{
+	dpif_assert_class(dpif, &dpif_solaris_class);
+	return (CONTAINER_OF(dpif, struct dpif_solaris, dpif));
+}
+
+static int
+dpif_solaris_enumerate(struct sset *all_dps)
+{
+	int error;
+
+	VLOG_DBG("dpif_solaris_enumerate");
+
+	error = solaris_init_rad();
+	if (error != 0)
+		return (error);
+
+	if (netdev_impl_etherstub_exists()) {
+		VLOG_DBG("dpif_solaris_enumerate ovs-system");
+		sset_add(all_dps, "ovs-system");
+	}
+	return (0);
+}
+
+static int
+dpif_solaris_open(const struct dpif_class *class, const char *name,
+    bool create, struct dpif **dpifp)
+{
+	struct dpif_solaris *dpif;
+	int error = 0;
+	boolean_t dp_exists;
+	dladm_status_t status;
+
+	VLOG_DBG("dpif_solaris_open class type %s name %s do %screate",
+	    class->type, name, create ? "" : "not ");
+
+	if (strcmp(name, "ovs-system") != 0)
+		return (ENODEV);
+
+	error = solaris_init_rad();
+	if (error != 0)
+		return (error);
+
+	ovs_mutex_lock(&dp_solaris_mutex);
+
+	dp_exists = netdev_impl_etherstub_exists();
+	dpif = shash_find_data(&dp_all_solaris, name);
+
+	/*
+	 * The same function is shared by ovs-vswitchd and other utilities as
+	 * they call into the same library. Note that for other utilities,
+	 * dpif does not exists at first, create it here if dp_exists indicate
+	 * the datapath already exists.
+	 */
+again:
+	if ((dp_exists && dpif == NULL) || (!dp_exists && create)) {
+		dladm_handle_t dh = NULL;
+
+		status = dladm_open(&dh);
+		error = solaris_dladm_status2error(status);
+		if (error != 0)
+			return (error);
+
+		if (!dp_exists) {
+			error = netdev_create_impl_etherstub();
+			if (error != 0) {
+				dladm_close(dh);
+				return (error);
+			}
+		}
+
+		dpif = xzalloc(sizeof (*dpif));
+		dpif->dh = dh;
+		dpif->dp_ifindex = getzoneid();
+		dpif->class = class;
+		dpif->name = xstrdup(name);
+		atomic_flag_clear(&dpif->destroyed);
+
+		/* UPCALL related */
+		ovs_rwlock_init(&dpif->upcall_lock);
+		dpif->recv_set = false;
+
+		/* uplink related */
+		ovs_rwlock_init(&dpif->bridge_rwlock);
+		hmap_init(&dpif->bridges);
+
+		/* port related */
+		ovs_rwlock_init(&dpif->port_rwlock);
+		hmap_init(&dpif->ports);
+		dpif->event_rfd = dpif->event_wfd = -1;
+
+		/* flow related */
+		ovs_rwlock_init(&dpif->flow_rwlock);
+		classifier_init(&dpif->cls, NULL);
+		hmap_init(&dpif->flows);
+
+		ovs_refcount_init(&dpif->ref_cnt);
+		shash_add(&dp_all_solaris, name, dpif);
+		if (dp_exists) {
+			ovs_refcount_ref(&dpif->ref_cnt);
+			goto again;
+		}
+	} else if (!dp_exists) {
+		error = ENODEV;
+	} else {
+		error = (dpif->class != class ? EOPNOTSUPP :
+		    create ? EEXIST : 0);
+	}
+
+	if (!error) {
+		ovs_refcount_ref(&dpif->ref_cnt);
+
+		/* Choose dp_ifindex to be used as netflow engine type and id */
+		dpif_init(&dpif->dpif, class, name, dpif->dp_ifindex,
+		    dpif->dp_ifindex);
+		*dpifp = &dpif->dpif;
+	}
+	ovs_mutex_unlock(&dp_solaris_mutex);
+	return (error);
+}
+
+/*
+ * Requires dp_netdev_mutex so that we can't get a new reference to 'dp'
+ * through the 'dp_netdevs' shash while freeing 'dp'.
+ */
+static void
+dp_solaris_free(struct dpif_solaris *dpif)
+    OVS_REQUIRES(dp_solaris_mutex)
+{
+	struct dpif_solaris_port *port, *next;
+
+	VLOG_DBG("dp_solaris_free %s", dpif->name);
+
+	shash_find_and_delete(&dp_all_solaris, dpif->name);
+	ovs_rwlock_wrlock(&dpif->port_rwlock);
+	HMAP_FOR_EACH_SAFE(port, next, node, &dpif->ports) {
+		dpif_solaris_port_del__(dpif, port);
+	}
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+
+	dpif_solaris_flow_flush__(dpif);
+	classifier_destroy(&dpif->cls);
+
+	hmap_destroy(&dpif->bridges);
+	ovs_rwlock_destroy(&dpif->bridge_rwlock);
+
+	hmap_destroy(&dpif->ports);
+	ovs_rwlock_destroy(&dpif->port_rwlock);
+
+	hmap_destroy(&dpif->flows);
+	ovs_rwlock_destroy(&dpif->flow_rwlock);
+
+	(void) close(dpif->event_rfd);
+	(void) close(dpif->event_wfd);
+
+	free(dpif->name);
+	dladm_close(dpif->dh);
+	netdev_delete_impl_etherstub();
+	ovs_rwlock_destroy(&dpif->upcall_lock);
+	free(dpif);
+}
+
+static void
+dp_solaris_unref(struct dpif_solaris *dpif)
+{
+	if (dpif != NULL) {
+		/*
+		 * Take dp_solaris_mutex so that, if dpif->ref_cnt falls to
+		 * zero, we can't get a hold of 'dpif'.
+		 */
+		ovs_mutex_lock(&dp_solaris_mutex);
+
+		if (ovs_refcount_unref(&dpif->ref_cnt) == 2) {
+			dp_solaris_free(dpif);
+		}
+		ovs_mutex_unlock(&dp_solaris_mutex);
+	}
+}
+
+static void
+dpif_solaris_close(struct dpif *dpif_)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+
+	VLOG_DBG("dpif_solaris_close %s", dpif->name);
+
+	ovs_rwlock_wrlock(&dpif->upcall_lock);
+	dpif_solaris_destroy_channels(dpif);
+	ovs_rwlock_unlock(&dpif->upcall_lock);
+
+	dp_solaris_unref(dpif);
+}
+
+static int
+dpif_solaris_destroy(struct dpif *dpif_)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	int cnt;
+
+	VLOG_DBG("dpif_solaris_destroy %s", dpif->name);
+	if (!atomic_flag_test_and_set(&dpif->destroyed)) {
+		cnt = ovs_refcount_unref(&dpif->ref_cnt);
+		if (cnt <= 2) {
+			OVS_NOT_REACHED();
+		}
+	}
+
+	return (0);
+}
+
+static int
+dpif_solaris_get_stats(const struct dpif *dpif_, struct dpif_dp_stats *stats)
+{
+	struct dpif_solaris		*dpif = dpif_solaris_cast(dpif_);
+	struct dpif_solaris_bridge	*bridge;
+	kstat2_status_t			stat;
+	kstat2_map_t			map;
+	char				kuri[1024];
+	char				kstat_name[MAXLINKNAMELEN];
+	char				*name;
+	zoneid_t			zid;
+	uint_t				instance;
+
+	bzero(stats, sizeof (struct dpif_dp_stats));
+	ovs_mutex_lock(&kstat_mutex);
+	if (!kstat2_handle_initialized) {
+		VLOG_DBG("dpif_solaris_get_stats initializing handle");
+		if (!kstat_handle_init(&dpif_khandle)) {
+			ovs_mutex_unlock(&kstat_mutex);
+			VLOG_DBG("dpif_solaris_get_stats: error initializing"
+			    " kstat handle");
+			return (-1);
+		}
+		kstat2_handle_initialized = B_TRUE;
+	} else if (!kstat_handle_update(dpif_khandle)) {
+		kstat_handle_close(&dpif_khandle);
+		kstat2_handle_initialized = B_FALSE;
+		ovs_mutex_unlock(&kstat_mutex);
+		VLOG_DBG("dpif_solaris_get_stats: error updating kstats");
+		return (-1);
+	}
+	ovs_rwlock_rdlock(&dpif->bridge_rwlock);
+	HMAP_FOR_EACH(bridge, node, &dpif->bridges) {
+		name = (char *)bridge->physname;
+		instance = 0;
+		if (strchr(bridge->physname, '/') != NULL) {
+			(void) solaris_dlparse_zonelinkname(bridge->physname,
+			    kstat_name, &zid);
+			name = kstat_name;
+			instance = zid;
+		}
+		(void) snprintf(kuri, sizeof (kuri), "kstat:/net/link/%s/%d",
+		    name, instance);
+		stat = kstat2_lookup_map(dpif_khandle, kuri, &map);
+
+		if (stat != KSTAT2_S_OK) {
+			ovs_rwlock_unlock(&dpif->bridge_rwlock);
+			ovs_mutex_unlock(&kstat_mutex);
+			VLOG_WARN("dpif_solaris_get_stats kstat_lookup of %s"
+			    " failed: %s", kuri, kstat2_status_string(stat));
+			return (-1);
+		}
+		stats->n_hit += (get_nvvt_int(map, "ipkthit") +
+		    get_nvvt_int(map, "opkthit"));
+		stats->n_missed += (get_nvvt_int(map, "ipktmiss") +
+		    get_nvvt_int(map, "opktmiss"));
+	}
+	ovs_rwlock_unlock(&dpif->bridge_rwlock);
+	ovs_mutex_unlock(&kstat_mutex);
+	stats->n_lost   = 0;
+	stats->n_flows  = solaris_flow_walk(NULL, NULL, B_TRUE, NULL);
+	stats->n_masks  = UINT32_MAX;
+	stats->n_mask_hit  = UINT64_MAX;
+	return (0);
+}
+
+static enum ovs_vport_type
+netdev_to_ovs_vport_type(const struct netdev *netdev)
+{
+	const char *type = netdev_get_type(netdev);
+
+	if (strcmp(type, "system") == 0) {
+		return (OVS_VPORT_TYPE_NETDEV);
+	} else if (strcmp(type, "internal") == 0) {
+		return (OVS_VPORT_TYPE_INTERNAL);
+	} else if (strcmp(type, "vxlan") == 0) {
+		return (OVS_VPORT_TYPE_VXLAN);
+	} else {
+		return (OVS_VPORT_TYPE_UNSPEC);
+	}
+}
+
+static int
+dpif_solaris_create_xsocket(struct dpif_solaris *dpif OVS_UNUSED,
+    struct dpif_solaris_port *port)
+{
+	int fd;
+	struct sockaddr_ll sll;
+	datalink_id_t linkid;
+	dladm_status_t status;
+	int error;
+
+	if (port->vtype != OVS_VPORT_TYPE_NETDEV &&
+	    port->vtype != OVS_VPORT_TYPE_VXLAN)
+		return (0);
+
+	if ((fd = socket(PF_PACKET, SOCK_RAW, ETH_P_ALL)) == -1) {
+		return (errno);
+	}
+
+	status = dladm_name2info(dpif->dh, port->linkname, &linkid,
+	    NULL, NULL, NULL);
+	error = solaris_dladm_status2error(status);
+	if (error != 0) {
+		(void) close(fd);
+		return (error);
+	}
+
+	(void) memset(&sll, 0, sizeof (sll));
+	sll.sll_family = AF_PACKET;
+	sll.sll_ifindex = linkid;
+	sll.sll_protocol = ETH_P_ALL;
+	if (bind(fd, (struct sockaddr *)&sll, sizeof (sll)) == -1) {
+		error = errno;
+		(void) close(fd);
+		return (error);
+	}
+	port->xfd = fd;
+	VLOG_DBG("dpif_solaris_create_xsocket %d on %s port_no: %d", fd,
+	    port->name, port->port_no);
+
+	return (0);
+}
+
+static boolean_t
+port_not_used(struct dpif_solaris *dpif, uint32_t port_no)
+    OVS_REQ_WRLOCK(dp->port_rwlock)
+{
+	struct dpif_solaris_port *port;
+	HMAP_FOR_EACH(port, node, &dpif->ports) {
+
+		if (port->port_no == port_no ||
+		    port->pf_port_no == port_no) {
+			return (true);
+		}
+	}
+	return (false);
+}
+
+/*
+ * Choose an unused, non-zero port number and return it on success.
+ * Return ODPP_NONE on failure.
+ * The current approach to choose unused port number is quite inefficient,
+ * need improvement. TBD
+ */
+static odp_port_t
+choose_port(struct dpif_solaris *dpif, uint32_t exclude_port_no)
+    OVS_REQ_WRLOCK(dp->port_rwlock)
+{
+	uint32_t port_no;
+
+	for (port_no = PORT_PF_PACKET_UPLINK + 1; port_no < OFPP_MAX;
+	    port_no++) {
+		if ((exclude_port_no == ODPP_NONE ||
+		    port_no != exclude_port_no) &&
+		    (!port_not_used(dpif, port_no))) {
+			return (u32_to_odp(port_no));
+		}
+	}
+
+	return (ODPP_NONE);
+}
+
+static struct dpif_solaris_bridge *
+dpif_solaris_lookup_bridge(const struct dpif_solaris *dpif,
+    const char *physname)
+    OVS_REQ_RDLOCK(dpif->bridge_rwlock)
+{
+	struct dpif_solaris_bridge *bridge;
+
+	HMAP_FOR_EACH(bridge, node, &dpif->bridges) {
+		if (strcmp(bridge->physname, physname) == 0) {
+			return (bridge);
+		}
+	}
+
+	return (NULL);
+}
+
+static struct dpif_solaris_bridge *
+dpif_solaris_bridge_add_port(struct dpif_solaris *dpif,
+    const char *physname, struct dpif_solaris_port *port)
+    OVS_REQ_WRLOCK(dpif->port_rwlock)
+{
+	struct dpif_solaris_bridge *bridge;
+
+	ovs_rwlock_wrlock(&dpif->bridge_rwlock);
+	VLOG_DBG("dpif_solaris_bridge_add_port adding port %d to uplink %s",
+	    port->port_no, physname);
+	bridge = dpif_solaris_lookup_bridge(dpif, physname);
+	if (bridge == NULL) {
+		VLOG_DBG("dpif_solaris_bridge_add_port creating bridge");
+		bridge = xzalloc(sizeof (*bridge));
+		bridge->physname = xstrdup(physname);
+		hmap_insert(&dpif->bridges, &bridge->node,
+		    hash_string(bridge->physname, 0));
+		hmap_init(&bridge->ports);
+	}
+	port->bridge = bridge;
+	hmap_insert(&bridge->ports, &port->brnode,
+	    hash_odp_port(port->port_no));
+	if (port->is_uplink)
+		bridge->uplink_port = port;
+
+	ovs_rwlock_unlock(&dpif->bridge_rwlock);
+	return (bridge);
+}
+
+static void
+dpif_solaris_bridge_del_port(struct dpif_solaris *dpif,
+    struct dpif_solaris_port *port)
+    OVS_REQ_WRLOCK(dpif->port_rwlock)
+{
+	struct dpif_solaris_bridge *bridge = port->bridge;
+
+	if (bridge == NULL) {
+		VLOG_DBG("dpif_solaris_bridge_del_port port %d not assigned "
+		    "to a bridge", port->port_no);
+		return;
+	}
+	VLOG_DBG("dpif_solaris_bridge_del_port deleting port %d from %s",
+	    port->port_no, bridge->physname);
+
+	ovs_rwlock_wrlock(&dpif->bridge_rwlock);
+	hmap_remove(&bridge->ports, &port->brnode);
+	if (port == bridge->uplink_port)
+		bridge->uplink_port = NULL;
+
+	if (hmap_is_empty(&bridge->ports)) {
+		VLOG_DBG("dpif_solaris_bridge_del_port destroying bridge");
+		hmap_destroy(&bridge->ports);
+		hmap_remove(&dpif->bridges, &bridge->node);
+		free(bridge->physname);
+		free(bridge);
+	}
+	ovs_rwlock_unlock(&dpif->bridge_rwlock);
+}
+
+static int
+dpif_solaris_port_add__(struct dpif_solaris *dpif, struct netdev *netdev,
+			odp_port_t *port_nop)
+{
+	char namebuf[NETDEV_VPORT_NAME_BUFSIZE];
+	const char *name = netdev_vport_get_dpif_port(netdev,
+	    namebuf, sizeof (namebuf));
+	const char *linkname = netdev_get_name(netdev);
+	const char *type = netdev_get_type(netdev);
+	enum ovs_vport_type vtype;
+	struct dpif_solaris_port *port = NULL;
+	char physname[MAXLINKNAMELEN];
+	char dlbuffer[DLADM_PROP_VAL_MAX];
+	int error = 0;
+	odp_port_t pf_port_no;
+	boolean_t is_uplink = false;
+
+	vtype = netdev_to_ovs_vport_type(netdev);
+	if (vtype == OVS_VPORT_TYPE_UNSPEC) {
+		VLOG_WARN_RL(&error_rl, "%s: cannot create port `%s' because "
+		    "it has unsupported type `%s'", dpif_name(&dpif->dpif),
+		    name, type);
+		return (EINVAL);
+	}
+
+	VLOG_DBG("dpif_solaris_port_add %s (%s) type %s port_no %d vtype %d",
+	    name, linkname, type, *port_nop, vtype);
+
+	if (vtype == OVS_VPORT_TYPE_NETDEV || vtype == OVS_VPORT_TYPE_VXLAN) {
+		error = solaris_get_dlclass(linkname, dlbuffer,
+		    sizeof (dlbuffer));
+		if (error != 0)
+			return (error);
+
+		if (solaris_is_uplink_class(dlbuffer)) {
+			if (strlcpy(physname, linkname, sizeof (physname)) >=
+			    sizeof (physname))
+				return (EINVAL);
+			VLOG_DBG("dpif_solaris_port_add primary port %s",
+			    name);
+			is_uplink = true;
+		} else {
+			error = solaris_get_dllower(linkname, dlbuffer,
+			    sizeof (dlbuffer));
+			if (error != 0)
+				return (error);
+			if (strlcpy(physname, dlbuffer, sizeof (physname)) >=
+			    sizeof (physname))
+				return (EINVAL);
+			VLOG_DBG("dpif_solaris_port_add non-primary port "
+			    "%s to %s", name, dlbuffer);
+		}
+	} else if (vtype == OVS_VPORT_TYPE_INTERNAL) {
+		VLOG_DBG("dpif_solaris_port_add adding internal port %s",
+		    name);
+	} else {
+		VLOG_DBG("dpif_solaris_port_add adding unknown type");
+		return (EINVAL);
+	}
+	ovs_rwlock_wrlock(&dpif->upcall_lock);
+	ovs_rwlock_wrlock(&dpif->port_rwlock);
+
+	if (*port_nop == ODPP_NONE) {
+		*port_nop = choose_port(dpif, ODPP_NONE);
+		if (*port_nop == ODPP_NONE) {
+			error = EFBIG;
+			goto fail;
+		}
+	}
+	pf_port_no = is_uplink ? PORT_PF_PACKET_UPLINK : choose_port(dpif,
+	    *port_nop);
+	if (pf_port_no == ODPP_NONE) {
+		error = EFBIG;
+		goto fail;
+	}
+	if (vtype == OVS_VPORT_TYPE_NETDEV || vtype == OVS_VPORT_TYPE_VXLAN) {
+		uint64_t u64 = (uint32_t)(*port_nop);
+
+		VLOG_DBG("set portno %d on %s", (uint32_t)(*port_nop),
+		    linkname);
+		if ((error = solaris_set_dlprop_ulong(linkname, "ofport",
+		    &u64)) != 0) {
+			VLOG_ERR("set portno %d on %s failed: %s",
+			    (uint32_t)(*port_nop), linkname,
+			    ovs_strerror(error));
+			goto fail;
+		}
+	}
+
+	port = xzalloc(sizeof (*port));
+	port->port_no = *port_nop;
+	port->pf_port_no = pf_port_no;
+	port->name = xstrdup(name);
+	port->linkname = xstrdup(linkname);
+	port->type = xstrdup(type);
+	port->vtype = vtype;
+	port->netdev = netdev;
+	port->upcall_fd = -1;
+	port->xfd = -1;
+	port->is_uplink = is_uplink;
+
+	/* Only create the TX PF_SOCKET on the physical link */
+	if (is_uplink) {
+		error = dpif_solaris_create_xsocket(dpif, port);
+		if (error != 0) {
+			VLOG_ERR("dpif_solaris_create_xsocket on %s failed: %s",
+			    port->linkname, ovs_strerror(error));
+			goto fail;
+		}
+	}
+
+	error = dpif_solaris_refresh_port_channel(dpif, port, physname, true);
+	if (error != 0) {
+		VLOG_ERR("dpif_solaris_refresh_port_channel on %s failed: %s",
+		    linkname, ovs_strerror(error));
+		goto fail;
+	}
+
+	if (vtype == OVS_VPORT_TYPE_NETDEV || vtype == OVS_VPORT_TYPE_VXLAN)
+		dpif_solaris_bridge_add_port(dpif, physname, port);
+	hmap_insert(&dpif->ports, &port->node, hash_odp_port(port->port_no));
+
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+	ovs_rwlock_unlock(&dpif->upcall_lock);
+	return (0);
+fail:
+	if (port != NULL) {
+		if (port->xfd != -1)
+			(void) close(port->xfd);
+		if (port->upcall_fd != -1) {
+			(void) setsockopt(port->upcall_fd, SOL_PACKET,
+			    PACKET_REM_OF_DEFFLOW, NULL, 0);
+			(void) close(port->upcall_fd);
+		}
+		free(port->name);
+		free(port->linkname);
+		free(port->type);
+		free(port);
+	}
+	if (vtype == OVS_VPORT_TYPE_NETDEV || vtype == OVS_VPORT_TYPE_VXLAN) {
+		VLOG_DBG("reset portno on %s", linkname);
+		(void) solaris_set_dlprop_ulong(linkname, "ofport", NULL);
+	}
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+	ovs_rwlock_unlock(&dpif->upcall_lock);
+	return (error);
+}
+
+static int
+dpif_solaris_port_add(struct dpif *dpif_, struct netdev *netdev,
+    odp_port_t *port_nop)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	int error;
+
+	error = dpif_solaris_port_add__(dpif, netdev, port_nop);
+	return (error);
+}
+
+static int
+dpif_solaris_get_port_by_number(struct dpif_solaris *dpif, odp_port_t port_no,
+    struct dpif_solaris_port **portp)
+    OVS_REQ_RDLOCK(dpif->port_rwlock)
+{
+	struct dpif_solaris_port *port;
+
+	if (port_no == ODPP_NONE) {
+		*portp = NULL;
+		return (EINVAL);
+	}
+
+	HMAP_FOR_EACH_WITH_HASH(port, node,
+	    hash_odp_port(port_no), &dpif->ports) {
+		if (port->port_no == port_no) {
+			*portp = port;
+			return (0);
+		}
+	}
+
+	*portp = NULL;
+	return (ENOENT);
+}
+
+static int
+dpif_solaris_get_uplink_port(struct dpif_solaris *dpif,
+    struct dpif_solaris_port **portp)
+    OVS_REQ_RDLOCK(dpif->port_rwlock)
+{
+	struct dpif_solaris_port *port;
+
+	HMAP_FOR_EACH(port, node, &dpif->ports) {
+		if (port->is_uplink) {
+			*portp = port;
+			return (0);
+		}
+	}
+
+	*portp = NULL;
+	return (ENOENT);
+}
+
+static void
+dpif_solaris_port_del__(struct dpif_solaris *dpif,
+    struct dpif_solaris_port *port)
+    OVS_REQ_WRLOCK(dpif->port_rwlock)
+{
+	VLOG_DBG("dpif_solaris_port_del__ port %s # %d", port->name,
+	    port->port_no);
+
+	hmap_remove(&dpif->ports, &port->node);
+	dpif_solaris_bridge_del_port(dpif, port);
+
+	if (port->xfd != -1)
+		(void) close(port->xfd);
+	if (port->upcall_fd != -1) {
+		(void) setsockopt(port->upcall_fd, SOL_PACKET,
+		    PACKET_REM_OF_DEFFLOW, NULL, 0);
+		(void) close(port->upcall_fd);
+	}
+	if (port->vtype == OVS_VPORT_TYPE_NETDEV || port->vtype ==
+	    OVS_VPORT_TYPE_VXLAN) {
+			VLOG_DBG("1.reset portno on %s", port->linkname);
+			(void) solaris_set_dlprop_ulong(port->linkname,
+			    "ofport", NULL);
+		if (port->is_uplink) {
+			VLOG_DBG("dpif_solaris_port_del__ primary port "
+			    "%s", port->name);
+		}
+	}
+	VLOG_DBG("dpif_solaris_port_del %s close xfd %d upcall_fd %d",
+	    port->name, port->xfd, port->upcall_fd);
+
+	free(port->type);
+	free(port->name);
+	free(port->linkname);
+	free(port);
+}
+
+static int
+dpif_solaris_port_del(struct dpif *dpif_, odp_port_t port_no)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	struct dpif_solaris_port *port;
+	int error;
+
+	VLOG_DBG("dpif_solaris_port_del port # %d", port_no);
+
+	if (port_no == ODPP_LOCAL) {
+		VLOG_ERR("dpif_solaris_port_del invalid port # %d", port_no);
+		return (EINVAL);
+	}
+
+	ovs_rwlock_wrlock(&dpif->port_rwlock);
+	error = dpif_solaris_get_port_by_number(dpif, port_no, &port);
+	if (error) {
+		ovs_rwlock_unlock(&dpif->port_rwlock);
+		VLOG_ERR("dpif_solaris_port_del port # %d failed %d", port_no,
+		    error);
+		return (error);
+	}
+	dpif_solaris_port_del__(dpif, port);
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+	return (0);
+}
+
+static int
+dpif_solaris_port_query_by_number(const struct dpif *dpif_, odp_port_t port_no,
+    struct dpif_port *dpif_port)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	struct dpif_solaris_port *port = NULL;
+	int error;
+
+	ovs_rwlock_rdlock(&dpif->port_rwlock);
+	error = dpif_solaris_get_port_by_number(dpif, port_no, &port);
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+	if (!error && dpif_port) {
+		dpif_port->name = xstrdup(port->name);
+		dpif_port->type = xstrdup(port->type);
+		dpif_port->port_no = port->port_no;
+	}
+	return (error);
+}
+
+static int
+dpif_solaris_port_query_by_name(const struct dpif *dpif_, const char *devname,
+				struct dpif_port *dpif_port)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	struct dpif_solaris_port *port = NULL;
+
+	ovs_rwlock_wrlock(&dpif->port_rwlock);
+	HMAP_FOR_EACH(port, node, &dpif->ports) {
+		if (strcmp(port->name, devname) == 0) {
+			if (dpif_port) {
+				dpif_port->name = xstrdup(devname);
+				dpif_port->type = xstrdup(port->type);
+				dpif_port->port_no = port->port_no;
+			}
+			ovs_rwlock_unlock(&dpif->port_rwlock);
+			return (0);
+		}
+	}
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+	return (ENOENT);
+}
+
+static int
+dpif_solaris_configure_bridge_port(const struct dpif *dpif_,
+    const char *devname)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	struct dpif_solaris_port *port = NULL;
+	char dlbuffer[DLADM_PROP_VAL_MAX];
+	char physname[MAXLINKNAMELEN];
+	uint64_t u64;
+	int error = 0;
+
+	VLOG_DBG("dpif_solaris_configure_bridge_port %s", devname);
+
+	ovs_rwlock_wrlock(&dpif->port_rwlock);
+	HMAP_FOR_EACH(port, node, &dpif->ports) {
+		if (strcmp(port->name, devname) == 0) {
+			error = solaris_get_dllower(devname, dlbuffer,
+			    sizeof (dlbuffer));
+			if (error != 0) {
+				VLOG_ERR("failed to get lowerlink: %s",
+				    ovs_strerror(error));
+				goto out;
+			}
+			if (strlcpy(physname, dlbuffer, sizeof (physname)) >=
+			    sizeof (physname)) {
+				VLOG_ERR("invalid lowerlink size");
+				error = EINVAL;
+				goto out;
+			}
+
+			u64 = (uint32_t)(port->port_no);
+			VLOG_DBG("set portno %d on %s",
+			    (uint32_t)(port->port_no), devname);
+			error = solaris_set_dlprop_ulong(devname, "ofport",
+			    &u64);
+			if (error != 0) {
+				VLOG_ERR("set portno %d on %s failed: %s",
+				    (uint32_t)(port->port_no), devname,
+				    ovs_strerror(error));
+				goto out;
+			}
+			VLOG_DBG("dpif_solaris_port_add internal port "
+			    "%s to %s", devname, physname);
+			(void) dpif_solaris_bridge_add_port(dpif, physname,
+			    port);
+			goto out;
+		}
+	}
+	error = ENOENT;
+
+out:
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+	return (error);
+}
+
+struct dpif_solaris_port_state {
+	struct hmap ports;
+	struct ovs_rwlock port_rwlock;
+	uint32_t bucket;
+	uint32_t offset;
+	char *name;
+};
+
+static void
+port_dump_start(void *arg, const char *name, char *type, odp_port_t portno)
+{
+	struct dpif_solaris_port_state *state = arg;
+	struct dpif_solaris_port *port;
+
+	port = xzalloc(sizeof (struct dpif_solaris_port));
+	port->name = xstrdup(name);
+	port->type = type;
+	port->port_no = portno;
+	ovs_rwlock_wrlock(&state->port_rwlock);
+	hmap_insert(&state->ports, &port->node, hash_odp_port(port->port_no));
+	ovs_rwlock_unlock(&state->port_rwlock);
+}
+
+static int
+dpif_solaris_port_dump_start(const struct dpif *dpif_ OVS_UNUSED,
+    void **statep)
+{
+	struct dpif_solaris_port_state *state;
+
+	*statep = state = xzalloc(sizeof (struct dpif_solaris_port_state));
+	hmap_init(&state->ports);
+	ovs_rwlock_init(&state->port_rwlock);
+	solaris_port_walk(state, port_dump_start);
+	return (0);
+}
+
+static int
+dpif_solaris_port_dump_next(const struct dpif *dpif_ OVS_UNUSED, void *state_,
+    struct dpif_port *dpif_port)
+{
+	struct dpif_solaris_port_state *state = state_;
+	struct hmap_node *node;
+	int retval;
+
+	ovs_rwlock_wrlock(&state->port_rwlock);
+	node = hmap_at_position(&state->ports, &state->bucket, &state->offset);
+	if (node) {
+		struct dpif_solaris_port *port;
+
+		port = CONTAINER_OF(node, struct dpif_solaris_port, node);
+
+		free(state->name);
+		state->name = xstrdup(port->name);
+		dpif_port->name = state->name;
+		dpif_port->type = port->type;
+		dpif_port->port_no = port->port_no;
+		retval = 0;
+	} else {
+		retval = EOF;
+	}
+	ovs_rwlock_unlock(&state->port_rwlock);
+
+	return (retval);
+}
+
+static int
+dpif_solaris_port_dump_done(const struct dpif *dpif_ OVS_UNUSED, void *state_)
+{
+	struct dpif_solaris_port_state *state = state_;
+	struct dpif_solaris_port *port, *next;
+
+	ovs_rwlock_wrlock(&state->port_rwlock);
+	HMAP_FOR_EACH_SAFE(port, next, node, &state->ports) {
+		free(port->name);
+		hmap_remove(&state->ports, &port->node);
+	}
+	ovs_rwlock_unlock(&state->port_rwlock);
+	hmap_destroy(&state->ports);
+	ovs_rwlock_destroy(&state->port_rwlock);
+	free(state->name);
+	free(state);
+	return (0);
+}
+
+static int
+dpif_solaris_port_poll(const struct dpif *dpif_ OVS_UNUSED,
+    char **devnamep OVS_UNUSED)
+{
+	return (EAGAIN);
+}
+
+static void
+dpif_solaris_port_poll_wait(const struct dpif *dpif_ OVS_UNUSED)
+{
+}
+
+static int
+dpif_solaris_key_to_flow(const struct nlattr *key, size_t key_len,
+    struct flow *f)
+{
+	if (odp_flow_key_to_flow(key, key_len, f)) {
+		/*
+		 * This should not happen: it indicates that
+		 * odp_flow_key_from_flow() and odp_flow_key_to_flow()
+		 * disagree on the acceptable form of a flow.
+		 * Log the problem as an error, with enough details to enable
+		 * debugging.
+		 */
+		static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+		if (!VLOG_DROP_ERR(&rl)) {
+			struct ds s;
+
+			ds_init(&s);
+
+			odp_flow_format(key, key_len, NULL, 0, NULL, &s, true);
+			VLOG_ERR("internal error parsing flow key %s",
+			    ds_cstr(&s));
+			ds_destroy(&s);
+		}
+
+		return (EINVAL);
+	}
+	return (0);
+}
+
+static int
+dpif_solaris_key_to_mask(const struct nlattr *key, uint32_t key_len,
+    const struct nlattr *mask_key, uint32_t mask_key_len,
+    const struct flow *f, struct flow *m)
+{
+	enum mf_field_id id;
+	enum odp_key_fitness fitness;
+
+	/*
+	 * Force unwildcard the in_port.
+	 * We need to do this even in the case where we unwildcard "everything"
+	 * above because "everything" only includes the 16-bit OpenFlow port
+	 * number mask->in_port.ofp_port, which only covers half of the 32-bit
+	 * datapath port number mask->in_port.odp_port.
+	 */
+	m->in_port.odp_port = ODPP_NONE;
+
+	if (!mask_key_len) {
+		/*
+		 * No mask key, unwildcard everything except fields whose
+		 * prerequisities are not met.
+		 */
+		memset(m, 0x0, sizeof (*m));
+
+		for (id = 0; id < MFF_N_IDS; ++id) {
+			/* Skip registers and metadata. */
+			if (!(id >= MFF_REG0 && id < MFF_REG0 + FLOW_N_REGS) &&
+			    id != MFF_METADATA) {
+				const struct mf_field *mf = mf_from_id(id);
+				if (mf_are_prereqs_ok(mf, f)) {
+					mf_mask_field(mf, m);
+				}
+			}
+		}
+		return (0);
+	}
+
+	fitness = odp_flow_key_to_mask(mask_key, mask_key_len, m, f);
+	if (fitness) {
+		/*
+		 * This should not happen: it indicates that
+		 * odp_flow_key_from_mask() and odp_flow_key_to_mask()
+		 * disagree on the acceptable form of a mask.
+		 * Log the problem as an error, with enough details to
+		 * enable debugging.
+		 */
+		static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+		if (!VLOG_DROP_ERR(&rl)) {
+			struct ds s;
+
+			ds_init(&s);
+			odp_flow_format(key, key_len, mask_key, mask_key_len,
+			    NULL, &s, true);
+			VLOG_ERR("internal error parsing flow mask %s (%s)",
+			    ds_cstr(&s), odp_key_fitness_to_string(fitness));
+			ds_destroy(&s);
+		}
+
+		return (EINVAL);
+	}
+
+	return (0);
+}
+
+static struct dpif_solaris_flow *
+dpif_solaris_flow_cast(const struct cls_rule *cr)
+{
+	return (cr ? CONTAINER_OF(cr, struct dpif_solaris_flow, cr) : NULL);
+}
+
+static struct dpif_solaris_flow *
+dpif_solaris_lookup_flow(const struct dpif_solaris *dpif,
+    const struct miniflow *key)
+    OVS_EXCLUDED(dpif->cls.rwlock)
+{
+	struct dpif_solaris_flow *solaris_flow;
+	struct cls_rule *rule;
+
+	fat_rwlock_rdlock(&dpif->cls.rwlock);
+	rule = classifier_lookup_miniflow_first(&dpif->cls, key);
+	solaris_flow = dpif_solaris_flow_cast(rule);
+	fat_rwlock_unlock(&dpif->cls.rwlock);
+
+	return (solaris_flow);
+}
+
+static struct dpif_solaris_flow *
+dpif_solaris_find_flow(const struct dpif_solaris *dpif, const struct flow *flow)
+{
+	struct dpif_solaris_flow *solaris_flow;
+
+	HMAP_FOR_EACH_WITH_HASH(solaris_flow, node, flow_hash(flow, 0),
+	    &dpif->flows) {
+		if (flow_equal(&solaris_flow->flow, flow)) {
+			return (solaris_flow);
+		}
+	}
+
+	return (NULL);
+}
+
+static struct dpif_solaris_flow *
+dpif_solaris_flow_add(struct dpif_solaris *dpif, const char *physname,
+    struct flow *flow, const struct flow_wildcards *wc)
+    OVS_REQ_WRLOCK(dpif->flow_rwlock)
+{
+	struct dpif_solaris_flow *solaris_flow;
+	struct match match;
+
+	solaris_flow = xzalloc(sizeof (*solaris_flow));
+
+	solaris_flow->physname = xstrdup(physname);
+	(void) snprintf(solaris_flow->flowname, MAXUSERFLOWNAMELEN,
+	    "%p.sys.of", (void *)solaris_flow);
+	solaris_flow->flow = *flow;
+	match_init(&match, flow, wc);
+	fat_rwlock_wrlock(&dpif->cls.rwlock);
+	cls_rule_init(&solaris_flow->cr, &match, 0x8001);
+	hmap_insert(&dpif->flows, &solaris_flow->node, flow_hash(flow, 0));
+	classifier_insert(&dpif->cls, &solaris_flow->cr);
+	fat_rwlock_unlock(&dpif->cls.rwlock);
+	return (solaris_flow);
+}
+
+static void
+dpif_solaris_flow_remove(struct dpif_solaris *dpif,
+    struct dpif_solaris_flow *solaris_flow) OVS_REQ_WRLOCK(dpif->flow_rwlock)
+{
+	struct cls_rule *cr;
+	struct hmap_node *node;
+
+	fat_rwlock_wrlock(&dpif->cls.rwlock);
+	cr = &solaris_flow->cr;
+	node = CONST_CAST(struct hmap_node *, &solaris_flow->node);
+	classifier_remove(&dpif->cls, cr);
+	hmap_remove(&dpif->flows, node);
+	cls_rule_destroy(CONST_CAST(struct cls_rule *, cr));
+	fat_rwlock_unlock(&dpif->cls.rwlock);
+	free(solaris_flow->physname);
+	free(solaris_flow);
+}
+
+static int
+dpif_solaris_get_flowstats(char *flowname, struct dpif_flow_stats *stats)
+{
+	uint64_t npackets, nbytes, lastused;
+	int error;
+
+	bzero(stats, sizeof (*stats));
+	error = solaris_get_flowstats(flowname, &npackets, &nbytes, &lastused);
+	if (error == 0) {
+		stats->n_packets = npackets;
+		stats->n_bytes = nbytes;
+		stats->used = lastused / 1000000;
+	}
+	return (error);
+}
+
+static int
+dpif_solaris_flow_get(const struct dpif *dpif_,
+    const struct nlattr *key, size_t key_len,
+    struct ofpbuf **bufp,
+    struct nlattr **maskp, size_t *mask_len,
+    struct nlattr **actionsp, size_t *actions_len,
+    struct dpif_flow_stats *stats)
+{
+	const struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	struct flow f, m;
+	struct dpif_solaris_flow *solaris_flow;
+	char flowname[MAXUSERFLOWNAMELEN];
+	struct ofpbuf actions;
+	int error;
+
+	VLOG_DBG("dpif_solaris_flow_get");
+
+	error = dpif_solaris_key_to_flow(key, key_len, &f);
+	if (error) {
+		VLOG_ERR("dpif_solaris_key_to_flow failed %d", error);
+		return (error);
+	}
+
+	ovs_rwlock_rdlock(&dpif->flow_rwlock);
+	solaris_flow = dpif_solaris_find_flow(dpif, &f);
+	if (solaris_flow == NULL) {
+		ovs_rwlock_unlock(&dpif->flow_rwlock);
+		error = ENOENT;
+		VLOG_ERR("dpif_solaris_flow_get failed %d", error);
+		return (error);
+	}
+	(void) strlcpy(flowname, solaris_flow->flowname, MAXUSERFLOWNAMELEN);
+	error = solaris_get_flowattr(flowname, &f, &m);
+	if (error) {
+		VLOG_ERR("solaris_get_flowattr failed %d", error);
+		ovs_rwlock_unlock(&dpif->flow_rwlock);
+		return (error);
+	}
+
+	ovs_rwlock_unlock(&dpif->flow_rwlock);
+
+	/* Allocate buffer big enough for everything. */
+	*bufp = ofpbuf_new(4096);
+	ofpbuf_init(*bufp, 4096);
+
+	/* Mask. */
+	odp_flow_key_from_mask(*bufp, &m, &f, m.in_port.odp_port,
+	    SIZE_MAX);
+	*maskp = ofpbuf_data(*bufp);
+	*mask_len = ofpbuf_size(*bufp);
+
+	/* Actions. */
+	ofpbuf_init(&actions, 0);
+	(void) solaris_get_flowaction(flowname, &actions);
+	if (ofpbuf_size(&actions) == 0) {
+		*actionsp = NULL;
+		*actions_len = 0;
+	} else {
+		*actionsp = ofpbuf_put(*bufp, ofpbuf_data(&actions),
+		    ofpbuf_size(&actions));
+		*actions_len = ofpbuf_size(&actions);
+	}
+	ofpbuf_uninit(&actions);
+
+	/* Stats. */
+	error = dpif_solaris_get_flowstats(flowname, stats);
+	return (0);
+}
+
+int
+dpif_solaris_get_priority_details(void *cookie, odp_port_t outport,
+    uint_t queueid, struct smap *details)
+{
+	struct dpif_solaris		*dpif = (struct dpif_solaris *)cookie;
+	struct dpif_solaris_port	*port = NULL;
+	int				error = 0;
+	struct netdev			*netdev;
+
+	VLOG_DBG("dpif_solaris_get_priority_details: "
+	" Port : %d, Queue: %d\n", outport, queueid);
+
+	ovs_rwlock_wrlock(&dpif->port_rwlock);
+	error = dpif_solaris_get_port_by_number(dpif, outport, &port);
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+	if (error != 0) {
+		VLOG_DBG("dpif_solaris_get_priority_details: "
+		"Error getting port %d\n", outport);
+		return (error);
+	}
+
+	netdev = port->netdev;
+	error = netdev_get_queue(netdev, queueid, details);
+	if (error != 0) {
+		VLOG_DBG("dpif_solaris_get_priority_details: "
+		" Error getting queue %d\n", queueid);
+		return (error);
+	}
+	VLOG_DBG("dpif_solaris_get_priority_details: done");
+	return (0);
+}
+
+void
+dpif_log(int err, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	if (err == 0)
+		vlog_valist(THIS_MODULE, VLL_DBG, fmt, ap);
+	else
+		vlog_valist(THIS_MODULE, VLL_ERR, fmt, ap);
+	va_end(ap);
+}
+
+static int
+dpif_solaris_flow_put(struct dpif *dpif_, const struct dpif_flow_put *put)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	struct flow f;
+	struct flow_wildcards wc;
+	struct miniflow miniflow;
+	struct dpif_solaris_flow *solaris_flow;
+	char flowname[MAXUSERFLOWNAMELEN];
+	int error;
+	struct ds fs, as;
+
+	VLOG_DBG("dpif_solaris_flow_put");
+
+	ds_init(&fs);
+	ds_init(&as);
+	odp_flow_format(put->key, put->key_len, put->mask,
+	    put->mask_len, NULL, &fs, true);
+	ds_put_cstr(&as, ", actions:");
+	format_odp_actions(&as, put->actions, put->actions_len);
+
+	error = dpif_solaris_key_to_flow(put->key, put->key_len, &f);
+	if (error) {
+		VLOG_ERR("dpif_solaris_key_to_flow failed %d", error);
+		goto done;
+	}
+	error = dpif_solaris_key_to_mask(put->key, put->key_len, put->mask,
+	    put->mask_len, &f, &wc.masks);
+	if (error) {
+		VLOG_ERR("dpif_solaris_key_to_mask failed %d", error);
+		goto done;
+	}
+
+	miniflow_init(&miniflow, &f);
+
+	ovs_rwlock_wrlock(&dpif->flow_rwlock);
+	solaris_flow = dpif_solaris_lookup_flow(dpif, &miniflow);
+	miniflow_destroy(&miniflow);
+
+	if (solaris_flow == NULL) {
+		if (put->flags & DPIF_FP_CREATE) {
+			struct dpif_solaris_port	*port = NULL;
+			odp_port_t			inport;
+
+			inport = f.in_port.odp_port;
+			ovs_rwlock_rdlock(&dpif->port_rwlock);
+			error = dpif_solaris_get_port_by_number(dpif, inport,
+			    &port);
+			ovs_rwlock_unlock(&dpif->port_rwlock);
+			if (error == 0) {
+				solaris_flow = dpif_solaris_flow_add(dpif,
+				    port->bridge->physname, &f, &wc);
+				(void) strlcpy(flowname,
+				    solaris_flow->flowname,
+				    MAXUSERFLOWNAMELEN);
+				VLOG_DBG("dpif_solaris_flow_put %s mask %s "
+				    "actions %s", flowname, ds_cstr(&fs),
+				    ds_cstr(&as));
+				/*
+				 * workaround to passing a struct dpif to
+				 * solaris_add_flow adding dpif_provider.h to
+				 * util-solaris brings up a conflict in
+				 * Solaris's list and the OVS list
+				 * implementations.
+				 */
+				error = solaris_add_flow((void *)dpif,
+				    port->bridge->physname, flowname, &f,
+				    &wc.masks, put->actions, put->actions_len);
+				if (error == 0) {
+					VLOG_DBG("dpif_solaris_flow_put "
+					    "solaris_add_flow %s on %s succeed",
+					    flowname, port->bridge->physname);
+				} else {
+					VLOG_ERR("dpif_solaris_flow_put "
+					    "solaris_add_flow %s on %s failed "
+					    "%d", flowname,
+					    port->bridge->physname,
+					    error);
+					dpif_solaris_flow_remove(dpif,
+					    solaris_flow);
+				}
+			} else {
+				VLOG_DBG("dpif_solaris_flow_put(): "
+				    "Error getting inport %d\n", inport);
+			}
+		} else {
+			VLOG_ERR("dpif_solaris_flow_put mask %s "
+			    "actions %s not found", ds_cstr(&fs),
+			    ds_cstr(&as));
+			error = ENOENT;
+		}
+	} else {
+		VLOG_DBG("dpif_solaris_flow_put exsting flow %s found for"
+		    " mask %s actions %s", solaris_flow->flowname,
+		    ds_cstr(&fs), ds_cstr(&as));
+		if ((put->flags & DPIF_FP_MODIFY) &&
+		    flow_equal(&f, &solaris_flow->flow)) {
+			(void) strlcpy(flowname, solaris_flow->flowname,
+			    MAXUSERFLOWNAMELEN);
+			error = solaris_modify_flow((void *)dpif,
+			    flowname, put->actions, put->actions_len);
+			if (error == 0) {
+				VLOG_DBG("dpif_solaris_flow_put "
+				    "exsting flow %s found and updated,"
+				    "for mask %s actions %s", flowname,
+				    ds_cstr(&fs), ds_cstr(&as));
+			} else {
+				VLOG_ERR("dpif_solaris_flow_put "
+				    "exsting flow %s found but update "
+				    "failed %d, for mask %s actions %s",
+				    flowname, error, ds_cstr(&fs),
+				    ds_cstr(&as));
+			}
+		} else if (put->flags & DPIF_FP_CREATE) {
+			VLOG_DBG("dpif_solaris_flow_put existing flow "
+			    "%s already exists for mask %s actions %s",
+			    solaris_flow->flowname, ds_cstr(&fs),
+			    ds_cstr(&as));
+			error = EEXIST;
+		} else {
+			/*
+			 * Overlapping flow. (Flow & Mask) matches but flow
+			 * itself does not match
+			 */
+			VLOG_ERR("dpif_solaris_flow_put overlapped "
+			    "with existing flow %s, for mask %s actions %s",
+			    solaris_flow->flowname, ds_cstr(&fs),
+			    ds_cstr(&as));
+			error = EINVAL;
+		}
+	}
+
+	if (error == 0) {
+		if (put->stats) {
+			if (dpif_solaris_get_flowstats(flowname,
+			    put->stats) == 0) {
+				VLOG_DBG("dpif_solaris_flow_put "
+				    "get_flowstats %s succeed", flowname);
+			} else {
+				VLOG_ERR("dpif_solaris_flow_put "
+				    "get_flowstats %s failed", flowname);
+			}
+		}
+		/*
+		 * If DPIF_FP_MODIFY and DPIF_FP_ZERO_STATS is set in
+		 * put->flags, we should zero out the statistics of the
+		 * given flow. This is currently not supported, and so far,
+		 * we don't see any problem yet.
+		 */
+	}
+	ovs_rwlock_unlock(&dpif->flow_rwlock);
+done:
+	ds_destroy(&fs);
+	ds_destroy(&as);
+	return (error);
+}
+
+static int
+dpif_solaris_flow_del(struct dpif *dpif_, const struct dpif_flow_del *del)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	struct dpif_solaris_flow *solaris_flow;
+	char flowname[MAXUSERFLOWNAMELEN];
+	char *physname;
+	struct flow f;
+	int error;
+	struct ds fs;
+
+	ds_init(&fs);
+	odp_flow_format(del->key, del->key_len, NULL, 0, NULL, &fs, true);
+	VLOG_DBG("dpif_solaris_flow_del %s", ds_cstr(&fs));
+	ds_destroy(&fs);
+
+	error = dpif_solaris_key_to_flow(del->key, del->key_len, &f);
+	if (error) {
+		VLOG_ERR("dpif_solaris_key_to_flow failed %d", error);
+		return (error);
+	}
+
+	ovs_rwlock_wrlock(&dpif->flow_rwlock);
+	solaris_flow = dpif_solaris_find_flow(dpif, &f);
+	if (solaris_flow == NULL) {
+		ovs_rwlock_unlock(&dpif->flow_rwlock);
+		error = ENOENT;
+		VLOG_ERR("dpif_solaris_flow_del failed %d", error);
+		return (error);
+	}
+	(void) strlcpy(flowname, solaris_flow->flowname, MAXUSERFLOWNAMELEN);
+	physname = xstrdup(solaris_flow->physname);
+	dpif_solaris_flow_remove(dpif, solaris_flow);
+	if (del->stats) {
+		error = dpif_solaris_get_flowstats(flowname, del->stats);
+		if (error == 0) {
+			VLOG_DBG("dpif_solaris_flow_del "
+			    "get_flowstats %s succeed", flowname);
+		} else {
+			VLOG_ERR("dpif_solaris_flow_del "
+			    "get_flowstats %s failed %d", flowname, error);
+		}
+	}
+	error = solaris_remove_flow(physname, flowname);
+	if (error == 0) {
+		VLOG_DBG("dpif_solaris_flow_del solaris_remove_flow %s "
+		    "succeed", flowname);
+	} else {
+		VLOG_ERR("dpif_solaris_flow_del solaris_remove_flow %s "
+		    "failed %d", flowname, error);
+	}
+	ovs_rwlock_unlock(&dpif->flow_rwlock);
+
+	free(physname);
+	return (error);
+}
+
+static int
+dpif_solaris_flow_flush__(struct dpif_solaris *dpif)
+{
+	struct dpif_solaris_flow *solaris_flow, *next;
+
+	ovs_rwlock_wrlock(&dpif->flow_rwlock);
+	HMAP_FOR_EACH_SAFE(solaris_flow, next, node, &dpif->flows) {
+		(void) solaris_remove_flow(solaris_flow->physname,
+		    solaris_flow->flowname);
+		dpif_solaris_flow_remove(dpif, solaris_flow);
+	}
+	ovs_rwlock_unlock(&dpif->flow_rwlock);
+	return (0);
+}
+
+static int
+dpif_solaris_flow_flush(struct dpif *dpif_)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	return (dpif_solaris_flow_flush__(dpif));
+}
+
+struct dpif_solaris_flow_state {
+	struct odputil_keybuf keybuf;
+	struct odputil_keybuf maskbuf;
+};
+
+struct dpif_solaris_flow_ent {
+	struct hmap_node node;
+	char flowname[MAXUSERFLOWNAMELEN];
+	struct flow f;
+	struct flow m;
+	struct ofpbuf action;
+	struct dpif_flow_stats stats;
+};
+
+struct dpif_solaris_flow_iter {
+	struct hmap flows;
+	uint32_t bucket;
+	uint32_t offset;
+	int status;
+	struct ovs_mutex mutex;
+};
+
+static void
+dpif_solaris_flow_dump_state_init(void **statep)
+{
+	struct dpif_solaris_flow_state *state;
+
+	*statep = state = xmalloc(sizeof (*state));
+}
+
+static void
+dpif_solaris_flow_dump_state_uninit(void *state_)
+{
+	struct dpif_solaris_flow_state *state = state_;
+
+	free(state);
+}
+
+static void
+walk_flow(void *arg, const char *name, boolean_t is_default, struct flow *f,
+    struct flow *m, struct ofpbuf *action, uint64_t npackets, uint64_t nbytes,
+    uint64_t lastused)
+{
+	struct dpif_solaris_flow_iter *iter = arg;
+	struct dpif_solaris_flow_ent *flow;
+
+	VLOG_DBG("dpif_solaris_flow_dump_start walk flow %s", name);
+	ovs_assert(!is_default);
+	flow = xzalloc(sizeof (*flow));
+	strlcpy(flow->flowname, name, sizeof (flow->flowname));
+	bcopy(f, &flow->f, sizeof (*f));
+	bcopy(m, &flow->m, sizeof (*m));
+	flow->stats.n_packets = npackets;
+	flow->stats.n_bytes = nbytes;
+	flow->stats.used = lastused / 1000000;
+	ofpbuf_init(&flow->action, 0);
+	if (ofpbuf_size(action) != 0) {
+		ofpbuf_put(&flow->action, ofpbuf_data(action),
+		    ofpbuf_size(action));
+	}
+
+	hmap_insert(&iter->flows, &flow->node,
+	    hash_words((const uint32_t *)flow, sizeof (*flow) / 4, 0));
+	ofpbuf_reinit(action, 0);
+}
+
+static int
+dpif_solaris_flow_dump_start(const struct dpif *dpif OVS_UNUSED, void **iterp)
+{
+	struct dpif_solaris_flow_iter *iter;
+	struct ofpbuf action;
+
+	*iterp = iter = xmalloc(sizeof (*iter));
+	iter->bucket = 0;
+	iter->offset = 0;
+	iter->status = 0;
+	hmap_init(&iter->flows);
+	ovs_mutex_init(&iter->mutex);
+	ovs_mutex_lock(&iter->mutex);
+	ofpbuf_init(&action, 0);
+	(void) solaris_flow_walk(iter, &action, B_TRUE, walk_flow);
+	ofpbuf_uninit(&action);
+	ovs_mutex_unlock(&iter->mutex);
+	return (0);
+}
+
+static int
+dpif_solaris_flow_dump_next(const struct dpif *dpif_ OVS_UNUSED, void *iter_,
+    void *state_, const struct nlattr **key, size_t *key_len,
+    const struct nlattr **mask, size_t *mask_len,
+    const struct nlattr **actions, size_t *actions_len,
+    const struct dpif_flow_stats **stats)
+{
+	struct dpif_solaris_flow_iter *iter = iter_;
+	struct dpif_solaris_flow_state *state = state_;
+	struct dpif_solaris_flow_ent *flow;
+	int error;
+
+	ovs_mutex_lock(&iter->mutex);
+	error = iter->status;
+	if (!error) {
+		struct hmap_node *node;
+
+		node = hmap_at_position(&iter->flows, &iter->bucket,
+		    &iter->offset);
+		if (node) {
+			flow = CONTAINER_OF(node,
+			    struct dpif_solaris_flow_ent, node);
+			VLOG_DBG("dpif_solaris_flow_dump_next %s",
+			    flow->flowname);
+		} else {
+			iter->status = error = EOF;
+		}
+	}
+	ovs_mutex_unlock(&iter->mutex);
+	if (error) {
+		return (error);
+	}
+
+	if (key) {
+		struct ofpbuf buf;
+		struct ds s;
+
+		ofpbuf_use_stack(&buf, &state->keybuf, sizeof (state->keybuf));
+		odp_flow_key_from_flow(&buf, &flow->f, &flow->m,
+		    flow->f.in_port.odp_port);
+
+		*key = ofpbuf_data(&buf);
+		*key_len = ofpbuf_size(&buf);
+
+		ds_init(&s);
+		odp_flow_format(*key, *key_len, NULL, 0, NULL, &s, true);
+
+		VLOG_DBG("dpif_solaris_flow_dump_next %s key %s",
+		    flow->flowname, ds_cstr(&s));
+		ds_destroy(&s);
+	}
+
+	if (key && mask) {
+		struct ofpbuf buf;
+		struct ds s;
+
+		ofpbuf_use_stack(&buf, &state->maskbuf,
+		    sizeof (state->maskbuf));
+		odp_flow_key_from_mask(&buf, &flow->m, &flow->m,
+		    flow->f.in_port.odp_port, SIZE_MAX);
+
+		*mask = ofpbuf_data(&buf);
+		*mask_len = ofpbuf_size(&buf);
+
+		ds_init(&s);
+		odp_flow_format(*key, *key_len, *mask, *mask_len, NULL, &s,
+		    true);
+		VLOG_DBG("dpif_solaris_flow_dump_next %s mask %s "
+		    "mask_key_len %"PRIuSIZE, flow->flowname,
+		    ds_cstr(&s), *mask_len);
+		ds_destroy(&s);
+	}
+
+	if (actions || stats) {
+		if (actions) {
+			*actions = ofpbuf_data(&flow->action);
+			*actions_len = ofpbuf_size(&flow->action);
+		}
+
+		if (stats) {
+			*stats = &flow->stats;
+		}
+	}
+	VLOG_DBG("dpif_solaris_flow_dump_next %s succeed",
+	    flow->flowname);
+
+	return (0);
+}
+
+static int
+dpif_solaris_flow_dump_done(const struct dpif *dpif OVS_UNUSED, void *iter_)
+{
+	struct dpif_solaris_flow_iter *iter = iter_;
+	struct dpif_solaris_flow_ent *flow, *next;
+
+	ovs_mutex_lock(&iter->mutex);
+	HMAP_FOR_EACH_SAFE(flow, next, node, &iter->flows) {
+		ofpbuf_uninit(&flow->action);
+		hmap_remove(&iter->flows, &flow->node);
+		free(flow);
+	}
+	ovs_mutex_unlock(&iter->mutex);
+	hmap_destroy(&iter->flows);
+	ovs_mutex_destroy(&iter->mutex);
+	free(iter);
+	return (0);
+}
+
+static int
+dpif_solaris_port_output(struct dpif_solaris *dpif, odp_port_t port_no,
+    struct ofpbuf *packet, bool may_steal)
+{
+	struct dpif_solaris_port	*port;
+	struct msghdr			msghdr;
+	struct iovec 			iov;
+	struct cmsghdr			*cmsg;
+	struct tpacket_auxdata		auxdata;
+	char coutmsg[sizeof (auxdata) + sizeof (*cmsg) + _CMSG_HDR_ALIGNMENT];
+	size_t		nwritten;
+	ssize_t		nbytes = 0;
+	const char	*buf = ofpbuf_data(packet);
+	size_t		buflen = ofpbuf_size(packet);
+	int error, fd = -1;
+
+	VLOG_DBG("dpif_solaris_port_output %d", port_no);
+
+	ovs_rwlock_rdlock(&dpif->port_rwlock);
+	error = dpif_solaris_get_uplink_port(dpif, &port);
+	if (error == 0 && port->is_uplink)
+		fd = port->xfd;
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+	if (fd == -1) {
+		if (may_steal) {
+			ofpbuf_delete(packet);
+		}
+		return (error);
+	}
+
+	bzero(&msghdr, sizeof (msghdr));
+	msghdr.msg_iov = &iov;
+	msghdr.msg_iovlen = 1;
+	msghdr.msg_control = (void *)_CMSG_HDR_ALIGN(coutmsg);
+	msghdr.msg_controllen = sizeof (*cmsg) + sizeof (auxdata);
+
+	iov.iov_len = buflen;
+	iov.iov_base = (char *)buf;
+
+	cmsg = CMSG_FIRSTHDR(&msghdr);
+	cmsg->cmsg_level = SOL_PACKET;
+	cmsg->cmsg_type = PACKET_AUXDATA;
+	cmsg->cmsg_len = CMSG_DATA(cmsg) + sizeof (auxdata) - (uchar_t *)cmsg;
+
+	memcpy(&auxdata, CMSG_DATA(cmsg), sizeof (auxdata));
+	auxdata.tp_of_port = port_no;
+	memcpy(CMSG_DATA(cmsg), &auxdata, sizeof (auxdata));
+	for (nwritten = 0; nwritten < buflen; nwritten += nbytes) {
+		nbytes = sendmsg(fd, &msghdr, 0);
+		if (nbytes == -1) {
+			if (errno != EAGAIN) {
+				error = errno;
+				break;
+			}
+			nbytes = 0;
+		} else if (nbytes == 0) {
+			error = EIO;
+			break;
+		}
+		iov.iov_len = buflen - (nwritten + nbytes);
+		iov.iov_base = (char *)buf + (nwritten + nbytes);
+	}
+
+	VLOG_DBG("dpif_solaris_port_output len %"PRIuSIZE" to %d %s",
+	    buflen, port_no, error == 0 ? "succeed" :
+	    ovs_strerror(error));
+	ovs_assert(error != 0 || nwritten == buflen);
+
+	return (0);
+}
+
+/*
+ * Return the port number of the uplink port; 0 if there is no uplink port
+ * Assumes caller holds port_rwlock.
+ */
+static odp_port_t
+dp_solaris_uplink_port(struct dpif_solaris *dpif)
+{
+	odp_port_t			port_no = 0;
+	struct dpif_solaris_port	*port;
+
+	ovs_rwlock_rdlock(&dpif->port_rwlock);
+	HMAP_FOR_EACH(port, node, &dpif->ports) {
+		if (port->is_uplink) {
+			port_no = port->port_no;
+			break;
+		}
+	}
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+
+	return (port_no);
+}
+
+static void
+dp_solaris_execute_cb(void *aux_, struct ofpbuf *packet,
+    struct pkt_metadata *md OVS_UNUSED, const struct nlattr *a, bool may_steal)
+{
+	struct dpif_solaris *dpif = aux_;
+	int type = nl_attr_type(a);
+
+	VLOG_DBG("dp_solaris_execute_cb type %d", type);
+
+	switch ((enum ovs_action_attr)type) {
+	case OVS_ACTION_ATTR_OUTPUT: {
+		odp_port_t port_no = u32_to_odp(nl_attr_get_u32(a));
+
+		VLOG_DBG("dp_solaris_execute_cb OVS_ACTION_ATTR_OUTPUT "
+		    "%d", port_no);
+
+		(void) dpif_solaris_port_output(dpif, port_no, packet,
+		    may_steal);
+		break;
+	}
+
+	/*
+	 * We'll send the packet back to the kernel and it'll be classified
+	 * and we should get it back. XXX Check if this could cause an
+	 * infinite loop. This is not terribly effective, but if we use
+	 * sockets, it will still get to the kernel before coming back.
+	 * Need a short-cut. Note we don't care about any userland data
+	 * since we will get it when the packets comes back from the
+	 * kernel. Also, currently we always send using the uplink in
+	 * dpif_solaris_port_output so we don't have to dp_solaris_uplink_port.
+	 */
+	case OVS_ACTION_ATTR_USERSPACE:	{	/* controller */
+		odp_port_t port_no = dp_solaris_uplink_port(dpif);
+
+		VLOG_DBG("dp_solaris_execute_cb OVS_ACTION_ATTR_USERSPACE");
+		if (port_no == 0) {
+			VLOG_DBG("dp_solaris_execute_cb "
+			"OVS_ACTION_ATTR_USERSPACE: no uplink port.");
+			if (may_steal) {
+				ofpbuf_delete(packet);
+			}
+			break;
+		}
+		VLOG_DBG("dp_solaris_execute_cb OVS_ACTION_ATTR_USERSPACE "
+		    "%d", port_no);
+		(void) dpif_solaris_port_output(dpif, port_no, packet,
+		    may_steal);
+		break;
+	}
+	case OVS_ACTION_ATTR_HASH:		/* for bonding */
+	case OVS_ACTION_ATTR_RECIRC:		/* for bonding */
+	case OVS_ACTION_ATTR_PUSH_VLAN:
+	case OVS_ACTION_ATTR_POP_VLAN:
+	case OVS_ACTION_ATTR_PUSH_MPLS:
+	case OVS_ACTION_ATTR_POP_MPLS:
+	case OVS_ACTION_ATTR_SET:
+	case OVS_ACTION_ATTR_SAMPLE:
+	case OVS_ACTION_ATTR_UNSPEC:
+	case __OVS_ACTION_ATTR_MAX:
+		VLOG_DBG("dp_solaris_execute_cb type %d", type);
+		OVS_NOT_REACHED();
+	}
+}
+
+static int
+dpif_solaris_execute(struct dpif *dpif_, struct dpif_execute *execute)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	struct pkt_metadata *md = &execute->md;
+
+	if (ofpbuf_size(execute->packet) < ETH_HEADER_LEN ||
+	    ofpbuf_size(execute->packet) > UINT16_MAX) {
+		VLOG_ERR("dpif_solaris_execute invalid size %d",
+		    ofpbuf_size(execute->packet));
+		return (EINVAL);
+	}
+
+	odp_execute_actions(dpif, execute->packet, false, md, execute->actions,
+	    execute->actions_len, dp_solaris_execute_cb);
+	return (0);
+}
+
+static void
+dpif_solaris_destroy_channels(struct dpif_solaris *dpif)
+    OVS_REQ_WRLOCK(dpif->upcall_lock)
+{
+	struct dpif_solaris_port *port;
+
+	if (!dpif->recv_set)
+		return;
+
+	ovs_rwlock_wrlock(&dpif->port_rwlock);
+	HMAP_FOR_EACH(port, node, &dpif->ports) {
+		if (port->upcall_fd != -1) {
+			(void) close(port->upcall_fd);
+			port->upcall_fd = -1;
+		}
+	}
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+	(void) close(dpif->event_rfd);
+	(void) close(dpif->event_wfd);
+	dpif->event_rfd = dpif->event_wfd = -1;
+	dpif->recv_set = false;
+}
+
+static int
+dpif_solaris_refresh_port_channel(struct dpif_solaris *dpif,
+    struct dpif_solaris_port *port, const char *physname, boolean_t notify)
+    OVS_REQ_WRLOCK(dpif->port_rwlock)
+{
+	int fd, flags, onoff = 1;
+	struct sockaddr_ll sll;
+	datalink_id_t linkid;
+	mac_of_ports_t mofport;
+	dladm_status_t status;
+	int error;
+
+	if (!dpif->recv_set || (port->vtype != OVS_VPORT_TYPE_NETDEV &&
+	    port->vtype != OVS_VPORT_TYPE_VXLAN))
+		return (0);
+
+	VLOG_DBG("dpif_solaris_refresh_port_channel on %s port_no: %d%s",
+	    port->linkname, port->port_no, notify ? " notify" : "");
+
+	fd = socket(PF_PACKET, SOCK_RAW, ETH_P_ALL);
+	if (fd == -1) {
+		VLOG_ERR("dpif_solaris_refresh_port_channel %s failed to "
+		    "create socket: %s", port->linkname, ovs_strerror(errno));
+		return (errno);
+	}
+
+	status = dladm_name2info(dpif->dh, physname, &linkid,
+	    NULL, NULL, NULL);
+	error = solaris_dladm_status2error(status);
+	if (error != 0) {
+		VLOG_ERR("dpif_solaris_refresh_port_channel %s failed to get "
+		    "linkid: %s", port->linkname, ovs_strerror(error));
+		(void) close(fd);
+		return (error);
+	}
+
+	(void) memset(&sll, 0, sizeof (sll));
+	sll.sll_family = AF_PACKET;
+	sll.sll_ifindex = linkid;
+	sll.sll_protocol = ETH_P_ALL;
+	if (bind(fd, (struct sockaddr *)&sll, sizeof (sll)) == -1) {
+		error = errno;
+		VLOG_ERR("dpif_solaris_refresh_port_channel %s failed to bind: "
+		    "%s", port->name, ovs_strerror(error));
+		(void) close(fd);
+		return (error);
+	}
+
+	mofport.mop_port = port->pf_port_no;
+	mofport.mop_sport = port->port_no;
+	if (setsockopt(fd, SOL_PACKET,
+	    PACKET_ADD_OF_DEFFLOW, &mofport, sizeof (mac_of_ports_t)) != 0) {
+		error = errno;
+		VLOG_ERR("dpif_solaris_refresh_port_channel %s failed to set "
+		    "PACKET_ADD_OF_DEFFLOW: %s", port->name,
+		    ovs_strerror(error));
+		(void) close(fd);
+		return (error);
+	}
+
+	if (setsockopt(fd, SOL_PACKET,
+	    PACKET_AUXDATA, &onoff, sizeof (onoff)) != 0) {
+		error = errno;
+		VLOG_ERR("dpif_solaris_refresh_port_channel %s failed to set "
+		    "PACKET_AUXDATAQ: %s", port->name,
+		    ovs_strerror(error));
+		(void) setsockopt(fd, SOL_PACKET, PACKET_REM_OF_DEFFLOW,
+		    NULL, 0);
+		(void) close(fd);
+		return (error);
+	}
+
+	/* must not block */
+	if (((flags = fcntl(fd, F_GETFL, 0)) == -1) ||
+	    (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)) {
+		error = errno;
+		VLOG_ERR("dpif_solaris_refresh_port_channel %s failed to set "
+		    "non-block: %s", port->name, ovs_strerror(error));
+		(void) setsockopt(fd, SOL_PACKET, PACKET_REM_OF_DEFFLOW,
+		    NULL, 0);
+		(void) close(fd);
+		return (error);
+	}
+
+	port->upcall_fd = fd;
+
+	VLOG_DBG("dpif_solaris_refresh_port_channel %s upcall_fd=%d",
+	    port->name, port->upcall_fd);
+
+	/* Inform the event_rfd that the port->upcall_fd has been changed */
+	if (notify && write(dpif->event_wfd, (char *)(&port->port_no),
+	    sizeof (odp_port_t)) < 0) {
+		VLOG_ERR("dpif_solaris_refresh_port_channel %s event "
+		    "notify failed: %s", port->name, ovs_strerror(errno));
+	}
+
+	return (0);
+}
+
+/*
+ * Synchronizes 'channels' in 'dpif->handlers'  with the set of vports
+ * currently in 'dpif' in the kernel, by adding a new set of channels for
+ * any kernel vport that lacks one and deleting any channels that have no
+ * backing kernel vports.
+ */
+static int
+dpif_solaris_refresh_channels(struct dpif_solaris *dpif, uint32_t n_handlers)
+    OVS_REQ_WRLOCK(dpif->upcall_lock)
+{
+	struct dpif_solaris_port *port;
+	int error, flags;
+	odp_port_t port_no = 0;
+	int fds[2];
+
+	VLOG_DBG("dpif_solaris_refresh_channels %d", n_handlers);
+	dpif_solaris_destroy_channels(dpif);
+
+	/*
+	 * Setup the event pipe to monitor the port changes, so that
+	 * port_channel_fd is able to be refreshed into the pollfds set.
+	 */
+	if ((pipe(fds)) < 0) {
+		error = errno;
+		return (error);
+	}
+	/* must not block */
+	if (((flags = fcntl(fds[0], F_GETFL, 0)) == -1) ||
+	    (fcntl(fds[0], F_SETFL, flags | O_NONBLOCK) == -1)) {
+		error = errno;
+		(void) close(fds[0]);
+		(void) close(fds[1]);
+		return (error);
+	}
+	if (((flags = fcntl(fds[1], F_GETFL, 0)) == -1) ||
+	    (fcntl(fds[1], F_SETFL, flags | O_NONBLOCK) == -1)) {
+		error = errno;
+		(void) close(fds[0]);
+		(void) close(fds[1]);
+		return (error);
+	}
+
+	dpif->recv_set = true;
+	dpif->event_rfd = fds[0];
+	dpif->event_wfd = fds[1];
+
+	ovs_rwlock_wrlock(&dpif->port_rwlock);
+	HMAP_FOR_EACH(port, node, &dpif->ports) {
+		error = dpif_solaris_refresh_port_channel(dpif, port,
+		    port->bridge->physname, false);
+		if (error != 0) {
+			ovs_rwlock_unlock(&dpif->port_rwlock);
+			dpif_solaris_destroy_channels(dpif);
+			return (error);
+		}
+	}
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+
+	/* Inform the event_rfd that all ports' upcall_fd has been changed */
+	if (write(dpif->event_wfd, (char *)&port_no, sizeof (odp_port_t)) < 0) {
+		VLOG_ERR("dpif_solaris_refresh_channels event notify"
+		    "failed: %s", ovs_strerror(errno));
+	}
+	return (0);
+}
+
+static int
+dpif_solaris_recv_set_(struct dpif_solaris *dpif, bool enable)
+{
+	if (dpif->recv_set == enable)
+		return (0);
+
+	if (!enable) {
+		dpif_solaris_destroy_channels(dpif);
+		return (0);
+	} else {
+		return (dpif_solaris_refresh_channels(dpif, 1));
+	}
+}
+
+static int
+dpif_solaris_recv_set(struct dpif *dpif_, bool enable)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	int error = 0;
+
+	VLOG_DBG("dpif_solaris_recv_set %s", enable ? "true" : "false");
+
+	ovs_rwlock_wrlock(&dpif->upcall_lock);
+	dpif_solaris_recv_set_(dpif, enable);
+	ovs_rwlock_unlock(&dpif->upcall_lock);
+	return (error);
+}
+
+/*
+ * For Solaris we will use the queue_id as the priority. The queue id
+ * will be used to get the QoS information and used to configure
+ * the bandwidth and priority
+ */
+static int
+dpif_solaris_queue_to_priority(const struct dpif *dpif OVS_UNUSED,
+    uint32_t queue_id, uint32_t *priority)
+{
+	*priority = queue_id;
+	return (0);
+}
+
+static int
+dpif_solaris_parse_pkt(struct dpif_solaris *dpif OVS_UNUSED,
+    struct ofpbuf *packet, struct pkt_metadata *md, uint16_t action_type,
+    struct dpif_upcall *upcall, struct ofpbuf *buf)
+{
+	struct flow flow;
+	void *data;
+	size_t buf_size;
+	const struct nlattr *userdata = NULL;
+
+	VLOG_DBG("dpif_solaris_parse_pkt");
+
+	if ((buf_size = ofpbuf_size(packet)) < ETH_HEADER_LEN) {
+		VLOG_ERR("dpif_solaris_parse_pkt bufsize too small %"PRIuSIZE,
+		    buf_size);
+		return (EINVAL);
+	}
+
+	flow_extract(packet, md, &flow);
+	upcall->type = (action_type == FLOW_ACTION_MISSED_PKT) ? DPIF_UC_MISS :
+	    DPIF_UC_ACTION;
+
+	/* Allocate buffer big enough for everything. */
+	buf_size = ODPUTIL_FLOW_KEY_BYTES;
+	if (action_type == FLOW_ACTION_OF_CONTROLLER) {
+		struct ofpbuf abuf;
+		char slow_path_buf[128];
+		const struct nlattr *a;
+
+		VLOG_DBG("dpif_solaris_parse_p:: FLOW_ACTION_OF_CONTROLLER!!");
+		ofpbuf_use_stack(&abuf, slow_path_buf, sizeof (slow_path_buf));
+		slowpath_to_actions(SLOW_CONTROLLER, &abuf);
+		a = nl_attr_find(&abuf, 0, OVS_ACTION_ATTR_USERSPACE);
+		if (a != NULL) {
+			userdata = nl_attr_find_nested(a,
+			    OVS_USERSPACE_ATTR_USERDATA);
+			if (userdata != NULL)
+				buf_size += NLA_ALIGN(userdata->nla_len);
+		}
+	}
+	buf_size += ofpbuf_size(packet);
+	ofpbuf_init(buf, buf_size);
+
+	VLOG_DBG("PARSE flow in_port %d", flow.in_port.odp_port);
+	odp_flow_key_from_flow(buf, &flow, NULL, flow.in_port.odp_port);
+	upcall->key = ofpbuf_data(buf);
+	upcall->key_len = ofpbuf_size(buf);
+
+	if (userdata != NULL) {
+		upcall->userdata = ofpbuf_put(buf, userdata,
+		    NLA_ALIGN(userdata->nla_len));
+	}
+
+	data = ofpbuf_put(buf, ofpbuf_data(packet), ofpbuf_size(packet));
+	ofpbuf_use_stub(&upcall->packet, data, ofpbuf_size(packet));
+	ofpbuf_set_size(&upcall->packet, ofpbuf_size(packet));
+	return (0);
+}
+
+static int
+dpif_solaris_recv__(struct dpif_solaris *dpif, uint32_t handler_id OVS_UNUSED,
+    struct dpif_upcall *upcall, struct ofpbuf *buf)
+    OVS_REQ_WRLOCK(dpif->upcall_lock)
+{
+	struct iovec iov;
+	struct msghdr msg;
+	struct cmsghdr *cmsg;
+	char cmsg_buf[sizeof (*cmsg) + sizeof (struct tpacket_auxdata) +
+	    _CMSG_HDR_ALIGNMENT];
+	uint8_t pktbuf[65536];
+	int pktlen;
+	struct dpif_solaris_port *port;
+	odp_port_t port_no;
+	uint16_t action_type;
+	int error;
+
+	if (!dpif->recv_set) {
+		return (EAGAIN);
+	}
+
+	(void) read(dpif->event_rfd, (char *)&port_no,
+	    sizeof (odp_port_t));
+
+	ovs_rwlock_wrlock(&dpif->port_rwlock);
+	HMAP_FOR_EACH(port, node, &dpif->ports) {
+		if (port->upcall_fd == -1)
+			continue;
+
+		msg.msg_name = NULL;
+		msg.msg_namelen = 0;
+		msg.msg_iov = &iov;
+		msg.msg_iovlen = 1;
+		msg.msg_control = (void *)_CMSG_HDR_ALIGN(cmsg_buf);
+		msg.msg_controllen = sizeof (cmsg_buf) -
+		    (_CMSG_HDR_ALIGN(cmsg_buf) - (uintptr_t)cmsg_buf);
+		msg.msg_flags = 0;
+
+		iov.iov_len = sizeof (pktbuf);
+		iov.iov_base = pktbuf;
+		pktlen = recvmsg(port->upcall_fd, &msg, MSG_TRUNC);
+		if (pktlen > 0) {
+			struct pkt_metadata	md;
+			mactun_info_t		*tuninfop;
+			struct flow_tnl		*tun;
+			struct ofpbuf		pkt;
+
+			action_type = FLOW_ACTION_OF_MAX;
+			for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
+			    cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+				struct tpacket_auxdata	aux;
+
+				if (cmsg->cmsg_len <
+				    CMSG_LEN(sizeof (struct tpacket_auxdata)) ||
+				    cmsg->cmsg_level != SOL_PACKET ||
+				    cmsg->cmsg_type != PACKET_AUXDATA)
+					continue;
+
+				memcpy(&aux, CMSG_DATA(cmsg), sizeof (aux));
+				action_type = aux.tp_of_action;
+				port_no = aux.tp_of_port;
+				tuninfop = &aux.tp_tun_info;
+				break;
+			}
+
+			if (action_type != FLOW_ACTION_MISSED_PKT &&
+			    action_type != FLOW_ACTION_OF_CONTROLLER) {
+				VLOG_ERR("dpif_solaris_recv__ on port %s len %d"
+				    " but with unknown auxdata type %d",
+				    port->name, pktlen, action_type);
+				continue;
+			}
+
+			VLOG_DBG("dpif_solaris_recv__ on port %s len %d "
+			    "type %s (src_port = %u)", port->name, pktlen,
+			    action_type == FLOW_ACTION_MISSED_PKT ?
+			    "miss_pkt" : "fwd_controller", port_no);
+
+			md = PKT_METADATA_INITIALIZER(port_no);
+			if (tuninfop->mti_dst._S6_un._S6_u32[3] != 0) {
+				tun = &md.tunnel;
+				tun->tun_id = htonll(tuninfop->mti_id);
+				tun->flags |= FLOW_TNL_F_KEY;
+				tun->ip_src =
+				    tuninfop->mti_src._S6_un._S6_u32[3];
+				tun->ip_dst =
+				    tuninfop->mti_dst._S6_un._S6_u32[3];
+				tun->ip_tos = tuninfop->mti_tos;
+				tun->ip_ttl = tuninfop->mti_ttl;
+				/* TBD? */
+				tun->flags |= FLOW_TNL_F_DONT_FRAGMENT;
+			}
+			ofpbuf_use_const(&pkt, pktbuf, pktlen);
+			error = dpif_solaris_parse_pkt(dpif, &pkt, &md,
+			    action_type, upcall, buf);
+			if (error != 0) {
+				VLOG_ERR("dpif_solaris_recv__ parse_pkt on "
+				    "port %s action %s failed %d", port->name,
+				    action_type == FLOW_ACTION_MISSED_PKT ?
+				    "miss_pkt" : "fwd_to_controller",
+				    error);
+			} else {
+				ovs_rwlock_unlock(&dpif->port_rwlock);
+				return (0);
+			}
+		}
+	}
+
+	ovs_rwlock_unlock(&dpif->port_rwlock);
+	return (EAGAIN);
+}
+
+static int
+dpif_solaris_recv(struct dpif *dpif_, uint32_t handler_id,
+    struct dpif_upcall *upcall, struct ofpbuf *buf)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	int error;
+
+	ovs_rwlock_rdlock(&dpif->upcall_lock);
+	error = dpif_solaris_recv__(dpif, handler_id, upcall, buf);
+	ovs_rwlock_unlock(&dpif->upcall_lock);
+
+	return (error);
+}
+
+static void
+dpif_solaris_recv_wait(struct dpif *dpif_, uint32_t handler_id OVS_UNUSED)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+	struct dpif_solaris_port *port;
+
+	ovs_rwlock_rdlock(&dpif->upcall_lock);
+	if (dpif->recv_set) {
+		poll_fd_wait(dpif->event_rfd, POLLIN);
+		ovs_rwlock_wrlock(&dpif->port_rwlock);
+		HMAP_FOR_EACH(port, node, &dpif->ports) {
+			if (port->upcall_fd != -1) {
+				poll_fd_wait(port->upcall_fd, POLLIN);
+			}
+		}
+		ovs_rwlock_unlock(&dpif->port_rwlock);
+	}
+	ovs_rwlock_unlock(&dpif->upcall_lock);
+}
+
+static void
+dpif_solaris_recv_purge__(struct dpif_solaris *dpif)
+    OVS_REQ_WRLOCK(dpif->upcall_lock)
+{
+	struct dpif_solaris_port *port;
+
+	if (dpif->recv_set) {
+		drain_rcvbuf(dpif->event_rfd);
+		ovs_rwlock_wrlock(&dpif->port_rwlock);
+		HMAP_FOR_EACH(port, node, &dpif->ports) {
+			drain_rcvbuf(port->upcall_fd);
+		}
+		ovs_rwlock_unlock(&dpif->port_rwlock);
+	}
+}
+
+static void
+dpif_solaris_recv_purge(struct dpif *dpif_)
+{
+	struct dpif_solaris *dpif = dpif_solaris_cast(dpif_);
+
+	VLOG_DBG("dpif_solaris_recv_purge");
+	ovs_rwlock_wrlock(&dpif->upcall_lock);
+	dpif_solaris_recv_purge__(dpif);
+	ovs_rwlock_unlock(&dpif->upcall_lock);
+}
+
+static int
+dpif_solaris_handlers_set(struct dpif *dpif OVS_UNUSED,
+    uint32_t n_handlers OVS_UNUSED)
+{
+	return (0);
+}
+
+const struct dpif_class dpif_solaris_class = {
+	"system",
+	dpif_solaris_enumerate,
+	NULL,					/* port_open_type */
+	dpif_solaris_open,
+	dpif_solaris_close,
+	dpif_solaris_destroy,
+	NULL,					/* run */
+	NULL,					/* wait */
+	dpif_solaris_get_stats,
+	dpif_solaris_port_add,
+	dpif_solaris_port_del,
+	dpif_solaris_port_query_by_number,
+	dpif_solaris_port_query_by_name,
+	NULL, 					/* port_get_pid */
+	dpif_solaris_port_dump_start,
+	dpif_solaris_port_dump_next,
+	dpif_solaris_port_dump_done,
+	dpif_solaris_port_poll,
+	dpif_solaris_port_poll_wait,
+	dpif_solaris_flow_get,
+	dpif_solaris_flow_put,
+	dpif_solaris_flow_del,
+	dpif_solaris_flow_flush,
+	dpif_solaris_flow_dump_state_init,
+	dpif_solaris_flow_dump_start,
+	dpif_solaris_flow_dump_next,
+	NULL,				/* flow_dump_next_may_destroy_keys */
+	dpif_solaris_flow_dump_done,
+	dpif_solaris_flow_dump_state_uninit,
+	dpif_solaris_execute,
+	NULL,					/* operate */
+	dpif_solaris_recv_set,
+	dpif_solaris_handlers_set,		/* handlers_set */
+	dpif_solaris_queue_to_priority,
+	dpif_solaris_recv,
+	dpif_solaris_recv_wait,
+	dpif_solaris_recv_purge,
+	dpif_solaris_configure_bridge_port
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/lib/dpif-solaris.h	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ */
+
+/*
+ * Copyright (c) 2010, 2011 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef	DPIF_SOLARIS_H
+#define	DPIF_SOLARIS_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "openvswitch/types.h"
+#include "smap.h"
+#include "util-solaris.h"
+
+#define	PORT_PF_PACKET_UPLINK	1
+struct dpif_flow_stats;
+
+int dpif_solaris_get_priority_details(void *, odp_port_t, uint_t,
+    struct smap *);
+void dpif_log(int, const char *, ...);
+#endif	/* DPIF_SOLARIS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/lib/netdev-solaris.c	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,2728 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ */
+
+/*
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netpacket/packet.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/sockio.h>
+#include <strings.h>
+
+#include <libdllink.h>
+#include <kstat2.h>
+
+#include "coverage.h"
+#include "dpif-netdev.h"
+#include "dynamic-string.h"
+#include "fatal-signal.h"
+#include "hash.h"
+#include "hmap.h"
+#include "netdev-provider.h"
+#include "netdev-vport.h"
+#include "ofpbuf.h"
+#include "openflow/openflow.h"
+#include "ovs-atomic.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "shash.h"
+#include "socket-util.h"
+#include "sset.h"
+#include "timer.h"
+#include "unaligned.h"
+#include "vlog.h"
+
+#include "netdev-solaris.h"
+#include "util-solaris.h"
+
+/*
+ * Enable vlog() for this module
+ */
+VLOG_DEFINE_THIS_MODULE(netdev_solaris);
+
+/*
+ * Per-network device state.
+ */
+struct netdev_solaris {
+	struct netdev up;
+
+	/* Protects all members below. */
+	struct ovs_mutex mutex;
+
+	/*
+	 * Bit mask of cached properties. These can be cached because
+	 * nothing besides vswitchd is altering the properties.
+	 */
+	unsigned int cache_valid;
+
+	/*
+	 * Network device features (e.g., speed, duplex, etc)
+	 */
+	enum netdev_features current;
+	enum netdev_features advertised;
+	enum netdev_features supported;
+	int get_features_error;
+
+	int ifindex;			/* interface index */
+	struct in_addr in4;		/* IPv4 address */
+	struct in_addr netmask;		/* IPv4 netmask */
+	struct in6_addr in6;		/* IPv6 address */
+	unsigned int plumbed;		 /* per-family plumbed */
+	unsigned int implicitly_plumbed; /* per-family implicitly plumbed */
+
+	char class[DLADM_PROP_VAL_MAX];	/* datalink class */
+	char brname[MAXLINKNAMELEN];	/* bridge name */
+	int mtu;			/* datalink MTU */
+	uint8_t etheraddr[ETH_ADDR_LEN]; /* MAC address */
+
+	struct tc *tc;			/* traffic control */
+};
+
+/*
+ * Bits indicating what network device configuration is cached.
+ * See cache_valid field above.
+ */
+enum {
+	VALID_IFINDEX		= 1 << 0,
+	VALID_ETHERADDR		= 1 << 1,
+	VALID_IN4		= 1 << 2,
+	VALID_IN6		= 1 << 3,
+	VALID_MTU		= 1 << 4,
+	VALID_LINKCLASS		= 1 << 5,
+	VALID_FEATURES		= 1 << 6
+};
+
+/*
+ * When network devices are constructed, the internal devices (i.e., bridges)
+ * are initially created over this etherstub and are moved when uplink ports
+ * are added to the bridge.
+ */
+#define	NETDEV_IMPL_ETHERSTUB	"ovs.etherstub0"
+
+/*
+ * Used to track state of IP plumbing on network devices.
+ */
+#define	SOLARIS_IPV4 0x01
+#define	SOLARIS_IPV6 0x02
+
+/*
+ * Cached socket descriptors.
+ */
+static int sock4;
+static int sock6;
+
+/*
+ * Cached kstat2_handle_t. Re-use unless an error causes it to be
+ * closed. In that case, cache the handle on the next open.
+ */
+static kstat2_handle_t	nd_khandle;
+static boolean_t	kstat2_handle_initialized = B_FALSE;
+static struct ovs_mutex	kstat_mutex = OVS_MUTEX_INITIALIZER;
+
+static int netdev_solaris_init(void);
+static struct netdev_solaris *netdev_solaris_cast(const struct netdev *);
+
+/*
+ * An instance of a traffic control class.
+ *
+ * Always associated with a particular network device.
+ *
+ * Each TC implementation subclasses this with whatever additional data
+ * it needs.
+ */
+struct tc {
+	const struct tc_ops *ops;
+
+	/*
+	 * Contains "struct tc_queues"s.
+	 * Read by generic TC layer.
+	 * Written only by TC implementation.
+	 */
+	struct hmap queues;
+};
+
+#define	TC_INITIALIZER(TC, OPS)	{ OPS, HMAP_INITIALIZER(&(TC)->queues) }
+
+/*
+ * One traffic control queue.
+ *
+ * Each TC implementation subclasses this with whatever additional
+ * data it needs.
+ */
+struct tc_queue {
+	struct hmap_node hmap_node;	/* In struct tc's "queues" hmap. */
+	unsigned int	queue_id;	/* OpenFlow queue ID. */
+	long long int	created;	/* Time queue was created, in msecs. */
+};
+
+/*
+ * A particular kind of traffic control.
+ *
+ * Each implementation generally maps to one particular Linux qdisc class.
+ *
+ * The functions below return 0 if successful or a positive errno value on
+ * failure, except where otherwise noted.  All of them must be provided,
+ * except where otherwise noted.
+ */
+struct tc_ops {
+	/*
+	 * Name used by kernel in the TCA_KIND attribute of tcmsg, e.g. "htb".
+	 * This is null for tc_ops_default since there is no appropriate
+	 * value.
+	 */
+	const char *linux_name;
+
+	/*
+	 * Name used in OVS database, e.g. "linux-htb".  Must be nonnull.
+	 */
+	const char *ovs_name;
+
+	/*
+	 * Number of supported OpenFlow queues, 0 for qdiscs that have no
+	 * queues.  The queues are numbered 0 through n_queues - 1.
+	 */
+	unsigned int n_queues;
+
+	/*
+	 * Called to install this TC class on 'netdev'.  The implementation
+	 * should make the calls required to set up 'netdev' with the right
+	 * qdisc and configure it according to 'details'.  The implementation
+	 * may assume that the current qdisc is the default; that is, there
+	 * is no need for it to delete the current qdisc before installing
+	 * itself.
+	 *
+	 * The contents of 'details' should be documented as valid for
+	 * 'ovs_name' in the "other_config" column in the "QoS" table in
+	 * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+	 *
+	 * This function must return 0 if and only if it sets 'netdev->tc'
+	 * to an initialized 'struct tc'.
+	 *
+	 * This function should always be nonnull.
+	 */
+	int (*tc_install)(struct netdev *netdev, const struct smap *details);
+
+	/*
+	 * Called when the netdev code determines that this TC class's qdisc
+	 * is installed on 'netdev', but we didn't install it ourselves and
+	 * so don't know any of the details.
+	 *
+	 * There is nothing for this function to do on Solaris.
+	 *
+	 * This function must return 0 if and only if it sets 'netdev->tc'
+	 * to an initialized 'struct tc'.
+	 */
+	int (*tc_load)(struct netdev *netdev, struct ofpbuf *nlmsg);
+
+	/*
+	 * Destroys the data structures allocated by the implementation as
+	 * part of 'tc'.  (This includes destroying 'tc->queues' by calling
+	 * tc_destroy(tc).
+	 *
+	 * This function may be null if 'tc' is trivial.
+	 */
+	void (*tc_destroy)(struct tc *tc);
+
+	/*
+	 * Retrieves details of 'netdev->tc' configuration into 'details'.
+	 *
+	 * The contents of 'details' should be documented as valid for
+	 * 'ovs_name' in the "other_config" column in the "QoS" table in
+	 * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+	 *
+	 * This function may be null if 'tc' is not configurable.
+	 */
+	int (*qdisc_get)(const struct netdev *netdev, struct smap *details);
+
+	/*
+	 * Reconfigures 'netdev->tc' according to 'details'.
+	 *
+	 * The contents of 'details' should be documented as valid for
+	 * 'ovs_name' in the "other_config" column in the "QoS" table in
+	 * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+	 *
+	 * This function may be null if 'tc' is not configurable.
+	 */
+	int (*qdisc_set)(struct netdev *, const struct smap *details);
+
+	/*
+	 * Retrieves details of 'queue' on 'netdev->tc' into 'details'.
+	 * 'queue' is one of the 'struct tc_queue's within
+	 * 'netdev->tc->queues'.
+	 *
+	 * The contents of 'details' should be documented as valid for
+	 * 'ovs_name' in the "other_config" column in the "Queue" table in
+	 * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+	 *
+	 * This function may be null if 'tc' does not have queues
+	 * ('n_queues' is 0).
+	 */
+	int (*class_get)(const struct netdev *netdev,
+	    const struct tc_queue *queue, struct smap *details);
+
+	/*
+	 * Configures or reconfigures 'queue_id' on 'netdev->tc' according to
+	 * 'details'. The caller ensures that 'queue_id' is less than
+	 * 'n_queues'.
+	 *
+	 * The contents of 'details' should be documented as valid for
+	 * 'ovs_name' in the "other_config" column in the "Queue" table in
+	 * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+	 *
+	 * This function may be null if 'tc' does not have queues or its
+	 * queues are not configurable.
+	 */
+	int (*class_set)(struct netdev *, unsigned int queue_id,
+	    const struct smap *details);
+
+	/*
+	 * Deletes 'queue' from 'netdev->tc'.  'queue' is one of the 'struct
+	 * tc_queue's within 'netdev->tc->queues'.
+	 *
+	 * This function may be null if 'tc' does not have queues or its queues
+	 * cannot be deleted.
+	 */
+	int (*class_delete)(struct netdev *, struct tc_queue *queue);
+
+	/*
+	 * Obtains stats for 'queue' from 'netdev->tc'.  'queue' is one of the
+	 * 'struct tc_queue's within 'netdev->tc->queues'.
+	 *
+	 * On success, initializes '*stats'.
+	 *
+	 * This function may be null if 'tc' does not have queues or if it
+	 * cannot report queue statistics.
+	 */
+	int (*class_get_stats)(const struct netdev *netdev,
+	    const struct tc_queue *queue, struct netdev_queue_stats *stats);
+
+	/*
+	 * Extracts queue stats from 'nlmsg', which is a response to a
+	 * RTM_GETTCLASS message, and passes them to 'cb' along with 'aux'.
+	 *
+	 * This function may be null if 'tc' does not have queues or if it
+	 * cannot report queue statistics.
+	 */
+	int (*class_dump_stats)(const struct netdev *netdev,
+	    const struct ofpbuf *nlmsg, netdev_dump_queue_stats_cb *cb,
+	    void *aux);
+};
+
+static void
+tc_init(struct tc *tc, const struct tc_ops *ops)
+{
+	tc->ops = ops;
+	hmap_init(&tc->queues);
+}
+
+static void
+tc_destroy(struct tc *tc)
+{
+	hmap_destroy(&tc->queues);
+}
+
+static const struct tc_ops tc_ops_htb;
+static const struct tc_ops tc_ops_default;
+
+static const struct tc_ops *const tcs[] = {
+	&tc_ops_htb,		/* Hierarchy token bucket */
+	&tc_ops_default,	/* Default qdisc */
+	NULL
+};
+
+static const struct tc_ops *
+tc_lookup_ovs_name(const char *name)
+{
+	const struct tc_ops *const *opsp;
+
+	for (opsp = tcs; *opsp != NULL; opsp++) {
+		const struct tc_ops *ops = *opsp;
+		if (strcmp(name, ops->ovs_name) == 0) {
+			return (ops);
+		}
+	}
+	return (NULL);
+}
+
+static struct tc_queue *
+tc_find_queue__(const struct netdev *netdev_, unsigned int queue_id,
+    size_t hash)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	struct tc_queue		*queue;
+
+	HMAP_FOR_EACH_IN_BUCKET(queue, hmap_node, hash, &netdev->tc->queues) {
+		if (queue->queue_id == queue_id) {
+			return (queue);
+		}
+	}
+	return (NULL);
+}
+
+static struct tc_queue *
+tc_find_queue(const struct netdev *netdev, unsigned int queue_id)
+{
+	return (tc_find_queue__(netdev, queue_id, hash_int(queue_id, 0)));
+}
+
+static int
+tc_delete_class(const struct netdev *netdev OVS_UNUSED,
+    unsigned int handle OVS_UNUSED)
+{
+	return (0);
+}
+
+static int
+tc_del_qdisc(struct netdev *netdev_)
+{
+	const char		*netdev_name = netdev_get_name(netdev_);
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+
+	VLOG_DBG("tc_del_qdisc device %s", netdev_name);
+	if (netdev->tc) {
+		if (netdev->tc->ops->tc_destroy) {
+			netdev->tc->ops->tc_destroy(netdev->tc);
+		}
+		netdev->tc = NULL;
+	}
+	return (0);
+}
+
+/*
+ * If 'netdev''s qdisc type and parameters are not yet known, queries the
+ * kernel to determine what they are.  Returns 0 if successful, otherwise a
+ * positive errno value.
+ */
+static int
+tc_query_qdisc(const struct netdev *netdev_)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const struct tc_ops	*ops;
+	int			load_error;
+	int			error;
+
+	if (netdev->tc) {
+		return (0);
+	}
+
+	/*
+	 * Either it's a built-in qdisc, or it's a qdisc set up by some
+	 * other entity that doesn't have a handle 1:0.  We will assume
+	 * that it's the system default qdisc.
+	 */
+	ops = &tc_ops_default;
+	error = 0;
+
+	/* Instantiate it. */
+	load_error = ops->tc_load(CONST_CAST(struct netdev *, netdev_), NULL);
+	ovs_assert((load_error == 0) == (netdev->tc != NULL));
+
+	return (error ? error : load_error);
+}
+
+static bool
+is_netdev_solaris_class(const struct netdev_class *netdev_class)
+{
+	return (netdev_class->init == netdev_solaris_init);
+}
+
+static struct netdev_solaris *
+netdev_solaris_cast(const struct netdev *netdev)
+{
+	ovs_assert(is_netdev_solaris_class(netdev_get_class(netdev)));
+
+	return (CONTAINER_OF(netdev, struct netdev_solaris, up));
+}
+
+static int
+netdev_solaris_plumb(const struct netdev *netdev_, sa_family_t af)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	char			*astring;
+	int			sock;
+	int			proto;
+	int			error;
+
+	ovs_assert(af == AF_INET || af == AF_INET6);
+
+	if (af == AF_INET) {
+		sock = sock4;
+		astring = "IPv4";
+		proto = SOLARIS_IPV4;
+	} else {
+		sock = sock6;
+		astring = "IPv6";
+		proto = SOLARIS_IPV6;
+	}
+
+	if ((netdev->plumbed & proto) != 0)
+		return (0);
+
+	error = solaris_plumb_if(sock, netdev_name, af);
+	if (error != 0) {
+		VLOG_ERR("%s device could not be plumbed", netdev_name);
+		return (error);
+	}
+	VLOG_DBG("%s device plumbed for %s", netdev_name, astring);
+
+	netdev->implicitly_plumbed |= proto;
+	netdev->plumbed |= proto;
+	return (0);
+}
+
+static int
+netdev_solaris_unplumb(const struct netdev *netdev_, sa_family_t af)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	char			*astring;
+	int			sock;
+	int			proto;
+	int			error;
+
+	ovs_assert(af == AF_INET || af == AF_INET6);
+
+	if (af == AF_INET) {
+		sock = sock4;
+		astring = "IPv4";
+		proto = SOLARIS_IPV4;
+	} else {
+		sock = sock6;
+		astring = "IPv6";
+		proto = SOLARIS_IPV6;
+	}
+
+	if ((netdev->implicitly_plumbed & proto) == 0)
+		return (0);
+
+	error = solaris_unplumb_if(sock, netdev_name, af);
+	if (error != 0) {
+		VLOG_ERR("%s device could not be unplumbed", netdev_name);
+		return (error);
+	}
+	VLOG_ERR("%s device unplumbed for %s", netdev_name, astring);
+	netdev->implicitly_plumbed &= ~proto;
+	netdev->plumbed &= ~proto;
+
+	return (0);
+}
+
+struct solaris_netdev_sdmap {
+	const char *sns_val;
+	enum netdev_features sns_feature;
+};
+
+static int
+netdev_solaris_chk_speed_duplex(const char *netdev_name,
+    const char *field_name, enum netdev_features *features)
+{
+	struct solaris_netdev_sdmap map [] = {
+		{"10G-f", NETDEV_F_10GB_FD},
+		{"1G-f", NETDEV_F_1GB_FD},
+		{"1G-h", NETDEV_F_1GB_HD},
+		{"100M-f", NETDEV_F_100MB_FD},
+		{"100M-h", NETDEV_F_100MB_HD},
+		{"10M-f", NETDEV_F_100MB_FD},
+		{"10M-h", NETDEV_F_10MB_HD},
+		{"40G-f", NETDEV_F_40GB_FD},
+		{"100G-f", NETDEV_F_100GB_FD},
+		{"1T-f", NETDEV_F_1TB_FD},
+		{NULL, 	NETDEV_F_OTHER},
+	};
+	struct solaris_netdev_sdmap *snsp;
+	char	buffer[DLADM_PROP_VAL_MAX];
+	int	i;
+	int	error;
+
+	error = solaris_get_dlprop(netdev_name, "speed-duplex", field_name,
+	    buffer, sizeof (buffer));
+	if (error != 0) {
+		VLOG_ERR("Unable to retrieve feature speed-duplex for %s "
+		    "device", netdev_name);
+		return (error);
+	}
+	snsp = map;
+	for (i = 0; snsp[i].sns_val != NULL; i++) {
+		if (strstr(buffer, snsp[i].sns_val) != NULL)
+			*features |= snsp[i].sns_feature;
+	}
+	return (0);
+}
+
+static int
+netdev_solaris_read_features(struct netdev_solaris *netdev,
+    const char *netdev_name)
+{
+	char	buffer[DLADM_PROP_VAL_MAX];
+	int	error = 0;
+	int	speed;
+	bool	full;
+
+	if (netdev->cache_valid & VALID_FEATURES)
+		goto exit;
+
+	/*
+	 * Supported features
+	 *
+	 * Unsupported features:
+	 * NETDEV_F_COPPER
+	 * NETDEV_F_FIBER
+	 * NETDEV_F_PAUSE;
+	 * NETDEV_F_PAUSE_ASYM;
+	 */
+	netdev->supported = 0;
+	if ((error = netdev_solaris_chk_speed_duplex(netdev_name,
+	    "possible", &netdev->supported)) != 0)
+		goto exit;
+	/*
+	 * Advertised features.
+	 *
+	 * Unsupported features:
+	 * NETDEV_F_COPPER
+	 * NETDEV_F_FIBER
+	 * NETDEV_F_PAUSE;
+	 * NETDEV_F_PAUSE_ASYM;
+	 */
+	netdev->advertised = 0;
+	if ((error = netdev_solaris_chk_speed_duplex(netdev_name,
+	    "current", &netdev->advertised)) != 0)
+		goto exit;
+
+	/* Current settings. */
+	error = solaris_get_dlprop(netdev_name, "speed", "current", buffer,
+	    sizeof (buffer));
+	if (error != 0) {
+		VLOG_ERR("Unable to retrieve speed for %s device",
+		    netdev_name);
+		goto exit;
+	}
+	speed = atoi(buffer);
+
+	error = solaris_get_dlprop(netdev_name, "duplex", "current", buffer,
+	    sizeof (buffer));
+	if (error != 0) {
+		VLOG_ERR("Unable to retrieve duplex for %s device",
+		    netdev_name);
+		goto exit;
+	}
+	full = (strcmp(buffer, "full") == 0);
+
+	speed /= 1000000;
+	if (speed == 10) {
+		netdev->current = full ? NETDEV_F_10MB_FD : NETDEV_F_10MB_HD;
+	} else if (speed == 100) {
+		netdev->current = full ? NETDEV_F_100MB_FD : NETDEV_F_100MB_HD;
+	} else if (speed == 1000) {
+		netdev->current = full ? NETDEV_F_1GB_FD : NETDEV_F_1GB_HD;
+	} else if (speed == 10000) {
+		netdev->current = NETDEV_F_10GB_FD;
+	} else if (speed == 40000) {
+		netdev->current = NETDEV_F_40GB_FD;
+	} else if (speed == 100000) {
+		netdev->current = NETDEV_F_100GB_FD;
+	} else if (speed == 1000000) {
+		netdev->current = NETDEV_F_1TB_FD;
+	} else {
+		netdev->current = 0;
+	}
+	netdev->advertised |= (netdev->supported & NETDEV_F_AUTONEG);
+	netdev->cache_valid |= VALID_FEATURES;
+exit:
+	netdev->get_features_error = error;
+	return (error);
+}
+
+static int
+netdev_solaris_init(void)
+{
+	int error;
+
+	sock4 = socket(AF_INET, SOCK_DGRAM, 0);
+	if (sock4 < 0) {
+		error = errno;
+		VLOG_ERR("failed to create AF_INET socket (%s)",
+		    ovs_strerror(error));
+		return (error);
+	}
+
+	sock6 = socket(AF_INET6, SOCK_DGRAM, 0);
+	if (sock6 < 0) {
+		error = errno;
+		VLOG_ERR("failed to create AF_INET6 socket (%s)",
+		    ovs_strerror(error));
+		return (error);
+	}
+
+	error = solaris_init_rad();
+	return (error);
+}
+
+static void
+netdev_solaris_run(void)
+{
+}
+
+static void
+netdev_solaris_wait(void)
+{
+}
+
+static struct netdev *
+netdev_solaris_alloc(void)
+{
+	struct netdev_solaris *netdev = xzalloc(sizeof (*netdev));
+
+	return (&netdev->up);
+}
+
+static int
+netdev_solaris_unconfigure_uplink(const struct netdev *netdev_)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			error;
+
+	VLOG_DBG("netdev_solaris_unconfigure_uplink device %s", netdev_name);
+
+	error = solaris_modify_vnic(NETDEV_IMPL_ETHERSTUB, netdev->brname);
+	if (error != 0 && error != ENODEV) {
+		VLOG_ERR("failed to unconfigure %s as uplink for %s: "
+		    "%s", netdev_name, netdev->brname,
+		    ovs_strerror(error));
+	}
+	return (0);
+}
+
+static int
+netdev_solaris_configure_uplink(const struct netdev *netdev_,
+    const char *brname)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	char			buffer[DLADM_PROP_VAL_MAX];
+	int			error;
+
+	VLOG_DBG("netdev_solaris_configure_uplink device %s", netdev_name);
+
+	/*
+	 * Normally, we would expect to see that the bridge VNIC has already
+	 * been created by a call to netdev_solaris_construct() to create the
+	 * bridge netdev. However, on a service restart ovs-vswitchd sometimes
+	 * adds the lower-link port prior to adding the bridge's internal port.
+	 * As a result, we need to create the bridge VNIC here if it does not
+	 * already exist.
+	 */
+	error = solaris_get_dlprop(brname, "lower-link", "current",
+	    buffer, sizeof (buffer));
+	if (error == ENODEV) {
+		VLOG_DBG("%s vnic being created on %s",
+		    brname, netdev_name);
+		error = solaris_create_vnic(netdev_name, brname);
+		if (error != 0) {
+			VLOG_ERR("Failed to create vnic for %s: %s",
+			    brname, ovs_strerror(error));
+		}
+		goto exit;
+	} else if (error != 0) {
+		VLOG_ERR("Failed to get the lower-link for %s: %s",
+		    brname, ovs_strerror(error));
+		goto exit;
+	}
+
+	/*
+	 * If the lower-link is already set correctly, then return with
+	 * success.
+	 */
+	if (strcmp(buffer, netdev_name) == 0) {
+		error = 0;
+		goto exit;
+	}
+
+	/*
+	 * If the lower-link is already set to something other than the
+	 * etherstub, something is wrong.
+	 */
+	if (strcmp(buffer, NETDEV_IMPL_ETHERSTUB) != 0) {
+		VLOG_ERR("Bridge already has uplink %s", buffer);
+		error = EEXIST;
+		goto exit;
+	}
+
+	/*
+	 * This is the "normal" case, where the bridge VNIC existed and had
+	 * the etherstub as its lower-link. Move the VNIC to its uplink.
+	 */
+	error = solaris_modify_vnic(netdev_name, brname);
+	if (error != 0) {
+		VLOG_ERR("failed to configure %s as uplink: %s",
+		    netdev_name, ovs_strerror(error));
+		goto exit;
+	}
+	(void) strlcpy(netdev->brname, brname, sizeof (netdev->brname));
+
+exit:
+	return (error);
+}
+
+static boolean_t
+solaris_is_if_plumbed(struct netdev *netdev_, sa_family_t af, uint64_t *flags)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			sock;
+	int			proto;
+	int			error;
+	char			*astring;
+
+	ovs_assert(af == AF_INET || af == AF_INET6);
+
+	if (af == AF_INET) {
+		sock = sock4;
+		astring = "IPv4";
+		proto = SOLARIS_IPV4;
+	} else {
+		sock = sock6;
+		astring = "IPv6";
+		proto = SOLARIS_IPV6;
+	}
+	error = solaris_if_enabled(sock, netdev_name, flags);
+	if (error == 0) {
+		netdev->plumbed |= proto;
+	} else if (error != ENXIO) {
+		VLOG_DBG("netdev_is_if_plumbed %s device encountered "
+		    "error %d for %s", netdev_name, error, astring);
+	}
+	return (error == 0);
+}
+
+int
+netdev_create_impl_etherstub(void)
+{
+	int error;
+
+	error = solaris_create_etherstub(NETDEV_IMPL_ETHERSTUB);
+	if (error != 0) {
+		VLOG_ERR("netdev_create_impl_etherstub: failed to create %s: "
+		    "%s", NETDEV_IMPL_ETHERSTUB, ovs_strerror(error));
+
+	} else {
+		boolean_t bval = B_TRUE;
+
+		error = solaris_set_dlprop_boolean(NETDEV_IMPL_ETHERSTUB,
+		    "openvswitch", &bval);
+		if (error != 0) {
+			(void) solaris_delete_etherstub(NETDEV_IMPL_ETHERSTUB);
+			VLOG_ERR("netdev_create_impl_etherstub: failed to "
+			    "set 'openvswitch' property on %s: %s: ",
+			    NETDEV_IMPL_ETHERSTUB, ovs_strerror(error));
+		}
+	}
+	return (error);
+}
+
+void
+netdev_delete_impl_etherstub(void)
+{
+	boolean_t	bval = B_FALSE;
+	int		error;
+
+	error = solaris_set_dlprop_boolean(NETDEV_IMPL_ETHERSTUB,
+	    "openvswitch", &bval);
+	if (error != 0) {
+		VLOG_ERR("netdev_create_impl_etherstub: failed to "
+		    "set 'openvswitch' property on %s: %s: ",
+		    NETDEV_IMPL_ETHERSTUB, ovs_strerror(error));
+	} else {
+		error = solaris_delete_etherstub(NETDEV_IMPL_ETHERSTUB);
+		if (error != 0) {
+			VLOG_ERR("netdev_delete_impl_etherstub: failed to "
+			    "delete %s: %s", NETDEV_IMPL_ETHERSTUB,
+			    ovs_strerror(error));
+		}
+	}
+}
+
+boolean_t
+netdev_impl_etherstub_exists(void)
+{
+	return (solaris_etherstub_exists(NETDEV_IMPL_ETHERSTUB));
+}
+
+/*
+ * Creates system and internal devices. Really very little to
+ * do here given that neither system or internal devices must
+ * exist in the kernel yet.
+ */
+static int
+netdev_solaris_construct(struct netdev *netdev_)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	char			buffer[DLADM_PROP_VAL_MAX];
+	uint64_t		flags;
+	int			error;
+
+	ovs_mutex_init(&netdev->mutex);
+	if (netdev->up.netdev_class != &netdev_internal_class) {
+		VLOG_DBG("netdev_solaris_construct system device %s",
+		    netdev_name);
+	} else {
+		VLOG_DBG("netdev_solaris_construct internal device %s",
+		    netdev_name);
+	}
+	netdev->implicitly_plumbed = 0;
+	netdev->plumbed = 0;
+
+	(void) solaris_is_if_plumbed(netdev_, AF_INET, &flags);
+	(void) solaris_is_if_plumbed(netdev_, AF_INET6, &flags);
+
+	/*
+	 * loopback is illegal.
+	 */
+	if (netdev->plumbed != 0 && flags & NETDEV_LOOPBACK) {
+		VLOG_ERR("%s: cannot add a loopback device",
+		    netdev_name);
+		return (EINVAL);
+	}
+
+	if (netdev->up.netdev_class == &netdev_internal_class) {
+		error = solaris_get_dlprop(netdev_name, "lower-link",
+		    "current", buffer, sizeof (buffer));
+		if (error == ENODEV) {
+			error = solaris_create_vnic(NETDEV_IMPL_ETHERSTUB,
+			    netdev_name);
+			if (error != 0) {
+				VLOG_ERR("failed to configure %s as uplink: "
+				"%s", netdev_name, ovs_strerror(error));
+				return (error);
+			}
+		} else if (error != 0) {
+			VLOG_ERR("Failed to get the lower-link for %s: %s",
+			    netdev_name, ovs_strerror(error));
+		}
+	}
+	return (0);
+}
+
+static void
+netdev_solaris_destruct(struct netdev *netdev_)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			error;
+
+	VLOG_DBG("netdev_solaris_destruct device %s", netdev_name);
+
+	/*
+	 * If implicitly plumbed, then unplumb it.
+	 */
+	(void) netdev_solaris_unplumb(netdev_, AF_INET);
+	(void) netdev_solaris_unplumb(netdev_, AF_INET6);
+
+	if (netdev->up.netdev_class == &netdev_internal_class) {
+		error = solaris_delete_vnic(netdev_name);
+		if (error != 0) {
+			VLOG_ERR("failed to delete %s: %s",
+			    netdev_name, ovs_strerror(error));
+		}
+	} else {
+		if (netdev->brname[0] != '\0') {
+			netdev_solaris_unconfigure_uplink(netdev_);
+		}
+	}
+
+	ovs_mutex_destroy(&netdev->mutex);
+}
+
+static void
+netdev_solaris_dealloc(struct netdev *netdev_)
+{
+	struct netdev_solaris *netdev = netdev_solaris_cast(netdev_);
+
+	free(netdev);
+}
+
+/*
+ * Attempts to set 'netdev''s MAC address to 'mac'. Returns 0 if successful,
+ * otherwise a positive errno value.
+ */
+static int
+netdev_solaris_set_etheraddr(struct netdev *netdev_,
+    const uint8_t mac[ETH_ADDR_LEN])
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	char			buffer[128];
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_set_etheraddr device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+	if ((netdev->cache_valid & VALID_ETHERADDR) &&
+	    (eth_addr_equals(netdev->etheraddr, mac))) {
+		goto exit;
+	}
+
+	/*
+	 * In case there is some kind of failure, invalidate
+	 * the cached value.
+	 */
+	netdev->cache_valid &= ~VALID_ETHERADDR;
+
+	/*
+	 * MAC addresses are datalink properties.
+	 */
+	(void) snprintf(buffer, sizeof (buffer), ETH_ADDR_FMT,
+	    ETH_ADDR_ARGS(mac));
+	error = solaris_set_dlprop_string(netdev_name, "mac-address", buffer);
+	if (error != 0) {
+		VLOG_ERR("set etheraddr %s on %s device failed: %s",
+		    buffer, netdev_name, ovs_strerror(error));
+		goto exit;
+	}
+
+	memcpy(netdev->etheraddr, mac, ETH_ADDR_LEN);
+	netdev->cache_valid |= VALID_ETHERADDR;
+	netdev_change_seq_changed(netdev_);
+
+exit:
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+static int
+netdev_solaris_get_dlclass(const struct netdev *netdev_)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	char			buffer[DLADM_PROP_VAL_MAX];
+	int			error = 0;
+
+	if (netdev->cache_valid & VALID_LINKCLASS)
+		goto exit;
+
+	/*
+	 * Get the datalink class for this link.
+	 */
+	error = solaris_get_dlclass(netdev_name, buffer, sizeof (buffer));
+	if (error != 0) {
+		VLOG_ERR("Unable to retrieve linkclass for %s device, %d",
+		    netdev_name, error);
+		goto exit;
+	}
+
+	(void) strlcpy(netdev->class, buffer, sizeof (netdev->class));
+	netdev->cache_valid |= VALID_LINKCLASS;
+exit:
+	return (error);
+}
+
+/*
+ * Determines if this netdev is an uplink for the bridge.
+ */
+static bool
+netdev_solaris_is_uplink(const struct netdev *netdev_)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+
+	VLOG_DBG("netdev_solaris_is_uplink device %s", netdev_name);
+	if (netdev_solaris_get_dlclass(netdev_) != 0)
+		return (false);
+	return (solaris_is_uplink_class(netdev->class));
+}
+
+static int
+netdev_solaris_get_etheraddr(const struct netdev *netdev_,
+    uint8_t mac[ETH_ADDR_LEN])
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	char			buffer[DLADM_PROP_VAL_MAX];
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_get_etheraddr device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+	/*
+	 * Only the bridge ethernet address can be changed outside the
+	 * knowledge of vswitchd (when the uplink is added and the bridge
+	 * VNIC is moved to the uplink).
+	 */
+	if ((netdev->cache_valid & VALID_ETHERADDR) &&
+	    (netdev->up.netdev_class != &netdev_internal_class)) {
+		memcpy(mac, netdev->etheraddr, ETH_ADDR_LEN);
+		goto exit;
+	}
+
+	/*
+	 * MAC addresses are datalink properties.
+	 */
+	error = solaris_get_dlprop(netdev_name, "mac-address", "current",
+	    buffer, sizeof (buffer));
+	if (error != 0) {
+		VLOG_ERR("Unable to retrieve etheraddr for %s device",
+		    netdev_name);
+		goto exit;
+	}
+
+	if (!eth_addr_from_string(buffer, mac)) {
+		VLOG_ERR("Invalid etheraddr for %s device", netdev_name);
+		error = EINVAL;
+		goto exit;
+	}
+
+	memcpy(netdev->etheraddr, mac, ETH_ADDR_LEN);
+	netdev->cache_valid |= VALID_ETHERADDR;
+exit:
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+static int
+netdev_solaris_get_mtu__(struct netdev_solaris *netdev, int *mtup,
+    const char *netdev_name)
+{
+	char	buffer[DLADM_PROP_VAL_MAX];
+	int	mtu;
+	int	error = 0;
+
+	if (netdev->cache_valid & VALID_MTU) {
+		*mtup = netdev->mtu;
+		return (0);
+	}
+
+	/*
+	 * MTU is a datalink property
+	 */
+	error = solaris_get_dlprop(netdev_name, "mtu", "current", buffer,
+	    sizeof (buffer));
+	if (error != 0) {
+		VLOG_ERR("Unable to retrieve mtu for %s device",
+		    netdev_name);
+		return (error);
+	}
+
+	mtu = atoi(buffer);
+	if (mtu == 0) {
+		VLOG_ERR("Invalid mtu for %s device", netdev_name);
+		error = EINVAL;
+		return (error);
+	}
+
+	netdev->mtu = *mtup = mtu;
+	netdev->cache_valid |= VALID_MTU;
+	return (0);
+}
+
+/*
+ * Returns the maximum size of transmitted (and received) packets on 'netdev',
+ * in bytes, not including the hardware header; thus, this is typically 1500
+ * bytes for Ethernet devices.
+ */
+static int
+netdev_solaris_get_mtu(const struct netdev *netdev_, int *mtup)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_get_mtu device %s", netdev_name);
+
+	if (!netdev_) {
+		VLOG_DBG("netdev_solaris_get_mtu null netdevs");
+		return (error);
+	}
+
+	ovs_mutex_lock(&netdev->mutex);
+	error = netdev_solaris_get_mtu__(netdev, mtup, netdev_name);
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+/*
+ * Sets the maximum size of transmitted (MTU) for given device.
+ */
+static int
+netdev_solaris_set_mtu(const struct netdev *netdev_ OVS_UNUSED, int mtu)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	uint64_t		ulval = mtu;
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_set_mtu device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+	if ((netdev->cache_valid & VALID_MTU && netdev->mtu == mtu))
+		goto exit;
+
+	/*
+	 * In case there is some kind of failure, invalidate
+	 * the cached value.
+	 */
+	netdev->cache_valid &= ~VALID_MTU;
+
+	/*
+	 * MTU is a datalink property.
+	 */
+	error = solaris_set_dlprop_ulong(netdev_name, "mtu", &ulval);
+	if (error != 0) {
+		VLOG_ERR("set mtu on %s device failed: %s",
+		    netdev_name, ovs_strerror(error));
+		goto exit;
+	}
+
+	netdev->mtu = mtu;
+	netdev->cache_valid |= VALID_MTU;
+	netdev_change_seq_changed(netdev_);
+
+exit:
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+/*
+ * Returns the ifindex of 'netdev', if successful, as a positive number.
+ * On failure, returns a negative errno.
+ */
+static int
+netdev_solaris_get_ifindex(const struct netdev *netdev_)
+{
+	struct netdev_solaris 	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			ifindex;
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_get_ifindex device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+	if (netdev->cache_valid & VALID_IFINDEX) {
+		ifindex = netdev->ifindex;
+		goto exit;
+	}
+
+	ifindex = (int)if_nametoindex(netdev_name);
+	if (ifindex <= 0) {
+		error = errno;
+		goto exit;
+	}
+
+	netdev->ifindex = ifindex;
+	netdev->cache_valid |= VALID_IFINDEX;
+
+exit:
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error ? -error : ifindex);
+}
+
+/*
+ * Retrieves current device stats for 'netdev-solaris'.
+ */
+static int
+netdev_solaris_get_stats(const struct netdev *netdev_,
+    struct netdev_stats *stats)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	kstat2_status_t		stat;
+	kstat2_map_t		map;
+	char			kuri[1024];
+	char			devname[DLADM_PROP_VAL_MAX];
+	char			modname[DLADM_PROP_VAL_MAX];
+	char			kstat_name[MAXLINKNAMELEN];
+	char			*name;
+	zoneid_t		zid;
+	uint_t			instance;
+	int			error = 0;
+	boolean_t		is_uplink;
+
+	VLOG_DBG("netdev_solaris_get_stats device %s", netdev_name);
+
+	is_uplink = netdev_solaris_is_uplink(netdev_);
+
+	/*
+	 * Initialize statistics only supported on uplink.
+	 */
+	stats->rx_length_errors = 0;
+	stats->rx_missed_errors = 0;
+	stats->rx_over_errors = 0;
+
+	/*
+	 * Unsupported on Solaris.
+	 */
+	stats->rx_crc_errors = UINT64_MAX;
+	stats->rx_frame_errors = UINT64_MAX;
+	stats->rx_fifo_errors = UINT64_MAX;
+	stats->tx_aborted_errors = UINT64_MAX;
+	stats->tx_carrier_errors = UINT64_MAX;
+	stats->tx_fifo_errors = UINT64_MAX;
+	stats->tx_heartbeat_errors = UINT64_MAX;
+	stats->tx_window_errors	= UINT64_MAX;
+
+	ovs_mutex_lock(&kstat_mutex);
+	if (!kstat2_handle_initialized) {
+		VLOG_DBG("netdev_solaris_get_stats initializing handle");
+		if (!kstat_handle_init(&nd_khandle)) {
+			error = -1;
+			goto done;
+		}
+		kstat2_handle_initialized = B_TRUE;
+	} else if (!kstat_handle_update(nd_khandle)) {
+		VLOG_DBG("netdev_solaris_get_stats error updating stats");
+		kstat_handle_close(&nd_khandle);
+		kstat2_handle_initialized = B_FALSE;
+		error = -1;
+		goto done;
+	}
+	name = (char *)netdev_name;
+	instance = 0;
+	if (strchr(netdev_name, '/') != NULL) {
+		(void) solaris_dlparse_zonelinkname(netdev_name, kstat_name,
+		    &zid);
+		name = kstat_name;
+		instance = zid;
+	}
+	(void) snprintf(kuri, sizeof (kuri), "kstat:/net/link/%s/%d",
+	    name, instance);
+	stat = kstat2_lookup_map(nd_khandle, kuri, &map);
+	if (stat != KSTAT2_S_OK) {
+		VLOG_WARN("kstat2_lookup_map of %s failed: %s", kuri,
+		    kstat2_status_string(stat));
+	} else {
+		if (is_uplink) {
+			stats->rx_packets = get_nvvt_int(map, "ipackets64");
+			stats->tx_packets = get_nvvt_int(map, "opackets64");
+			stats->rx_bytes = get_nvvt_int(map, "rbytes");
+			stats->tx_bytes = get_nvvt_int(map, "obytes");
+			stats->rx_errors = get_nvvt_int(map, "ierrors");
+			stats->tx_errors = get_nvvt_int(map, "oerrors");
+		} else {
+			stats->tx_packets = get_nvvt_int(map, "ipackets64");
+			stats->rx_packets = get_nvvt_int(map, "opackets64");
+			stats->tx_bytes = get_nvvt_int(map, "rbytes");
+			stats->rx_bytes = get_nvvt_int(map, "obytes");
+			stats->tx_errors = get_nvvt_int(map, "ierrors");
+			stats->rx_errors = get_nvvt_int(map, "oerrors");
+		}
+		stats->collisions = get_nvvt_int(map, "collisions");
+		stats->multicast = get_nvvt_int(map, "multircv") +
+		    get_nvvt_int(map, "multixmt");
+	}
+
+	(void) snprintf(kuri, sizeof (kuri), "kstat:/net/%s/link/%d",
+	    name, instance);
+	stat = kstat2_lookup_map(nd_khandle, kuri, &map);
+	if (stat != KSTAT2_S_OK) {
+		VLOG_WARN("kstat2_lookup_map of %s failed: %s", kuri,
+		    kstat2_status_string(stat));
+	} else {
+		if (is_uplink) {
+			stats->rx_dropped = get_nvvt_int(map, "idrops");
+			stats->tx_dropped = get_nvvt_int(map, "odrops");
+		} else {
+			stats->tx_dropped = get_nvvt_int(map, "idrops");
+			stats->rx_dropped = get_nvvt_int(map, "odrops");
+		}
+	}
+
+	if (!is_uplink)
+		goto done;
+
+	/*
+	 * Can we do anything for aggr?
+	 */
+	if (strcmp("phys", netdev->class) != 0)
+		goto done;
+
+	if (solaris_get_devname(netdev_name, devname, sizeof (devname)) != 0) {
+		VLOG_WARN("Failed to retrieve devname of uplink\n");
+		error = -1;
+		goto done;
+	}
+
+	if (!dlparse_drvppa(devname, modname, sizeof (modname),
+	    &instance)) {
+		VLOG_DBG("netdev_solaris_get_stats error getting "
+		    "mod/instance for %s\n", devname);
+		error = -1;
+		goto done;
+	}
+
+	(void) snprintf(kuri, sizeof (kuri), "kstat:/net/%s/statistics/%d",
+	    modname, instance);
+	stat = kstat2_lookup_map(nd_khandle, kuri, &map);
+	if (stat != KSTAT2_S_OK) {
+		VLOG_WARN("kstat2_lookup_map of %s failed: %s", kuri,
+		    kstat2_status_string(stat));
+		error = -1;
+		goto done;
+	}
+	stats->rx_length_errors = get_nvvt_int(map, "Recv_Length_Errors");
+	stats->rx_missed_errors = get_nvvt_int(map, "Recv_Missed_Packets");
+	stats->rx_over_errors = get_nvvt_int(map, "Recv_Oversize");
+
+done:
+	ovs_mutex_unlock(&kstat_mutex);
+	return (error);
+}
+
+/*
+ * Stores the features supported by 'netdev' into of '*current', '*advertised',
+ * '*supported', and '*peer'.  Each value is a bitmap of NETDEV_* bits.
+ * Returns 0 if successful, otherwise a positive errno value.
+ */
+static int
+netdev_solaris_get_features(const struct netdev *netdev_,
+    enum netdev_features *current,
+    enum netdev_features *advertised,
+    enum netdev_features *supported,
+    enum netdev_features *peer)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	const char		*physname;
+	char			buffer[DLADM_PROP_VAL_MAX];
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_get_features device %s", netdev_name);
+	ovs_mutex_lock(&netdev->mutex);
+
+	if (netdev_solaris_is_uplink(netdev_)) {
+		physname = netdev_name;
+	} else {
+		error = solaris_get_dllower(netdev_name, buffer,
+		    sizeof (buffer));
+		if (error != 0)
+			goto exit;
+		physname = buffer;
+	}
+
+	error = netdev_solaris_read_features(netdev, physname);
+	if (error != 0)
+		goto exit;
+
+	*current = netdev->current;
+	*advertised = netdev->advertised;
+	*supported = netdev->supported;
+	*peer = 0;
+
+exit:
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+/*
+ * Attempts to set input rate limiting (policing) policy.  Returns 0 if
+ * successful, otherwise a positive errno value.
+ */
+static int
+netdev_solaris_set_policing(struct netdev *netdev_,
+    uint32_t kbits_rate OVS_UNUSED, uint32_t kbits_burst OVS_UNUSED)
+{
+	const char	*netdev_name = netdev_get_name(netdev_);
+	int		error = 0;
+
+	VLOG_DBG("netdev_solaris_set_policing device %s", netdev_name);
+
+	/* XXXSolaris check libdladm:setlinkrop maxbw/priority */
+
+	return (error);
+}
+
+static int
+netdev_solaris_get_qos_types(const struct netdev *netdev_,
+    struct sset *types)
+{
+	const char	*netdev_name = netdev_get_name(netdev_);
+	const struct tc_ops *const *opsp;
+
+	VLOG_DBG("netdev_solaris_get_qos_types device %s", netdev_name);
+
+	/*
+	 * XXXSolaris we will support only HTB-equivalent which will translate
+	 * to maxbw/priority.
+	 */
+	for (opsp = tcs; *opsp != NULL; opsp++) {
+		const struct tc_ops	*ops = *opsp;
+
+		if (ops->tc_install && ops->ovs_name[0] != '\0')
+			sset_add(types, ops->ovs_name);
+	}
+	return (0);
+}
+
+static int
+netdev_solaris_get_qos_capabilities(const struct netdev *netdev_,
+    const char *type, struct netdev_qos_capabilities *caps)
+{
+	const char	*netdev_name = netdev_get_name(netdev_);
+	int		error = EOPNOTSUPP;
+	const struct tc_ops *ops = tc_lookup_ovs_name(type);
+
+	VLOG_DBG("netdev_solaris_get_qos_capabilities device %s",
+	    netdev_name);
+
+	if (!ops)
+		return (EOPNOTSUPP);
+
+	/*
+	 * Arbit number for now. In theory we are not limited for bandwidth
+	 * limit. But, with shares and priority this can be revisited.
+	 */
+	caps->n_queues = ops->n_queues;
+
+	return (error);
+}
+
+static int
+netdev_solaris_get_qos(const struct netdev *netdev_,
+    const char **typep, struct smap *details)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_get_qos device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+	error = tc_query_qdisc(netdev_);
+	if (!error) {
+		*typep = netdev->tc->ops->ovs_name;
+		error = (netdev->tc->ops->qdisc_get ?
+		    netdev->tc->ops->qdisc_get(netdev_, details) : 0);
+	}
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+static int
+netdev_solaris_set_qos(struct netdev *netdev_,
+    const char *type, const struct smap *details)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	const struct tc_ops	*new_ops;
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_set_qos device %s", netdev_name);
+	new_ops = tc_lookup_ovs_name(type);
+	if (!new_ops) {
+		VLOG_DBG("netdev_solaris_set_qos type %s not found", type);
+		return (EOPNOTSUPP);
+	}
+	ovs_mutex_lock(&netdev->mutex);
+	error = tc_query_qdisc(netdev_);
+	if (error) {
+		VLOG_DBG("netdev_solaris_set_qos qdisc querry failed");
+		goto exit;
+	}
+
+	if (new_ops == netdev->tc->ops) {
+		error = new_ops->qdisc_set ?
+		    new_ops->qdisc_set(netdev_, details) :
+		    0;
+	} else {
+		error = tc_del_qdisc(netdev_);
+		if (error) {
+			VLOG_DBG("netdev_solaris_set_qos error deleting %s",
+			    type);
+			goto exit;
+		}
+		ovs_assert(netdev->tc == NULL);
+		/* Install new qdisc. */
+		error = new_ops->tc_install(netdev_, details);
+		ovs_assert((error == 0) == (netdev->tc != NULL));
+		VLOG_DBG("netdev_solaris_set_qos installed %s, %d", type,
+		    error);
+	}
+exit:
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+static int
+netdev_solaris_get_queue(const struct netdev *netdev_,
+    unsigned int queue_id, struct smap *details)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_get_queue device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+	error = tc_query_qdisc(netdev_);
+	if (!error) {
+		struct tc_queue *queue = tc_find_queue(netdev_, queue_id);
+		error = (queue ?
+		    netdev->tc->ops->class_get(netdev_, queue, details) :
+		    ENOENT);
+	}
+	ovs_mutex_unlock(&netdev->mutex);
+	VLOG_DBG("netdev_solaris_get_queue device %s done %d", netdev_name,
+	    error);
+	return (error);
+}
+
+static int
+netdev_solaris_set_queue(struct netdev *netdev_,
+    unsigned int queue_id, const struct smap *details)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_set_queue device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+	error = tc_query_qdisc(netdev_);
+	if (!error) {
+		error = (queue_id < netdev->tc->ops->n_queues &&
+		    netdev->tc->ops->class_set ?
+		    netdev->tc->ops->class_set(netdev_, queue_id, details) :
+		    EINVAL);
+	} else {
+		VLOG_DBG("netdev_solaris_set_queue %s: no qdisc", netdev_name);
+	}
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+static int
+netdev_solaris_delete_queue(struct netdev *netdev_,
+    unsigned int queue_id)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_delete_queue device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+	error = tc_query_qdisc(netdev_);
+	if (!error) {
+		if (netdev->tc->ops->class_delete) {
+			struct tc_queue *queue =
+			    tc_find_queue(netdev_, queue_id);
+
+			error = (queue
+			    ? netdev->tc->ops->class_delete(netdev_, queue)
+			    : ENOENT);
+		} else {
+			error = EINVAL;
+		}
+	}
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+static int
+netdev_solaris_get_queue_stats(const struct netdev *netdev_,
+    unsigned int queue_id OVS_UNUSED,
+    struct netdev_queue_stats *stats OVS_UNUSED)
+{
+	const char	*netdev_name = netdev_get_name(netdev_);
+	int		error = 0;
+
+	VLOG_DBG("netdev_solaris_set_queue_stats device %s", netdev_name);
+
+	return (error);
+}
+
+struct netdev_solaris_queue_state {
+	unsigned int	*queues;
+	size_t		cur_queue;
+	size_t		n_queues;
+};
+
+static int
+netdev_solaris_queue_dump_start(const struct netdev *netdev_,
+    void **statep)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_queue_dump_start device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+	error = tc_query_qdisc(netdev_);
+	if (!error) {
+		if (netdev->tc->ops->class_get) {
+			struct netdev_solaris_queue_state *state;
+			struct tc_queue *queue;
+			size_t i;
+
+			*statep = state = xmalloc(sizeof (*state));
+			state->n_queues = hmap_count(&netdev->tc->queues);
+			state->cur_queue = 0;
+			state->queues =
+			    xmalloc(state->n_queues * sizeof (*state->queues));
+
+			i = 0;
+			HMAP_FOR_EACH(queue, hmap_node, &netdev->tc->queues) {
+				state->queues[i++] = queue->queue_id;
+			}
+		} else {
+			error = EOPNOTSUPP;
+		}
+	} else {
+		VLOG_DBG("netdev_solaris_queue_dump_start: no qdisc");
+	}
+	ovs_mutex_unlock(&netdev->mutex);
+
+	return (error);
+}
+
+static int
+netdev_solaris_queue_dump_next(const struct netdev *netdev_,
+    void *state_, unsigned int *queue_idp, struct smap *details)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			error = EOF;
+	struct netdev_solaris_queue_state *state = state_;
+
+	if (!state) {
+		VLOG_DBG("netdev_solaris_queue_dump_next %s: null state\n",
+		    netdev_name);
+		return (EOF);
+	}
+
+	VLOG_DBG("netdev_solaris_queue_dump_next device %s, %"PRIuSIZE
+	    ", %"PRIuSIZE, netdev_name, state->cur_queue, state->n_queues);
+	ovs_mutex_lock(&netdev->mutex);
+	while (state->cur_queue < state->n_queues) {
+		unsigned int queue_id = state->queues[state->cur_queue++];
+		struct tc_queue *queue = tc_find_queue(netdev_, queue_id);
+
+		if (queue) {
+			*queue_idp = queue_id;
+			if (!netdev->tc || !netdev->tc->ops ||
+			    !netdev->tc->ops->class_get) {
+				break;
+			}
+			error = netdev->tc->ops->class_get(netdev_, queue,
+			    details);
+			break;
+		}
+	}
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+static int
+netdev_solaris_queue_dump_done(const struct netdev *netdev_,
+    void *state_)
+{
+	const char	*netdev_name = netdev_get_name(netdev_);
+	struct netdev_solaris_queue_state *state = state_;
+
+	VLOG_DBG("netdev_solaris_queue_dump_done device %s", netdev_name);
+	if (state) {
+		if (state->queues)
+			free(state->queues);
+		free(state);
+	}
+	return (0);
+}
+
+static int
+netdev_solaris_dump_queue_stats(const struct netdev *netdev_,
+    netdev_dump_queue_stats_cb *cb OVS_UNUSED, void *aux OVS_UNUSED)
+{
+	const char	*netdev_name = netdev_get_name(netdev_);
+	int		error = 0;
+
+	VLOG_DBG("netdev_solaris_queue_dump_stats device %s", netdev_name);
+
+	return (error);
+}
+
+/*
+ * If 'netdev' has an assigned IPv4 address, sets '*address' to that
+ * address and '*netmask' to the associated netmask. Otherwise, returns
+ * errno.
+ */
+static int
+netdev_solaris_get_in4(const struct netdev *netdev_,
+    struct in_addr *address, struct in_addr *netmask)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	struct lifreq		lifr;
+	const struct sockaddr_in *sin;
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_get_in4 device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+	if (netdev->cache_valid & VALID_IN4) {
+		*address = netdev->in4;
+		*netmask = netdev->netmask;
+		goto exit;
+	}
+
+	error = netdev_solaris_plumb(netdev_, AF_INET);
+	if (error != 0)
+		goto exit;
+
+	/*
+	 * In the future, a RAD IP module might be a good provider
+	 * of this information. For now, use the SIOCGLIFADDR.
+	 */
+	bzero(&lifr, sizeof (lifr));
+	(void) strncpy(lifr.lifr_name, netdev_name, sizeof (lifr.lifr_name));
+	sin = ALIGNED_CAST(struct sockaddr_in *, &lifr.lifr_addr);
+	if (ioctl(sock4, SIOCGLIFADDR, &lifr) < 0) {
+		error = errno;
+		goto exit;
+	}
+	netdev->in4 = sin->sin_addr;
+
+	if (ioctl(sock4, SIOCGLIFNETMASK, (caddr_t)&lifr) < 0) {
+		error = errno;
+		goto exit;
+	}
+	netdev->netmask = sin->sin_addr;
+	netdev->cache_valid |= VALID_IN4;
+
+	if (netdev->in4.s_addr != INADDR_ANY) {
+		*address = netdev->in4;
+		*netmask = netdev->netmask;
+	} else {
+		error = EADDRNOTAVAIL;
+	}
+
+exit:
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+static int
+netdev_solaris_set_in4(struct netdev *netdev_, struct in_addr address,
+    struct in_addr netmask)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	struct lifreq		lifr;
+	struct sockaddr_in	*sin;
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_set_in4 device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+
+	error = netdev_solaris_plumb(netdev_, AF_INET);
+	if (error != 0)
+		goto exit;
+
+	/*
+	 * In the future, a RAD IP module might be a good provider
+	 * of this information. For now, use the SIOCSLIFADDR.
+	 */
+	bzero(&lifr, sizeof (lifr));
+	(void) strncpy(lifr.lifr_name, netdev_name, sizeof (lifr.lifr_name));
+	sin = ALIGNED_CAST(struct sockaddr_in *, &lifr.lifr_addr);
+	sin->sin_addr = address;
+	sin->sin_family = AF_INET;
+	if (ioctl(sock4, SIOCSLIFADDR, &lifr) < 0) {
+		error = errno;
+		goto exit;
+	}
+	if (address.s_addr == htonl(INADDR_ANY))
+		goto done;
+
+	sin->sin_addr = netmask;
+	if (ioctl(sock4, SIOCSLIFNETMASK, (caddr_t)&lifr) < 0) {
+		error = errno;
+		goto done;
+	}
+	netdev->in4 = address;
+	netdev->netmask = netmask;
+	netdev->cache_valid |= VALID_IN4;
+
+done:
+	netdev_change_seq_changed(netdev_);
+exit:
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+/*
+ * If 'netdev' has an assigned IPv6 address, sets '*address' to that
+ * address. Otherwise, returns errno.
+ */
+static int
+netdev_solaris_get_in6(const struct netdev *netdev_, struct in6_addr *address)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	struct lifreq		lifr;
+	const struct sockaddr_in6 *sin6;
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_get_in6 device %s", netdev_name);
+
+	ovs_mutex_lock(&netdev->mutex);
+	if (netdev->cache_valid & VALID_IN6) {
+		if (!IN6_IS_ADDR_UNSPECIFIED(address))
+			*address = netdev->in6;
+		else
+			error = EADDRNOTAVAIL;
+		goto exit;
+	}
+
+	error = netdev_solaris_plumb(netdev_, AF_INET6);
+	if (error != 0)
+		goto exit;
+
+	/*
+	 * In the future, a RAD IP module might be a good provider
+	 * of this information. For now, use the SIOCGLIFADDR.
+	 */
+	bzero(&lifr, sizeof (lifr));
+	(void) strncpy(lifr.lifr_name, netdev_name, sizeof (lifr.lifr_name));
+	if (ioctl(sock6, SIOCGLIFADDR, &lifr) < 0) {
+		error = errno;
+		goto exit;
+	}
+	sin6 = ALIGNED_CAST(struct sockaddr_in6 *, &lifr.lifr_addr);
+	netdev->in6 = sin6->sin6_addr;
+	netdev->cache_valid |= VALID_IN6;
+
+	if (!IN6_IS_ADDR_UNSPECIFIED(address))
+		*address = netdev->in6;
+	else
+		error = EADDRNOTAVAIL;
+
+exit:
+	ovs_mutex_unlock(&netdev->mutex);
+	return (error);
+}
+
+#define	ROUNDUP_LONG(a) ((a) > 0 ? (1 + (((a) - 1) | (sizeof (long) - 1))) : \
+    sizeof (long))
+#define	RT_ADVANCE(x, n) ((x) += ROUNDUP_LONG(salen(n)))
+#define	BUF_SIZE 2048
+
+typedef struct rtmsg {
+	struct rt_msghdr	m_rtm;
+	char			m_space[BUF_SIZE];
+} rtmsg_t;
+
+static int
+salen(const struct sockaddr *sa)
+{
+	switch (sa->sa_family) {
+	case AF_INET:
+		return (sizeof (struct sockaddr_in));
+	case AF_LINK:
+		return (sizeof (struct sockaddr_dl));
+	case AF_INET6:
+		return (sizeof (struct sockaddr_in6));
+	default:
+		return (sizeof (struct sockaddr));
+	}
+}
+
+/*
+ * Adds 'router' as a default IP gateway.
+ */
+static int
+netdev_solaris_add_router(struct netdev *netdev_, struct in_addr router)
+{
+	const char		*netdev_name = netdev_get_name(netdev_);
+	rtmsg_t			m_rtmsg;
+	struct rt_msghdr	*rtm = &m_rtmsg.m_rtm;
+	char			*cp = m_rtmsg.m_space;
+	struct sockaddr_in	gateway;
+	struct sockaddr_in	dest;
+	struct sockaddr_in	mask;
+	int			rtsock_fd;
+	int			error = 0;
+	int			l;
+
+	VLOG_DBG("netdev_solaris_add_router %s", netdev_name);
+
+	rtsock_fd = socket(PF_ROUTE, SOCK_RAW, 0);
+	if (rtsock_fd == -1) {
+		error = errno;
+		VLOG_ERR("failed to create PF_ROUTE socket (%s)",
+		    ovs_strerror(error));
+		return (error);
+	}
+
+	memset(&gateway, 0, sizeof (gateway));
+	gateway.sin_family = AF_INET;
+	gateway.sin_addr = router;
+
+	memset(&dest, 0, sizeof (dest));
+	dest.sin_family = AF_INET;
+	dest.sin_addr.s_addr = htonl(INADDR_ANY);
+
+	memset(&dest, 0, sizeof (mask));
+	mask.sin_family = AF_INET;
+	mask.sin_addr.s_addr = htonl(INADDR_ANY);
+
+	(void) memset(&m_rtmsg, 0, sizeof (m_rtmsg));
+	rtm->rtm_version = RTM_VERSION;
+	rtm->rtm_type	 = RTM_ADD;
+	rtm->rtm_flags	 = RTF_GATEWAY | RTF_STATIC | RTF_UP;
+	rtm->rtm_addrs	 = RTA_GATEWAY | RTA_DST | RTA_NETMASK;
+
+	l = ROUNDUP_LONG(sizeof (struct sockaddr_in));
+	(void) memcpy(cp, &dest, l);
+	cp += l;
+	(void) memcpy(cp, &gateway, l);
+	cp += l;
+	(void) memcpy(cp, &mask, l);
+	cp += l;
+
+	rtm->rtm_msglen = l = cp - (char *)&m_rtmsg;
+	if (write(rtsock_fd, (char *)&m_rtmsg, l) != l) {
+		char buffer[INET_ADDRSTRLEN];
+		(void) inet_ntop(AF_INET, &router, buffer, sizeof (buffer));
+		error = errno;
+		VLOG_ERR("failed to add router %s: %s", buffer,
+		    ovs_strerror(error));
+	}
+	close(rtsock_fd);
+	return (error);
+}
+
+/*
+ * Looks up the next hop for 'host' in the host's routing table.  If
+ * successful, stores the next hop gateway's address (0 if 'host' is on a
+ * directly connected network) in '*next_hop' and a copy of the name of the
+ * device to reach 'host' in '*netdev_name', and returns 0. The caller is
+ * responsible for freeing '*netdev_name' (by calling free()).
+ */
+static int
+netdev_solaris_get_next_hop(const struct in_addr *host,
+    struct in_addr *next_hop, char **netdev_name)
+{
+	rtmsg_t			m_rtmsg;
+	struct rt_msghdr	*rtm = &m_rtmsg.m_rtm;
+	char			*cp = m_rtmsg.m_space;
+	int			rtsock_fd;
+	struct sockaddr_in	sin;
+	struct sockaddr_dl	sdl;
+	const pid_t		pid = getpid();
+	static int		seq;
+	boolean_t		gateway = B_FALSE;
+	ssize_t			ssz;
+	char			*ifname = NULL;
+	int			saved_errno;
+	int			error = 0;
+	int			rlen;
+	int			l;
+	int			i;
+
+	memset(&sin, 0, sizeof (sin));
+	sin.sin_family = AF_INET;
+	sin.sin_port = 0;
+	sin.sin_addr = *host;
+
+	memset(&sdl, 0, sizeof (sdl));
+	sdl.sdl_family = AF_LINK;
+
+	rtsock_fd = socket(PF_ROUTE, SOCK_RAW, 0);
+	if (rtsock_fd == -1) {
+		error = errno;
+		VLOG_ERR("failed to create PF_ROUTE socket (%s)",
+		    ovs_strerror(error));
+		return (error);
+	}
+
+	(void) memset(&m_rtmsg, 0, sizeof (m_rtmsg));
+	rtm->rtm_type = RTM_GET;
+	rtm->rtm_flags = RTF_HOST|RTF_UP;
+	rtm->rtm_version = RTM_VERSION;
+	rtm->rtm_seq = ++seq;
+	rtm->rtm_addrs = RTA_DST|RTA_IFP;
+
+	l = ROUNDUP_LONG(sizeof (struct sockaddr_in));
+	(void) memcpy(cp, &sin, l);
+	cp += l;
+
+	l = ROUNDUP_LONG(sizeof (struct sockaddr_dl));
+	(void) memcpy(cp, &sdl, l);
+	cp += l;
+
+	rtm->rtm_msglen = l = cp - (char *)&m_rtmsg;
+	if ((rlen = write(rtsock_fd, (char *)&m_rtmsg, l)) < l) {
+		error = errno;
+		VLOG_ERR("failed to get route: %s", ovs_strerror(error));
+		close(rtsock_fd);
+		return (errno);
+	}
+
+	memset(next_hop, 0, sizeof (*next_hop));
+	*netdev_name = NULL;
+	memset(&m_rtmsg, 0, sizeof (m_rtmsg));
+	do {
+		ssz = read(rtsock_fd, &m_rtmsg, sizeof (m_rtmsg));
+	} while (ssz > 0 && (rtm->rtm_seq != seq || rtm->rtm_pid != pid));
+	saved_errno = errno;
+	close(rtsock_fd);
+	if (ssz <= 0) {
+		if (ssz < 0) {
+			return (saved_errno);
+		}
+		return (EPIPE);
+	}
+	cp = (void *)&m_rtmsg.m_space;
+	for (i = 1; i; i <<= 1) {
+		if ((rtm->rtm_addrs & i) != 0) {
+			const struct sockaddr *sa = (const void *)cp;
+
+			if ((i == RTA_GATEWAY) && sa->sa_family == AF_INET) {
+				const struct sockaddr_in * const sin =
+				    ALIGNED_CAST(const struct sockaddr_in *,
+				    sa);
+
+				*next_hop = sin->sin_addr;
+				gateway = B_TRUE;
+			}
+			if ((i == RTA_IFP) && sa->sa_family == AF_LINK) {
+				const struct sockaddr_dl * const sdl =
+				    ALIGNED_CAST(const struct sockaddr_dl *,
+				    sa);
+
+				ifname = xmemdup0(sdl->sdl_data,
+				    sdl->sdl_nlen);
+			}
+			RT_ADVANCE(cp, sa);
+		}
+	}
+	if (ifname == NULL) {
+		return (ENXIO);
+	}
+	if (!gateway) {
+		*next_hop = *host;
+	}
+	*netdev_name = ifname;
+	VLOG_DBG("host " IP_FMT " next-hop " IP_FMT " if %s\n",
+	    IP_ARGS(host->s_addr), IP_ARGS(next_hop->s_addr), *netdev_name);
+
+	return (0);
+}
+
+static int
+netdev_solaris_get_status(const struct netdev *netdev_,
+    struct smap *smap OVS_UNUSED)
+{
+	const char	*netdev_name = netdev_get_name(netdev_);
+	int		error = EOPNOTSUPP;
+
+	VLOG_DBG("netdev_solaris_get_status %s", netdev_name);
+
+	/*
+	 * It looks like this is used to populate a column,
+	 * OVSREC_INTERFACE_COL_STATUS in the OVSDB. It doesn't appear
+	 * to be required though. If we wanted to return the driver name
+	 * then we could return that using libdladm.
+	 */
+	return (error);
+}
+
+/*
+ * Looks up the ARP table entry for 'ip' on 'netdev'.  If one exists and can be
+ * successfully retrieved, it stores the corresponding MAC address in 'mac' and
+ * returns 0.  Otherwise, it returns a positive errno value; in particular,
+ * ENXIO indicates that there is not ARP table entry for 'ip' on 'netdev'.
+ */
+static int
+netdev_solaris_arp_lookup(const struct netdev *netdev_,
+    ovs_be32 ip OVS_UNUSED, uint8_t mac[ETH_ADDR_LEN] OVS_UNUSED)
+{
+	const char		*netdev_name = netdev_get_name(netdev_);
+	int			error = 0;
+	struct xarpreq		ar;
+	struct sockaddr_in	*sin;
+
+	VLOG_DBG("netdev_solaris_arp_lookup %s", netdev_name);
+
+	bzero(&ar, sizeof (ar));
+	sin = ALIGNED_CAST(struct sockaddr_in *, &ar.xarp_pa);
+	sin->sin_addr.s_addr = ip;
+	sin->sin_family = AF_INET;
+	ar.xarp_ha.sdl_family = AF_LINK;
+
+	if (ioctl(sock4, SIOCGXARP, &ar) < 0) {
+		error = errno;
+		goto out;
+	}
+	if (!(ar.xarp_flags & ATF_COM)) {
+		errno = EOPNOTSUPP; /* XXX */
+		goto out;
+	}
+	memcpy(mac, LLADDR(&ar.xarp_ha), ETH_ADDR_LEN);
+out:
+	return (error);
+}
+
+static int
+lifr_to_nd_flags(int64_t lifrflags)
+{
+	enum netdev_flags nd_flags = 0;
+
+	if (lifrflags & IFF_UP) {
+		nd_flags |= NETDEV_UP;
+	}
+	if (lifrflags & IFF_PROMISC) {
+		nd_flags |= NETDEV_PROMISC;
+	}
+	if (lifrflags & IFF_LOOPBACK) {
+		nd_flags |= NETDEV_LOOPBACK;
+	}
+	return (nd_flags);
+}
+
+static int64_t
+nd_to_lifr_flags(enum netdev_flags nd_flags)
+{
+	int64_t lifrflags = 0;
+
+	if (nd_flags & NETDEV_UP) {
+		lifrflags |= IFF_UP;
+	}
+	if (nd_flags & NETDEV_PROMISC) {
+		lifrflags |= IFF_PROMISC;
+	}
+	if (nd_flags & NETDEV_LOOPBACK) {
+		lifrflags |= IFF_LOOPBACK;
+	}
+	return (lifrflags);
+}
+
+/*
+ * Retrieves the current set of flags on 'netdev' into '*old_flags'.  Then,
+ * turns off the flags that are set to 1 in 'off' and turns on the flags
+ * that are set to 1 in 'on'.  (No bit will be set to 1 in both 'off' and
+ * 'on'; that is, off & on == 0.)
+ */
+static int
+netdev_solaris_update_flags(struct netdev *netdev_, enum netdev_flags off,
+    enum netdev_flags on, enum netdev_flags *old_flagsp)
+{
+	const char		*netdev_name = netdev_get_name(netdev_);
+	struct lifreq		lifr;
+	int64_t			old_lifr_flags;
+	int64_t			new_lifr_flags;
+	int			error = 0;
+
+	VLOG_DBG("netdev_solaris_update_flags %s", netdev_name);
+
+	bzero(&lifr, sizeof (lifr));
+	(void) strncpy(lifr.lifr_name, netdev_name, sizeof (lifr.lifr_name));
+	if (ioctl(sock4, SIOCGLIFFLAGS, (caddr_t)&lifr) < 0) {
+		error = errno;
+		goto exit;
+	}
+	old_lifr_flags = lifr.lifr_flags;
+	*old_flagsp = lifr_to_nd_flags(old_lifr_flags);
+	new_lifr_flags = (old_lifr_flags & ~nd_to_lifr_flags(off)) |
+	    nd_to_lifr_flags(on);
+
+	if (new_lifr_flags == old_lifr_flags)
+		goto exit;
+
+	lifr.lifr_flags = new_lifr_flags;
+	if (ioctl(sock4, SIOCSLIFFLAGS, (caddr_t)&lifr) < 0) {
+		error = errno;
+		goto exit;
+	}
+	netdev_change_seq_changed(netdev_);
+
+exit:
+	return (error);
+}
+
+static int
+netdev_internal_get_stats(const struct netdev *netdev_ OVS_UNUSED,
+    struct netdev_stats *stats OVS_UNUSED)
+{
+	return (0);
+}
+
+static int
+netdev_internal_get_status(const struct netdev *netdev OVS_UNUSED,
+    struct smap *smap)
+{
+	smap_add(smap, "driver_name", "openvswitch");
+	return (0);
+}
+
+#define	NETDEV_SOLARIS_CLASS(NAME, CONSTRUCT, GET_STATS, SET_STATS,	\
+    GET_FEATURES, GET_STATUS)						\
+{									\
+	NAME,								\
+	netdev_solaris_init,						\
+	netdev_solaris_run,						\
+	netdev_solaris_wait,						\
+	netdev_solaris_alloc,						\
+	CONSTRUCT,							\
+	netdev_solaris_destruct,					\
+	netdev_solaris_dealloc,						\
+	NULL,								\
+	NULL,								\
+	NULL,								\
+	NULL,								\
+	NULL,								\
+	netdev_solaris_set_etheraddr,					\
+	netdev_solaris_get_etheraddr,					\
+	netdev_solaris_get_mtu,						\
+	netdev_solaris_set_mtu,						\
+	netdev_solaris_get_ifindex,					\
+	NULL,								\
+	NULL,								\
+	NULL,								\
+	GET_STATS,							\
+	SET_STATS,							\
+	GET_FEATURES,							\
+	NULL,								\
+	netdev_solaris_set_policing,					\
+	netdev_solaris_get_qos_types,					\
+	netdev_solaris_get_qos_capabilities,				\
+	netdev_solaris_get_qos,						\
+	netdev_solaris_set_qos,						\
+	netdev_solaris_get_queue,					\
+	netdev_solaris_set_queue,					\
+	netdev_solaris_delete_queue,					\
+	netdev_solaris_get_queue_stats,					\
+	netdev_solaris_queue_dump_start,				\
+	netdev_solaris_queue_dump_next,					\
+	netdev_solaris_queue_dump_done,					\
+	netdev_solaris_dump_queue_stats,				\
+	netdev_solaris_get_in4,						\
+	netdev_solaris_set_in4,						\
+	netdev_solaris_get_in6,						\
+	netdev_solaris_add_router,					\
+	netdev_solaris_get_next_hop,					\
+	GET_STATUS,							\
+	netdev_solaris_arp_lookup,					\
+	netdev_solaris_update_flags,					\
+	netdev_solaris_configure_uplink,				\
+	netdev_solaris_is_uplink,					\
+	NULL,								\
+	NULL,								\
+	NULL,								\
+	NULL,								\
+	NULL,								\
+	NULL,								\
+	NULL								\
+}
+
+const struct netdev_class netdev_solaris_class =
+    NETDEV_SOLARIS_CLASS(
+	"system",
+	netdev_solaris_construct,
+	netdev_solaris_get_stats,
+	NULL,				/* set_stats */
+	netdev_solaris_get_features,
+	netdev_solaris_get_status);
+
+const struct netdev_class netdev_internal_class =
+    NETDEV_SOLARIS_CLASS(
+	"internal",
+	netdev_solaris_construct,
+	netdev_internal_get_stats,
+	NULL,				/* set_stats */
+	NULL,				/* get_features */
+	netdev_internal_get_status);
+
+/* Solaris HTB traffic control class */
+#define	HTB_N_QUEUES	0xf000
+
+struct htb {
+	struct tc	tc;
+	unsigned int	max_rate;	/* In bytes/s. */
+};
+
+struct htb_class {
+	struct tc_queue	tc_queue;
+	unsigned int	min_rate;	/* In bytes/s */
+	unsigned int	max_rate;	/* In bytes/s */
+	unsigned int	burst;		/* In bytes/s -- unused */
+	unsigned int	priority;	/* Lower value is higher priority */
+};
+
+
+/*
+ * Create an HTB qdisc.
+ *
+ * Equivalent to "tc qdisc add dev <dev> root handle 1: htb default 1".
+ */
+static int
+htb_setup_qdisc__(struct netdev *netdev)
+{
+	VLOG_DBG("htb_setup_qdisc__ device %s", netdev->name);
+	tc_del_qdisc(netdev);
+
+	return (0);
+}
+
+static struct htb *
+htb_get__(const struct netdev *netdev_)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+
+	VLOG_DBG("htb_get__ device %s", netdev_name);
+	return (CONTAINER_OF(netdev->tc, struct htb, tc));
+}
+
+static void
+htb_install__(struct netdev *netdev_, uint64_t max_rate)
+{
+	const char		*netdev_name = netdev_get_name(netdev_);
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	struct htb		*htb;
+
+	VLOG_DBG("htb_install__ device %s", netdev_name);
+	htb = xmalloc(sizeof (*htb));
+	tc_init(&htb->tc, &tc_ops_htb);
+	htb->max_rate = max_rate;
+
+	netdev->tc = &htb->tc;
+	VLOG_DBG("htb_install__ device %s TC configured ", netdev_name);
+}
+
+static int
+htb_setup_class__(struct netdev *netdev, unsigned int handle OVS_UNUSED,
+    unsigned int parent OVS_UNUSED,
+    struct htb_class *class OVS_UNUSED)
+{
+	VLOG_DBG("htb_setup_class__ device %s", netdev->name);
+
+	return (0);
+}
+
+static void
+htb_parse_qdisc_details__(struct netdev *netdev_,
+			const struct smap *details, struct htb_class *hc)
+{
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	const char		*netdev_name = netdev_get_name(netdev_);
+	const char		*max_rate_s;
+	const char		*physname;
+	char			buffer[DLADM_PROP_VAL_MAX];
+	int			error = 0;
+
+	VLOG_DBG("htb_parse_qdisc_details__ device %s", netdev_name);
+
+	/*
+	 * Initialize in case of early return.
+	 */
+	hc->max_rate = 0;
+	hc->min_rate = 0;
+	hc->burst = 0;
+	hc->priority = 0;
+
+	if (netdev_solaris_is_uplink(netdev_)) {
+		physname = netdev_name;
+	} else {
+		error = solaris_get_dllower(netdev_name, buffer,
+		    sizeof (buffer));
+		if (error != 0)
+			return;
+		physname = buffer;
+	}
+
+	max_rate_s = smap_get(details, "max-rate");
+	hc->max_rate = max_rate_s ? strtoull(max_rate_s, NULL, 10) / 8 : 0;
+	if (!hc->max_rate) {
+		enum netdev_features current;
+
+		netdev_solaris_read_features(netdev, physname);
+		current = !netdev->get_features_error ? netdev->current : 0;
+		hc->max_rate = netdev_features_to_bps(current,
+		    100 * 1000 * 1000) / 8;
+	}
+}
+
+static int
+htb_tc_install(struct netdev *netdev, const struct smap *details)
+{
+	int error;
+
+	VLOG_DBG("htb_tc_install device %s", netdev->name);
+
+	error = htb_setup_qdisc__(netdev);
+	if (!error) {
+		struct htb_class hc;
+
+		htb_parse_qdisc_details__(netdev, details, &hc);
+		error = htb_setup_class__(netdev, 0, 0, &hc);
+		if (!error) {
+			htb_install__(netdev, hc.max_rate);
+		}
+	}
+	return (error);
+}
+
+static void
+htb_tc_destroy(struct tc *tc)
+{
+	struct htb		*htb = CONTAINER_OF(tc, struct htb, tc);
+	struct htb_class	*hc, *next;
+
+	HMAP_FOR_EACH_SAFE(hc, next, tc_queue.hmap_node, &htb->tc.queues) {
+		hmap_remove(&htb->tc.queues, &hc->tc_queue.hmap_node);
+		free(hc);
+	}
+	tc_destroy(tc);
+	free(htb);
+}
+
+static int
+htb_qdisc_get(const struct netdev *netdev, struct smap *details)
+{
+	const struct htb *htb = htb_get__(netdev);
+
+	VLOG_DBG("htb_qdisc_get device %s", netdev->name);
+	smap_add_format(details, "max-rate", "%llu", 8ULL * htb->max_rate);
+	return (0);
+}
+
+static int
+htb_qdisc_set(struct netdev *netdev, const struct smap *details)
+{
+	struct htb_class	hc;
+	int			error;
+
+	VLOG_DBG("htb_qdisc_set device %s", netdev->name);
+	htb_parse_qdisc_details__(netdev, details, &hc);
+	/* Solaris: don't care about the handles */
+	error = htb_setup_class__(netdev, 0, 0, &hc);
+	if (!error) {
+		htb_get__(netdev)->max_rate = hc.max_rate;
+	}
+
+	return (error);
+}
+
+static struct htb_class *
+htb_class_cast__(const struct tc_queue *queue)
+{
+	return (CONTAINER_OF(queue, struct htb_class, tc_queue));
+}
+
+static int
+htb_class_get(const struct netdev *netdev,
+    const struct tc_queue *queue, struct smap *details)
+{
+	const struct htb_class *hc = htb_class_cast__(queue);
+
+	VLOG_DBG("htb_class_get device %s", netdev->name);
+
+	if (hc->max_rate > 0)
+		smap_add_format(details, "max-rate", "%llu",
+		    8ULL * hc->max_rate);
+
+	VLOG_DBG("htb_class_get device done");
+	return (0);
+}
+
+/* Solaris: currently, min-rate, burst and priority are not supported */
+static int
+htb_parse_class_details__(struct netdev *netdev,
+    const struct smap *details, struct htb_class *hc)
+{
+	const struct htb	*htb = htb_get__(netdev);
+	const char		*max_rate_s = smap_get(details, "max-rate");
+
+	VLOG_DBG("htb_parse_class_details__ device %s", netdev->name);
+
+	/* max-rate */
+	hc->max_rate = (max_rate_s
+	    ? strtoull(max_rate_s, NULL, 10) / 8
+	    : htb->max_rate);
+	VLOG_DBG("htb_parse_class_details__ device max_rate is %u",
+	    hc->max_rate);
+
+	return (0);
+}
+
+static void
+htb_update_queue__(struct netdev *netdev, unsigned int queue_id,
+    const struct htb_class *hc)
+{
+	struct htb		*htb = htb_get__(netdev);
+	size_t			hash = hash_int(queue_id, 0);
+	struct tc_queue		*queue;
+	struct htb_class	*hcp;
+
+	VLOG_DBG("htb_update_queue__ %s", netdev->name);
+
+	queue = tc_find_queue__(netdev, queue_id, hash);
+	if (queue) {
+		hcp = htb_class_cast__(queue);
+	} else {
+		hcp = xmalloc(sizeof (*hcp));
+		queue = &hcp->tc_queue;
+		queue->queue_id = queue_id;
+		queue->created = time_msec();
+		hmap_insert(&htb->tc.queues, &queue->hmap_node, hash);
+	}
+
+	hcp->max_rate = hc->max_rate;
+}
+
+static int
+htb_class_set(struct netdev *netdev, unsigned int queue_id,
+    const struct smap *details)
+{
+	struct htb_class	hc;
+	int			error;
+
+	VLOG_DBG("htb_class_set %s", netdev->name);
+
+	error = htb_parse_class_details__(netdev, details, &hc);
+	if (error) {
+		return (error);
+	}
+
+	error = htb_setup_class__(netdev, 0, 0, &hc);
+	if (error) {
+		return (error);
+	}
+
+	htb_update_queue__(netdev, queue_id, &hc);
+	return (0);
+}
+
+static int
+htb_class_delete(struct netdev *netdev, struct tc_queue *queue)
+{
+	struct htb_class	*hc = htb_class_cast__(queue);
+	struct htb		*htb = htb_get__(netdev);
+	int			error;
+
+	VLOG_DBG("htb_class_delete %s", netdev->name);
+	error = tc_delete_class(netdev, 0);
+	if (!error) {
+		hmap_remove(&htb->tc.queues, &hc->tc_queue.hmap_node);
+		free(hc);
+	}
+	return (error);
+}
+
+/*
+ * Used to get existing configuration for qdiscs in the kernel, not used in
+ * Solaris.
+ */
+static int
+htb_tc_load(struct netdev *netdev, struct ofpbuf *nlmsg OVS_UNUSED)
+{
+	VLOG_DBG("htb_tc_load %s", netdev->name);
+
+	return (0);
+}
+
+static const struct tc_ops tc_ops_htb = {
+	"htb",			/* linux_name */
+	"linux-htb",		/* ovs_name */
+	HTB_N_QUEUES,		/* n_queues */
+	htb_tc_install,
+	htb_tc_load,
+	htb_tc_destroy,
+	htb_qdisc_get,
+	htb_qdisc_set,
+	htb_class_get,
+	htb_class_set,
+	htb_class_delete,
+	NULL,
+	NULL
+};
+
+/*
+ * The default traffic control class.
+ *
+ * This class represents the default, unnamed Linux qdisc.  It corresponds to
+ * the "" (empty string) QoS type in the OVS database.
+ */
+static void
+default_install__(struct netdev *netdev_)
+{
+	const char		*netdev_name = netdev_get_name(netdev_);
+	struct netdev_solaris	*netdev = netdev_solaris_cast(netdev_);
+	static const struct tc	tc = TC_INITIALIZER(&tc, &tc_ops_default);
+
+	VLOG_DBG("default_install__ device %s", netdev_name);
+
+	/*
+	 * Nothing but a tc class implementation is allowed to write to a tc.
+	 * This class never does that, so we can legitimately use a const tc
+	 * object.
+	 */
+	netdev->tc = CONST_CAST(struct tc *, &tc);
+}
+
+static int
+default_tc_install(struct netdev *netdev,
+    const struct smap *details OVS_UNUSED)
+{
+	VLOG_DBG("default_tc_install device %s", netdev->name);
+	default_install__(netdev);
+	return (0);
+}
+
+static int
+default_tc_load(struct netdev *netdev, struct ofpbuf *nlmsg OVS_UNUSED)
+{
+	VLOG_DBG("default_tc_load device %s", netdev->name);
+	default_install__(netdev);
+	return (0);
+}
+
+static const struct tc_ops tc_ops_default = {
+	NULL,			/* linux_name */
+	"",			/* ovs_name */
+	0,			/* n_queues */
+	default_tc_install,
+	default_tc_load,
+	NULL,			/* tc_destroy */
+	NULL,			/* qdisc_get */
+	NULL,			/* qdisc_set */
+	NULL,			/* class_get */
+	NULL,			/* class_set */
+	NULL,			/* class_delete */
+	NULL,			/* class_get_stats */
+	NULL			/* class_dump_stats */
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/lib/netdev-solaris.h	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ */
+
+/*
+ * Copyright (c) 2011, 2013, 2014 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef	NETDEV_SOLARIS_H
+#define	NETDEV_SOLARIS_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/*
+ * These functions are Solaris specific, so they should be used directly only by
+ * Solaris-specific code.
+ */
+
+struct netdev;
+
+int netdev_create_impl_etherstub(void);
+void netdev_delete_impl_etherstub(void);
+boolean_t netdev_impl_etherstub_exists(void);
+
+#endif	/* NETDEV_SOLARIS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/lib/route-table-solaris.c	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ */
+
+/*
+ * Copyright (c) 2012 Ed Maste. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "route-table.h"
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+#include <netinet/in.h>
+
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static int pid;
+static unsigned int register_count = 0;
+
+#define	ROUNDUP_LONG(a) ((a) > 0 ? (1 + (((a) - 1) | (sizeof (long) - 1))) : \
+    sizeof (long))
+#define	RT_ADVANCE(x, n) ((x) += ROUNDUP_LONG(salen(n)))
+#define	BUF_SIZE 2048
+
+typedef struct rtmsg {
+	struct rt_msghdr	m_rtm;
+	char			m_space[BUF_SIZE];
+} rtmsg_t;
+
+static int
+salen(const struct sockaddr *sa)
+{
+	switch (sa->sa_family) {
+	case AF_INET:
+		return (sizeof (struct sockaddr_in));
+	case AF_LINK:
+		return (sizeof (struct sockaddr_dl));
+	case AF_INET6:
+		return (sizeof (struct sockaddr_in6));
+	default:
+		return (sizeof (struct sockaddr));
+	}
+}
+
+bool
+route_table_get_name(ovs_be32 ip, char name[IFNAMSIZ])
+{
+	rtmsg_t			m_rtmsg;
+	struct rt_msghdr	*rtm = &m_rtmsg.m_rtm;
+	char			*cp = m_rtmsg.m_space;
+	int			rtsock_fd;
+	struct sockaddr_in	sin;
+	struct sockaddr_dl	sdl;
+	ssize_t			ssz;
+	static int		seq;
+	int			rlen;
+	int			i;
+	int			l;
+
+	rtsock_fd = socket(PF_ROUTE, SOCK_RAW, 0);
+	if (rtsock_fd == -1)
+		return (false);
+
+	memset(&sin, 0, sizeof (sin));
+	sin.sin_family = AF_INET;
+	sin.sin_port = 0;
+	sin.sin_addr.s_addr = ip;
+
+	memset(&sdl, 0, sizeof (sdl));
+	sdl.sdl_family = AF_LINK;
+
+	memset(&m_rtmsg, 0, sizeof (m_rtmsg));
+	rtm->rtm_version = RTM_VERSION;
+	rtm->rtm_type = RTM_GET;
+	rtm->rtm_addrs = RTA_DST|RTA_IFP;
+	rtm->rtm_seq = ++seq;
+
+	l = ROUNDUP_LONG(sizeof (struct sockaddr_in));
+	(void) memcpy(cp, &sin, l);
+	cp += l;
+
+	l = ROUNDUP_LONG(sizeof (struct sockaddr_dl));
+	(void) memcpy(cp, &sdl, l);
+	cp += l;
+
+	rtm->rtm_msglen = l = cp - (char *)&m_rtmsg;
+	if ((rlen = write(rtsock_fd, (char *)&m_rtmsg, l)) < l) {
+		close(rtsock_fd);
+		return (false);
+	}
+
+	do {
+		ssz = read(rtsock_fd, &m_rtmsg, sizeof (m_rtmsg));
+	} while (ssz > 0 && (rtm->rtm_seq != seq || rtm->rtm_pid != pid));
+	close(rtsock_fd);
+
+	if (ssz < 0)
+		return (false);
+
+	cp = (void *)&m_rtmsg.m_space;
+	for (i = 1; i; i <<= 1) {
+		if ((rtm->rtm_addrs & i) != 0) {
+			const struct sockaddr *sa = (const void *)cp;
+
+			if ((i == RTA_IFP) && sa->sa_family == AF_LINK) {
+				const struct sockaddr_dl * const sdlp =
+				    ALIGNED_CAST(const struct sockaddr_dl *,
+				    sa);
+				int namelen;
+
+				namelen = sdlp->sdl_nlen;
+				if (namelen > IFNAMSIZ - 1)
+					namelen = IFNAMSIZ - 1;
+				memcpy(name, sdlp->sdl_data, namelen);
+				name[namelen] = '\0';
+				return (true);
+			}
+			RT_ADVANCE(cp, sa);
+		}
+	}
+	return (false);
+}
+
+uint64_t
+route_table_get_change_seq(void)
+{
+	return (0);
+}
+
+void
+route_table_register(void)
+{
+	if (!register_count) {
+		pid = getpid();
+	}
+
+	register_count++;
+}
+
+void
+route_table_unregister(void)
+{
+	register_count--;
+}
+
+void
+route_table_run(void)
+{
+}
+
+void
+route_table_wait(void)
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/lib/util-solaris.c	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,3789 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <alloca.h>
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <strings.h>
+#include <stropts.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <inet/ip.h>
+#include <inet/ip6.h>
+#include <arpa/inet.h>
+#include <sys/sockio.h>
+#include <libdlpi.h>
+#include <libdladm.h>
+#include <libdllink.h>
+#include <zone.h>
+#include <net/if_types.h>
+#include <inet/arp.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <rad/radclient.h>
+#include <rad/client/1/dlmgr.h>
+
+#undef	IP_VERSION
+#include "netlink-protocol.h"
+#include "netlink.h"
+#include "flow.h"
+#include "packets.h"
+#include "util-solaris.h"
+#include "util.h"
+#include "dpif-solaris.h"
+
+static rc_conn_t *rad_conn = NULL;
+static boolean_t b_true = B_TRUE;
+
+typedef struct {
+	uint_t		ifsp_ppa;	/* Physical Point of Attachment */
+	uint_t		ifsp_lun;	/* Logical Unit number */
+	boolean_t	ifsp_lunvalid;	/* TRUE if lun is valid */
+	char		ifsp_devnm[LIFNAMSIZ]; /* only the device name */
+} ifspec_t;
+
+static int
+extract_uint(const char *valstr, uint_t *val)
+{
+	char		*ep;
+	unsigned long	ul;
+
+	errno = 0;
+	ul = strtoul(valstr, &ep, 10);
+	if (errno != 0 || *ep != '\0' || ul > UINT_MAX)
+		return (-1);
+	*val = (uint_t)ul;
+	return (0);
+}
+
+/*
+ * Given a token with a logical unit spec, return the logical unit converted
+ * to a uint_t.
+ *
+ * Returns: 0 for success, nonzero if an error occurred. errno is set if
+ * necessary.
+ */
+static int
+getlun(const char *bp, size_t bpsize, uint_t *lun)
+{
+	const char	*ep = &bp[bpsize - 1];
+	const char	*tp;
+
+	/* Lun must be all digits */
+	for (tp = ep; tp > bp && isdigit(*tp); tp--)
+		/* Null body */;
+
+	if (tp == ep || tp != bp || extract_uint(bp + 1, lun) != 0) {
+		errno = EINVAL;
+		return (-1);
+	}
+	return (0);
+}
+
+/*
+ * Given a single token ending with a ppa spec, return the ppa spec converted
+ * to a uint_t.
+ *
+ * Returns: 0 for success, nonzero if an error occurred. errno is set if
+ * necessary.
+ */
+static int
+getppa(const char *bp, size_t bpsize, uint_t *ppa)
+{
+	const char	*ep = &bp[bpsize - 1];
+	const char	*tp;
+
+	for (tp = ep; tp >= bp && isdigit(*tp); tp--)
+		/* Null body */;
+
+	/*
+	 * If the device name does not end with a digit or the device
+	 * name is a sequence of numbers or a PPA contains a leading
+	 * zero, return error.
+	 */
+	if (tp == ep || tp < bp || ((ep - tp) > 1 && *(tp + 1) == '0'))
+		goto fail;
+
+	if (extract_uint(tp + 1, ppa) != 0)
+		goto fail;
+
+	/* max value of PPA is 4294967294, which is (UINT_MAX - 1) */
+	if (*ppa > UINT_MAX - 1)
+		goto fail;
+	return (0);
+fail:
+	errno = EINVAL;
+	return (-1);
+}
+
+/*
+ * Given a `linkname' of the form drv(ppa), parse it into `driver' and `ppa'.
+ * If the `dsize' for the `driver' is not atleast MAXLINKNAMELEN then part of
+ * the driver name will be copied to `driver'.
+ *
+ * This function also validates driver name and PPA and therefore callers can
+ * call this function with `driver' and `ppa' set to NULL, to just verify the
+ * linkname.
+ */
+boolean_t
+dlparse_drvppa(const char *linknamep, char *driver, uint_t dsize, uint_t *ppa)
+{
+	char	*tp;
+	char    linkname[MAXLINKNAMELEN];
+	size_t	len;
+	uint_t	lppa;
+
+	if (linknamep == NULL || linknamep[0] == '\0')
+		goto fail;
+
+	len = strlcpy(linkname, linknamep, MAXLINKNAMELEN);
+	if (len >= MAXLINKNAMELEN)
+		goto fail;
+
+	/* Get PPA */
+	if (getppa(linkname, len, &lppa) != 0)
+		return (_B_FALSE);
+
+	/* strip the ppa off of the linkname, if present */
+	for (tp = &linkname[len - 1]; tp >= linkname && isdigit(*tp); tp--)
+		*tp = '\0';
+
+	/*
+	 * Now check for the validity of the device name. The legal characters
+	 * in a device name are: alphanumeric (a-z,  A-Z,  0-9), underscore
+	 * ('_'), hyphen ('-'), and period ('.'). The first character
+	 * of the device name cannot be a digit and should be an alphabetic
+	 * character.
+	 */
+	if (!isalpha(linkname[0]))
+		goto fail;
+	for (tp = linkname + 1; *tp != '\0'; tp++) {
+		if (!isalnum(*tp) && *tp != '_' && *tp != '-' && *tp != '.')
+			goto fail;
+	}
+
+	if (driver != NULL)
+		(void) strlcpy(driver, linkname, dsize);
+
+	if (ppa != NULL)
+		*ppa = lppa;
+
+	return (_B_TRUE);
+fail:
+	errno = EINVAL;
+	return (_B_FALSE);
+}
+
+/*
+ * Given an IP interface name, which is either a
+ *	- datalink name (which is driver name plus PPA), for e.g. bge0 or
+ *	- datalink name plus a logical interface identifier (delimited by ':'),
+ *		for e.g. bge0:34
+ * the following function validates its form and decomposes the contents into
+ * ifspec_t.
+ *
+ * Returns _B_TRUE for success, otherwise _B_FALSE is returned.
+ */
+static boolean_t
+ifparse_ifspec(const char *ifname, ifspec_t *ifsp)
+{
+	char	*lp;
+	char	ifnamecp[LIFNAMSIZ];
+
+	if (ifname == NULL || ifname[0] == '\0' ||
+	    strlcpy(ifnamecp, ifname, LIFNAMSIZ) >= LIFNAMSIZ) {
+		errno = EINVAL;
+		return (_B_FALSE);
+	}
+
+	ifsp->ifsp_lunvalid = _B_FALSE;
+
+	/* Any logical units? */
+	lp = strchr(ifnamecp, ':');
+	if (lp != NULL) {
+		if (getlun(lp, strlen(lp), &ifsp->ifsp_lun) != 0)
+			return (_B_FALSE);
+		*lp = '\0';
+		ifsp->ifsp_lunvalid = _B_TRUE;
+	}
+
+	return (dlparse_drvppa(ifnamecp, ifsp->ifsp_devnm,
+	    sizeof (ifsp->ifsp_devnm), &ifsp->ifsp_ppa));
+}
+
+/*
+ * Issues the ioctl SIOCSLIFNAME to kernel.
+ */
+static int
+slifname(const char *ifname, uint64_t flags, int fd)
+{
+	struct lifreq	lifr;
+	int		status = 0;
+	ifspec_t	ifsp;
+	boolean_t	valid_if;
+
+	bzero(&lifr, sizeof (lifr));
+
+	/* We should have already validated the interface name. */
+	valid_if = ifparse_ifspec(ifname, &ifsp);
+	ovs_assert(valid_if);
+
+	lifr.lifr_ppa = ifsp.ifsp_ppa;
+	lifr.lifr_flags = flags;
+	(void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
+	if (ioctl(fd, SIOCSLIFNAME, &lifr) == -1) {
+		status = errno;
+	}
+
+	return (status);
+}
+
+/*
+ * Wrapper for sending a non-transparent I_STR ioctl().
+ * Returns: Result from ioctl().
+ */
+static int
+strioctl(int s, int cmd, char *buf, uint_t buflen)
+{
+	struct strioctl ioc;
+
+	(void) memset(&ioc, 0, sizeof (ioc));
+	ioc.ic_cmd = cmd;
+	ioc.ic_timout = 0;
+	ioc.ic_len = buflen;
+	ioc.ic_dp = buf;
+
+	return (ioctl(s, I_STR, (char *)&ioc));
+}
+
+/*
+ * Issues the ioctl SIOCSLIFNAME to kernel on the given ARP stream fd.
+ */
+static int
+slifname_arp(const char *ifname, uint64_t flags, int fd)
+{
+	struct lifreq	lifr;
+	ifspec_t	ifsp;
+
+	bzero(&lifr, sizeof (lifr));
+	(void) ifparse_ifspec(ifname, &ifsp);
+	lifr.lifr_ppa = ifsp.ifsp_ppa;
+	lifr.lifr_flags = flags;
+	(void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
+	/*
+	 * Tell ARP the name and unit number for this interface.
+	 * Note that arp has no support for transparent ioctls.
+	 */
+	if (strioctl(fd, SIOCSLIFNAME, (char *)&lifr,
+	    sizeof (lifr)) == -1) {
+		return (errno);
+	}
+	return (0);
+}
+
+/*
+ * Open "/dev/udp{,6}" for use as a multiplexor to PLINK the interface stream
+ * under. We use "/dev/udp" instead of "/dev/ip" since STREAMS will not let
+ * you PLINK a driver under itself, and "/dev/ip" is typically the driver at
+ * the bottom of the stream for tunneling interfaces.
+ */
+static int
+open_arp_on_udp(const char *udp_dev_name, int *fd)
+{
+	int err;
+
+	if ((*fd = open(udp_dev_name, O_RDWR)) == -1)
+		return (errno);
+	/*
+	 * Pop off all undesired modules (note that the user may have
+	 * configured autopush to add modules above udp), and push the
+	 * arp module onto the resulting stream. This is used to make
+	 * IP+ARP be able to atomically track the muxid for the I_PLINKed
+	 * STREAMS, thus it isn't related to ARP running the ARP protocol.
+	 */
+	while (ioctl(*fd, I_POP, 0) != -1)
+		;
+	if (errno == EINVAL && ioctl(*fd, I_PUSH, ARP_MOD_NAME) != -1)
+		return (0);
+
+	err = errno;
+	(void) close(*fd);
+	return (err);
+}
+
+static char *
+solaris_proto2str(uint8_t protocol)
+{
+	if (protocol == IPPROTO_TCP)
+		return ("tcp");
+	if (protocol == IPPROTO_UDP)
+		return ("udp");
+	if (protocol == IPPROTO_SCTP)
+		return ("sctp");
+	if (protocol == IPPROTO_ICMPV6)
+		return ("icmpv6");
+	if (protocol == IPPROTO_ICMP)
+		return ("icmp");
+	else
+		return ("");
+}
+
+static uint8_t
+solaris_str2proto(const char *protostr)
+{
+	if (strcasecmp(protostr, "tcp") == 0)
+		return (IPPROTO_TCP);
+	else if (strcasecmp(protostr, "udp") == 0)
+		return (IPPROTO_UDP);
+	else if (strcasecmp(protostr, "sctp") == 0)
+		return (IPPROTO_SCTP);
+	else if (strcasecmp(protostr, "icmpv6") == 0)
+		return (IPPROTO_ICMPV6);
+	else if (strcasecmp(protostr, "icmp") == 0)
+		return (IPPROTO_ICMP);
+	return (0);
+}
+
+/*
+ * Returns the flags value for the logical interface in `lifname'
+ * in the buffer pointed to by `flags'.
+ */
+static int
+solaris_get_flags(int sock, const char *lifname, uint64_t *flags)
+{
+	struct lifreq	lifr;
+
+	bzero(&lifr, sizeof (lifr));
+	(void) strlcpy(lifr.lifr_name, lifname, sizeof (lifr.lifr_name));
+
+	if (ioctl(sock, SIOCGLIFFLAGS, (caddr_t)&lifr) < 0)
+		return (errno);
+	*flags = lifr.lifr_flags;
+
+	return (0);
+}
+
+/*
+ * For a given interface name, checks if IP interface exists.
+ */
+int
+solaris_if_enabled(int sock, const char *ifname, uint64_t *flags)
+{
+	struct lifreq	lifr;
+	int		error = 0;
+
+	bzero(&lifr, sizeof (lifr));
+	(void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
+	if (ioctl(sock, SIOCGLIFFLAGS, (caddr_t)&lifr) != 0)
+		error = errno;
+
+	if (error == 0 && flags != NULL)
+		*flags = lifr.lifr_flags;
+
+	return (error);
+}
+
+int
+solaris_unplumb_if(int sock, const char *ifname, sa_family_t af)
+{
+	int		ip_muxid;
+	int		arp_muxid;
+	int		mux_fd = -1;
+	int		muxid_fd = -1;
+	char		*udp_dev_name;
+	uint64_t	ifflags = 0;
+	boolean_t	changed_arp_muxid = B_FALSE;
+	struct lifreq	lifr;
+	int		ret = 0;
+	boolean_t	v6 = (af == AF_INET6);
+
+	/*
+	 * We used /dev/udp or udp6 to set up the mux. So we have to use
+	 * the same now for PUNLINK also.
+	 */
+	udp_dev_name = (v6 ?  UDP6_DEV_NAME : UDP_DEV_NAME);
+	if ((muxid_fd = open(udp_dev_name, O_RDWR)) == -1) {
+		ret = errno;
+		goto done;
+	}
+	ret = open_arp_on_udp(udp_dev_name, &mux_fd);
+	if (ret != 0)
+		goto done;
+
+	bzero(&lifr, sizeof (lifr));
+	(void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
+	if (ioctl(muxid_fd, SIOCGLIFMUXID, (caddr_t)&lifr) < 0) {
+		ret = errno;
+		goto done;
+	}
+	arp_muxid = lifr.lifr_arp_muxid;
+	ip_muxid = lifr.lifr_ip_muxid;
+
+	ret = solaris_get_flags(sock, ifname, &ifflags);
+	if (ret != 0)
+		goto done;
+	/*
+	 * We don't have a good way of knowing whether the arp stream is
+	 * plumbed. We can't rely on IFF_NOARP because someone could
+	 * have turned it off later using "ifconfig xxx -arp".
+	 */
+	if (arp_muxid != 0) {
+		if (ioctl(mux_fd, I_PUNLINK, arp_muxid) < 0) {
+			if ((errno == EINVAL) &&
+			    (ifflags & (IFF_NOARP | IFF_IPV6))) {
+				/*
+				 * Some plumbing utilities set the muxid to
+				 * -1 or some invalid value to signify that
+				 * there is no arp stream. Set the muxid to 0
+				 * before trying to unplumb the IP stream.
+				 * IP does not allow the IP stream to be
+				 * unplumbed if it sees a non-null arp muxid,
+				 * for consistency of IP-ARP streams.
+				 */
+				lifr.lifr_arp_muxid = 0;
+				(void) ioctl(muxid_fd, SIOCSLIFMUXID,
+				    (caddr_t)&lifr);
+				changed_arp_muxid = B_TRUE;
+			}
+		}
+	}
+
+	if (ioctl(mux_fd, I_PUNLINK, ip_muxid) < 0) {
+		if (changed_arp_muxid) {
+			/*
+			 * Some error occurred, and we need to restore
+			 * everything back to what it was.
+			 */
+			ret = errno;
+			lifr.lifr_arp_muxid = arp_muxid;
+			lifr.lifr_ip_muxid = ip_muxid;
+			(void) ioctl(muxid_fd, SIOCSLIFMUXID, (caddr_t)&lifr);
+		}
+	}
+done:
+	if (muxid_fd != -1)
+		(void) close(muxid_fd);
+	if (mux_fd != -1)
+		(void) close(mux_fd);
+
+	return (ret);
+}
+
+/*
+ * Plumbs the interface `ifname'.
+ */
+int
+solaris_plumb_if(int sock, const char *ifname, sa_family_t af)
+{
+	int		ip_muxid;
+	int		mux_fd = -1, ip_fd, arp_fd;
+	char		*udp_dev_name;
+	dlpi_handle_t	dh_arp = NULL, dh_ip = NULL;
+	uint64_t	ifflags;
+	uint_t		dlpi_flags;
+	int		status = 0;
+	const char	*linkname;
+	int		ret;
+
+	if (solaris_if_enabled(sock, ifname, NULL) == 0) {
+		status = EEXIST;
+		goto done;
+	}
+
+	dlpi_flags = DLPI_NOATTACH;
+	linkname = ifname;
+
+	/*
+	 * We use DLPI_NOATTACH because the ip module will do the attach
+	 * itself for DLPI style-2 devices.
+	 */
+	ret = dlpi_open(linkname, &dh_ip, dlpi_flags);
+	if (ret != DLPI_SUCCESS) {
+		ret = (ret == DL_SYSERR) ? errno : EOPNOTSUPP;
+		goto done;
+	}
+
+	ip_fd = dlpi_fd(dh_ip);
+	if (ioctl(ip_fd, I_PUSH, IP_MOD_NAME) == -1) {
+		status = errno;
+		goto done;
+	}
+
+	if (af == AF_INET) {
+		ifflags = IFF_IPV4;
+	} else {
+		ifflags = IFF_IPV6;
+		ifflags |= IFF_NOLINKLOCAL;
+	}
+
+	status = slifname(ifname, ifflags, ip_fd);
+	if (status != 0)
+		goto done;
+
+	/* Get the full set of existing flags for this stream */
+	status = solaris_get_flags(sock, ifname, &ifflags);
+	if (status != 0)
+		goto done;
+
+	udp_dev_name = (af == AF_INET6 ? UDP6_DEV_NAME : UDP_DEV_NAME);
+	status = open_arp_on_udp(udp_dev_name, &mux_fd);
+	if (status != 0)
+		goto done;
+
+	/* Check if arp is not needed */
+	if (ifflags & (IFF_NOARP|IFF_IPV6)) {
+		/*
+		 * PLINK the interface stream so that the application can exit
+		 * without tearing down the stream.
+		 */
+		if ((ip_muxid = ioctl(mux_fd, I_PLINK, ip_fd)) == -1)
+			status = errno;
+		goto done;
+	}
+
+	/*
+	 * This interface does use ARP, so set up a separate stream
+	 * from the interface to ARP.
+	 *
+	 * We use DLPI_NOATTACH because the arp module will do the attach
+	 * itself for DLPI style-2 devices.
+	 */
+	ret = dlpi_open(linkname, &dh_arp, dlpi_flags);
+	if (ret != DLPI_SUCCESS) {
+		ret = (ret == DL_SYSERR) ? errno : EOPNOTSUPP;
+		goto done;
+	}
+
+	arp_fd = dlpi_fd(dh_arp);
+	if (ioctl(arp_fd, I_PUSH, ARP_MOD_NAME) == -1) {
+		status = errno;
+		goto done;
+	}
+	status = slifname_arp(ifname, ifflags, arp_fd);
+	if (status != 0)
+		goto done;
+	/*
+	 * PLINK the IP and ARP streams so that we can exit
+	 * without tearing down the stream.
+	 */
+	if ((ip_muxid = ioctl(mux_fd, I_PLINK, ip_fd)) == -1) {
+		status = errno;
+		goto done;
+	}
+
+	if (ioctl(mux_fd, I_PLINK, arp_fd) < 0) {
+		status = errno;
+		(void) ioctl(mux_fd, I_PUNLINK, ip_muxid);
+	}
+
+done:
+	dlpi_close(dh_ip);
+	if (dh_arp != NULL)
+		dlpi_close(dh_arp);
+	if (mux_fd != -1)
+		(void) close(mux_fd);
+
+	return (status);
+}
+
+int
+solaris_init_rad()
+{
+	if (rad_conn == NULL) {
+		rad_conn = rc_connect_unix(NULL, B_TRUE, NULL);
+		if (rad_conn == NULL) {
+			return (ENODEV); /* Not sure what to return */
+		}
+	}
+	return (0);
+}
+
+static rc_err_t
+test_dlclass(const char *key, dlmgr_DLValue_t *dlval, void *arg)
+{
+	if (strcmp(key, "class") == 0)
+		(void) strlcpy((char *)arg, dlval->ddlv_sval, DLADM_STRSIZE);
+	dlmgr_DLValue_free(dlval);
+	return (RCE_OK);
+}
+
+static rc_err_t
+test_dllower(const char *key, dlmgr_DLValue_t *dlval, void *arg)
+{
+	/* TODO(gmoodalb): seems like we need physname/devname here */
+	if (strcmp(key, "over") == 0) {
+		(void) strlcpy((char *)arg, dlval->ddlv_slist[0],
+		    DLADM_STRSIZE);
+	}
+	dlmgr_DLValue_free(dlval);
+	return (RCE_OK);
+}
+
+static int
+solaris_get_dlinfo(const char *netdev_name, char *info_val, size_t info_len,
+    rc_err_t (*test_cb)(const char *, dlmgr_DLValue_t *, void *))
+{
+	dlmgr__rad_dict_string_DLValue_t *linkinfo = NULL;
+	dlmgr_DatalinkError_t	*derrp = NULL;
+	rc_instance_t		*link = NULL;
+	rc_err_t		status;
+	char			propstr[DLADM_STRSIZE];
+	int			error = 0;
+
+	status = dlmgr_Datalink__rad_lookup(rad_conn, B_TRUE, &link, 1,
+	    "name", netdev_name);
+	if (status != RCE_OK) {
+		error = ENODEV;
+		goto out;
+	}
+
+	status = dlmgr_Datalink_getInfo(link, NULL, 0, &linkinfo, &derrp);
+	if (status != RCE_OK) {
+		if (status == RCE_SERVER_OBJECT) {
+			dpif_log(derrp->dde_err,
+			    "failed Datalink_getInfo(%s): %s",
+			    netdev_name, derrp->dde_errmsg);
+		}
+		error = ENOTSUP;
+		goto out;
+	}
+
+	propstr[0] = '\0';
+	status = dlmgr__rad_dict_string_DLValue_map(linkinfo, test_cb,
+	    propstr);
+	if (status != RCE_OK) {
+		error = EINVAL;
+		goto out;
+	}
+
+	memcpy(info_val, propstr, info_len);
+out:
+	dlmgr__rad_dict_string_DLValue_free(linkinfo);
+	dlmgr_DatalinkError_free(derrp);
+	rc_instance_rele(link);
+	return (error);
+}
+
+int
+solaris_get_devname(const char *netdev_name, char *name_val, size_t name_len)
+{
+	dlmgr__rad_dict_string_DLValue_t *linkinfo = NULL;
+	dlmgr_DLValue_t		*dlval = NULL;
+	dlmgr_DatalinkError_t	*derrp = NULL;
+	rc_instance_t		*link = NULL;
+	rc_err_t		status;
+	int			error = 0;
+
+	status = dlmgr_Physical__rad_lookup(rad_conn, B_TRUE, &link, 1,
+	    "name", netdev_name);
+	if (status != RCE_OK) {
+		error = ENODEV;
+		goto out;
+	}
+
+	status = dlmgr_Physical_getInfo(link, NULL, 0, &linkinfo, &derrp);
+	if (status != RCE_OK) {
+		if (status == RCE_SERVER_OBJECT) {
+			dpif_log(derrp->dde_err,
+			    "failed Physical_getInfo(%s): %s",
+			    netdev_name, derrp->dde_errmsg);
+		}
+		error = ENODEV;
+		goto out;
+	}
+
+	status = dlmgr__rad_dict_string_DLValue_get(linkinfo, "device",
+	    &dlval);
+	if (status != RCE_OK) {
+		error = ENODEV;
+		goto out;
+	}
+
+	memcpy(name_val, dlval->ddlv_sval, name_len);
+out:
+	dlmgr_DLValue_free(dlval);
+	dlmgr_DatalinkError_free(derrp);
+	dlmgr__rad_dict_string_DLValue_free(linkinfo);
+	rc_instance_rele(link);
+	return (error);
+}
+
+int
+solaris_get_dlclass(const char *netdev_name, char *class_val, size_t class_len)
+{
+	return (solaris_get_dlinfo(netdev_name, class_val, class_len,
+	    test_dlclass));
+}
+
+int
+solaris_get_dllower(const char *netdev_name, char *lower_val, size_t lower_len)
+{
+	return (solaris_get_dlinfo(netdev_name, lower_val, lower_len,
+	    test_dllower));
+}
+
+int
+solaris_get_dlprop(const char *netdev_name, const char *prop_name,
+    const char *field_name, char *prop_value, size_t prop_len)
+{
+	dlmgr_DLDict_t		**dlist = NULL;
+	dlmgr_DLValue_t		*dlval = NULL;
+	dlmgr_DatalinkError_t   *derrp = NULL;
+	rc_instance_t		*link = NULL;
+	rc_err_t		status;
+	const char		*props[1];
+	const char		*fields[1];
+	int			ndlist = 0;
+	int			error = 0, i = 0;
+	char			buf[DLADM_STRSIZE];
+
+	status = dlmgr_Datalink__rad_lookup(rad_conn, B_TRUE, &link, 1,
+	    "name", netdev_name);
+	if (status != RCE_OK) {
+		error = ENODEV;
+		goto out;
+	}
+
+	props[0] = prop_name;
+	fields[0] = field_name;
+	status = dlmgr_Datalink_getProperties(link, props, 1, fields, 1,
+	    &dlist, &ndlist, &derrp);
+	if (status != RCE_OK) {
+		if (status == RCE_SERVER_OBJECT) {
+			dpif_log(derrp->dde_err,
+			    "failed Datalink_getProperties(%s, %s): %s",
+			    netdev_name, prop_name, derrp->dde_errmsg);
+		}
+		error = ENOTSUP;
+		goto out;
+	}
+	status = dlmgr__rad_dict_string_DLValue_get((*dlist)->ddld_map,
+	    field_name, &dlval);
+	if (status != RCE_OK) {
+		error = EINVAL;
+		goto out;
+	}
+	switch (dlval->ddlv_type) {
+	case DDLVT_STRING:
+		if (dlval->ddlv_sval == NULL) {
+			error = EINVAL;
+			goto out;
+		}
+		memcpy(prop_value, dlval->ddlv_sval, prop_len);
+		break;
+	case DDLVT_STRINGS:
+		if (dlval->ddlv_slist_count == 0) {
+			error = EINVAL;
+			goto out;
+		}
+		for (i = 0; i < dlval->ddlv_slist_count; i++) {
+			(void) snprintf(buf, sizeof (buf), "%s%s",
+			    (i != 0 ? "," : ""), dlval->ddlv_slist[i]);
+		}
+		memcpy(prop_value, buf, prop_len);
+		break;
+	case DDLVT_ULONG:
+		if (dlval->ddlv_ulval == NULL) {
+			error = EINVAL;
+			goto out;
+		}
+		(void) snprintf(buf, sizeof (buf), "%llu", *dlval->ddlv_ulval);
+		memcpy(prop_value, buf, prop_len);
+		break;
+	case DDLVT_BOOLEAN:
+	case DDLVT_BOOLEANS:
+	case DDLVT_LONG:
+	case DDLVT_LONGS:
+	case DDLVT_ULONGS:
+	case DDLVT_DICTIONARY:
+	case DDLVT_DICTIONARYS:
+	default:
+		ovs_assert(0);
+		break;
+	}
+out:
+	dlmgr_DLValue_free(dlval);
+	dlmgr_DatalinkError_free(derrp);
+	dlmgr_DLDict_array_free(dlist, ndlist);
+	rc_instance_rele(link);
+	return (error);
+}
+
+static int
+solaris_set_dlprop(const char *netdev_name, const char *propname, void *arg,
+    dlmgr_DLValueType_t vtype)
+{
+	dlmgr__rad_dict_string_DLValue_t *sprop_dict = NULL;
+	dlmgr_DLValue_t			*old_val = NULL;
+	dlmgr_DLValue_t			new_val;
+	rc_instance_t			*link = NULL;
+	rc_err_t			status;
+	dlmgr_DatalinkError_t   	*derrp = NULL;
+	int				error = 0;
+
+	status = dlmgr_Datalink__rad_lookup(rad_conn, B_TRUE, &link, 1,
+	    "name", netdev_name);
+	if (status != RCE_OK) {
+		error = ENODEV;
+		goto out;
+	}
+
+	sprop_dict = dlmgr__rad_dict_string_DLValue_create(link);
+	if (sprop_dict == NULL) {
+		status = ENOMEM;
+		goto out;
+	}
+
+	bzero(&new_val, sizeof (new_val));
+	new_val.ddlv_type = vtype;
+	switch (vtype) {
+	case DDLVT_BOOLEAN:
+		new_val.ddlv_bval = arg;
+		status = dlmgr__rad_dict_string_DLValue_put(
+		    sprop_dict, propname, &new_val, &old_val);
+		if (status != RCE_OK) {
+			error = EINVAL;
+			goto out;
+		}
+		dlmgr_DLValue_free(old_val);
+		break;
+	case DDLVT_ULONG:
+		new_val.ddlv_ulval = arg;
+		status = dlmgr__rad_dict_string_DLValue_put(
+		    sprop_dict, propname, &new_val, &old_val);
+		if (status != RCE_OK) {
+			error = EINVAL;
+			goto out;
+		}
+		dlmgr_DLValue_free(old_val);
+		break;
+	case DDLVT_STRING:
+		new_val.ddlv_sval = arg;
+		status = dlmgr__rad_dict_string_DLValue_put(
+		    sprop_dict, propname, &new_val, &old_val);
+		if (status != RCE_OK) {
+			error = EINVAL;
+			goto out;
+		}
+		dlmgr_DLValue_free(old_val);
+		break;
+	case DDLVT_LONG:
+	case DDLVT_LONGS:
+	case DDLVT_ULONGS:
+	case DDLVT_STRINGS:
+	case DDLVT_BOOLEANS:
+	case DDLVT_DICTIONARY:
+	case DDLVT_DICTIONARYS:
+	default:
+		ovs_assert(0);
+		break;
+	}
+
+	/* we need to add temporary flag */
+	bzero(&new_val, sizeof (new_val));
+	old_val = NULL;
+	new_val.ddlv_type = DDLVT_BOOLEAN;
+	new_val.ddlv_bval = &b_true;
+	status = dlmgr__rad_dict_string_DLValue_put(
+	    sprop_dict, "temporary", &new_val, &old_val);
+	if (status != RCE_OK) {
+		error = EINVAL;
+		goto out;
+	}
+	dlmgr_DLValue_free(old_val);
+
+	status = dlmgr_Datalink_setProperties(link, sprop_dict, &derrp);
+	if (status != RCE_OK) {
+		if (status == RCE_SERVER_OBJECT) {
+			dpif_log(derrp->dde_err,
+			    "failed Datalink_setPropertiess(%s, %s): %s",
+			    netdev_name, propname, derrp->dde_errmsg);
+		}
+		error = ENOTSUP;
+	}
+out:
+	dlmgr_DatalinkError_free(derrp);
+	dlmgr__rad_dict_string_DLValue_free(sprop_dict);
+	rc_instance_rele(link);
+	return (error);
+}
+
+int
+solaris_set_dlprop_boolean(const char *netdev_name, const char *propname,
+    void *arg)
+{
+	return (solaris_set_dlprop(netdev_name, propname, arg, DDLVT_BOOLEAN));
+}
+
+int
+solaris_set_dlprop_ulong(const char *netdev_name, const char *propname,
+    void *arg)
+{
+	return (solaris_set_dlprop(netdev_name, propname, arg, DDLVT_ULONG));
+}
+
+int
+solaris_set_dlprop_string(const char *netdev_name, const char *propname,
+    void *arg)
+{
+	return (solaris_set_dlprop(netdev_name, propname, arg, DDLVT_STRING));
+}
+
+int
+solaris_create_vnic(const char *linkname, const char *vnicname)
+{
+	dlmgr__rad_dict_string_DLValue_t *prop = NULL;
+	dlmgr__rad_dict_string_DLValue_t *macaddr_info = NULL;
+	dlmgr_DatalinkError_t		*derrp = NULL;
+	dlmgr_DLValue_t			*old_val = NULL;
+	dlmgr_DLValue_t			name_val;
+	dlmgr_DLValue_t			temp_val;
+	dlmgr_DLValue_t			type_val;
+	dlmgr_DLValue_t			macaddr_info_val;
+	rc_instance_t			*linkmgr = NULL;
+	rc_instance_t			*vnic = NULL;
+	rc_err_t			status;
+
+	status = dlmgr_DatalinkManager__rad_lookup(rad_conn, B_TRUE,
+	    &linkmgr, 0);
+	if (status != RCE_OK)
+		goto out;
+
+	prop = dlmgr__rad_dict_string_DLValue_create(linkmgr);
+	if (prop == NULL)
+		goto out;
+
+	bzero(&name_val, sizeof (name_val));
+	name_val.ddlv_type = DDLVT_STRING;
+	/* linkname is 'const char *' and ddlv_sval is 'char *' */
+	name_val.ddlv_sval = strdupa(linkname);
+	status = dlmgr__rad_dict_string_DLValue_put(prop, "lower-link",
+	    &name_val, &old_val);
+	if (status != RCE_OK)
+		goto out;
+	dlmgr_DLValue_free(old_val);
+	old_val = NULL;
+
+	bzero(&temp_val, sizeof (temp_val));
+	temp_val.ddlv_type = DDLVT_BOOLEAN;
+	temp_val.ddlv_bval = &b_true;
+	status = dlmgr__rad_dict_string_DLValue_put(prop, "temporary",
+	    &temp_val, &old_val);
+	if (status != RCE_OK)
+		goto out;
+	dlmgr_DLValue_free(old_val);
+	old_val = NULL;
+
+	macaddr_info = dlmgr__rad_dict_string_DLValue_create(linkmgr);
+	if (macaddr_info == NULL)
+		goto out;
+
+	bzero(&type_val, sizeof (type_val));
+	type_val.ddlv_type = DDLVT_STRING;
+	type_val.ddlv_sval = strdupa("auto");
+	status = dlmgr__rad_dict_string_DLValue_put(macaddr_info,
+	    "mac-address-type", &type_val, &old_val);
+	if (status != RCE_OK)
+		goto out;
+	dlmgr_DLValue_free(old_val);
+	old_val = NULL;
+
+	bzero(&macaddr_info_val, sizeof (macaddr_info_val));
+	macaddr_info_val.ddlv_type = DDLVT_DICTIONARY;
+	macaddr_info_val.ddlv_dval = macaddr_info;
+	status = dlmgr__rad_dict_string_DLValue_put(prop, "mac-address-info",
+	    &macaddr_info_val, &old_val);
+	if (status != RCE_OK)
+		goto out;
+	dlmgr_DLValue_free(old_val);
+
+	status = dlmgr_DatalinkManager_createVNIC(linkmgr, vnicname, prop,
+	    &vnic, &derrp);
+	if (status == RCE_SERVER_OBJECT) {
+		dpif_log(derrp->dde_err,
+		    "failed DatalinkManager_createVNIC(%s): %s",
+		    vnicname, derrp->dde_errmsg);
+	}
+	rc_instance_rele(vnic);
+	dlmgr_DatalinkError_free(derrp);
+out:
+	dlmgr__rad_dict_string_DLValue_free(macaddr_info);
+	dlmgr__rad_dict_string_DLValue_free(prop);
+	rc_instance_rele(linkmgr);
+	return ((status != RCE_OK) ? ENOTSUP : 0);
+}
+
+int
+solaris_modify_vnic(const char *linkname, const char *vnicname)
+{
+	dlmgr__rad_dict_string_DLValue_t *sprop_dict = NULL;
+	dlmgr__rad_dict_string_DLValue_t *macaddr_info = NULL;
+	dlmgr_DLValue_t			*old_val = NULL;
+	dlmgr_DLValue_t			new_val;
+	dlmgr_DLValue_t			type_val;
+	dlmgr_DLValue_t			macaddr_info_val;
+	rc_instance_t			*link = NULL;
+	rc_err_t			status;
+	dlmgr_DatalinkError_t   	*derrp = NULL;
+	int				error = 0;
+
+	status = dlmgr_Datalink__rad_lookup(rad_conn, B_TRUE, &link, 1,
+	    "name", vnicname);
+	if (status != RCE_OK) {
+		error = ENODEV;
+		goto out;
+	}
+
+	sprop_dict = dlmgr__rad_dict_string_DLValue_create(link);
+	if (sprop_dict == NULL) {
+		status = ENOMEM;
+		goto out;
+	}
+
+	bzero(&new_val, sizeof (new_val));
+	new_val.ddlv_type = DDLVT_STRING;
+	new_val.ddlv_sval = strdupa(linkname);
+	status = dlmgr__rad_dict_string_DLValue_put(
+	    sprop_dict, "lower-link", &new_val, &old_val);
+	if (status != RCE_OK) {
+		error = EINVAL;
+		goto out;
+	}
+	dlmgr_DLValue_free(old_val);
+
+	/* we need to add temporary flag */
+	bzero(&new_val, sizeof (new_val));
+	old_val = NULL;
+	new_val.ddlv_type = DDLVT_BOOLEAN;
+	new_val.ddlv_bval = &b_true;
+	status = dlmgr__rad_dict_string_DLValue_put(
+	    sprop_dict, "temporary", &new_val, &old_val);
+	if (status != RCE_OK) {
+		error = EINVAL;
+		goto out;
+	}
+	dlmgr_DLValue_free(old_val);
+
+	macaddr_info = dlmgr__rad_dict_string_DLValue_create(link);
+	if (macaddr_info == NULL)
+		goto out;
+
+	bzero(&type_val, sizeof (type_val));
+	old_val = NULL;
+	type_val.ddlv_type = DDLVT_STRING;
+	type_val.ddlv_sval = strdupa("auto");
+	status = dlmgr__rad_dict_string_DLValue_put(macaddr_info,
+	    "mac-address-type", &type_val, &old_val);
+	if (status != RCE_OK)
+		goto out;
+	dlmgr_DLValue_free(old_val);
+
+	bzero(&macaddr_info_val, sizeof (macaddr_info_val));
+	old_val = NULL;
+	macaddr_info_val.ddlv_type = DDLVT_DICTIONARY;
+	macaddr_info_val.ddlv_dval = macaddr_info;
+	status = dlmgr__rad_dict_string_DLValue_put(sprop_dict,
+	    "mac-address-info", &macaddr_info_val, &old_val);
+	if (status != RCE_OK)
+		goto out;
+	dlmgr_DLValue_free(old_val);
+
+	status = dlmgr_Datalink_setProperties(link, sprop_dict, &derrp);
+	if (status != RCE_OK) {
+		if (status == RCE_SERVER_OBJECT) {
+			dpif_log(derrp->dde_err,
+			    "failed Datalink_setPropertiess(%s, lower-link): "
+			    " %s", vnicname, derrp->dde_errmsg);
+		}
+		error = ENOTSUP;
+	}
+out:
+	dlmgr_DatalinkError_free(derrp);
+	dlmgr__rad_dict_string_DLValue_free(macaddr_info);
+	dlmgr__rad_dict_string_DLValue_free(sprop_dict);
+	rc_instance_rele(link);
+	return (error);
+}
+
+int
+solaris_delete_vnic(const char *vnicname)
+{
+	dlmgr__rad_dict_string_DLValue_t *prop = NULL;
+	dlmgr_DatalinkError_t		*derrp = NULL;
+	dlmgr_DLValue_t			*old_val = NULL;
+	dlmgr_DLValue_t			temp_val;
+	rc_instance_t			*linkmgr = NULL;
+	rc_err_t			status;
+	int				err = 0;
+
+	status = dlmgr_DatalinkManager__rad_lookup(rad_conn, B_TRUE,
+	    &linkmgr, 0);
+	if (status != RCE_OK) {
+		err = EINVAL;
+		goto out;
+	}
+
+	prop = dlmgr__rad_dict_string_DLValue_create(linkmgr);
+	if (prop == NULL) {
+		err = EINVAL;
+		goto out;
+	}
+
+	bzero(&temp_val, sizeof (temp_val));
+	temp_val.ddlv_type = DDLVT_BOOLEAN;
+	temp_val.ddlv_bval = &b_true;
+	status = dlmgr__rad_dict_string_DLValue_put(prop, "temporary",
+	    &temp_val, &old_val);
+	if (status != RCE_OK) {
+		err = EINVAL;
+		goto out;
+	}
+	dlmgr_DLValue_free(old_val);
+
+	status = dlmgr_DatalinkManager_deleteVNIC(linkmgr, vnicname, prop,
+	    &derrp);
+	if (status != RCE_OK) {
+		err = -1;
+		if (status == RCE_SERVER_OBJECT) {
+			err = derrp->dde_err;
+			dpif_log(err,
+			    "failed DatalinkManager_deleteVNIC(%s): %s",
+			    vnicname, derrp->dde_errmsg);
+		}
+	}
+	dlmgr_DatalinkError_free(derrp);
+
+out:
+	dlmgr__rad_dict_string_DLValue_free(prop);
+	rc_instance_rele(linkmgr);
+	return (err);
+}
+
+int
+solaris_create_etherstub(const char *name)
+{
+	dlmgr__rad_dict_string_DLValue_t *prop = NULL;
+	dlmgr_DatalinkError_t		*derrp = NULL;
+	dlmgr_DLValue_t			*old_val = NULL;
+	dlmgr_DLValue_t			temp_val;
+	rc_instance_t			*linkmgr = NULL;
+	rc_instance_t			*etherstub = NULL;
+	rc_err_t			status;
+
+	status = dlmgr_DatalinkManager__rad_lookup(rad_conn, B_TRUE,
+	    &linkmgr, 0);
+	if (status != RCE_OK)
+		goto out;
+
+	prop = dlmgr__rad_dict_string_DLValue_create(linkmgr);
+	if (prop == NULL)
+		goto out;
+
+	bzero(&temp_val, sizeof (temp_val));
+	temp_val.ddlv_type = DDLVT_BOOLEAN;
+	temp_val.ddlv_bval = &b_true;
+	status = dlmgr__rad_dict_string_DLValue_put(prop, "temporary",
+	    &temp_val, &old_val);
+	if (status != RCE_OK)
+		goto out;
+	dlmgr_DLValue_free(old_val);
+
+	status = dlmgr_DatalinkManager_createEtherstub(linkmgr, name, prop,
+	    &etherstub, &derrp);
+	if (status == RCE_SERVER_OBJECT) {
+		dpif_log(derrp->dde_err,
+		    "failed DatalinkManager_createEtherstub(%s): %s",
+		    name, derrp->dde_errmsg);
+	}
+	rc_instance_rele(etherstub);
+	dlmgr_DatalinkError_free(derrp);
+
+out:
+	dlmgr__rad_dict_string_DLValue_free(prop);
+	rc_instance_rele(linkmgr);
+
+	return ((status != RCE_OK) ? ENOTSUP : 0);
+}
+
+int
+solaris_delete_etherstub(const char *name)
+{
+	dlmgr__rad_dict_string_DLValue_t *prop = NULL;
+	dlmgr_DatalinkError_t		*derrp = NULL;
+	dlmgr_DLValue_t			*old_val = NULL;
+	dlmgr_DLValue_t			temp_val;
+	rc_instance_t			*linkmgr = NULL;
+	rc_err_t			status;
+
+	status = dlmgr_DatalinkManager__rad_lookup(rad_conn, B_TRUE,
+	    &linkmgr, 0);
+	if (status != RCE_OK)
+		goto out;
+
+	prop = dlmgr__rad_dict_string_DLValue_create(linkmgr);
+	if (prop == NULL)
+		goto out;
+
+	bzero(&temp_val, sizeof (temp_val));
+	temp_val.ddlv_type = DDLVT_BOOLEAN;
+	temp_val.ddlv_bval = &b_true;
+	status = dlmgr__rad_dict_string_DLValue_put(prop, "temporary",
+	    &temp_val, &old_val);
+	if (status != RCE_OK)
+		goto out;
+	dlmgr_DLValue_free(old_val);
+
+	status = dlmgr_DatalinkManager_deleteEtherstub(linkmgr, name, prop,
+	    &derrp);
+	if (status == RCE_SERVER_OBJECT) {
+		dpif_log(derrp->dde_err,
+		    "failed DatalinkManager_deleteEtherstub(%s): %s",
+		    name, derrp->dde_errmsg);
+	}
+	dlmgr_DatalinkError_free(derrp);
+
+out:
+	dlmgr__rad_dict_string_DLValue_free(prop);
+	rc_instance_rele(linkmgr);
+	return ((status != RCE_OK) ? ENOTSUP : 0);
+}
+
+boolean_t
+solaris_etherstub_exists(const char *name)
+{
+	rc_instance_t	*etherstub = NULL;
+	boolean_t	exists;
+	rc_err_t	status;
+
+	status = dlmgr_Etherstub__rad_lookup(rad_conn, B_TRUE, &etherstub, 1,
+	    "name", name);
+	if (status == RCE_OK) {
+		dlmgr__rad_dict_string_DLValue_t *linkinfo = NULL;
+		dlmgr_DatalinkError_t *derrp = NULL;
+
+		status = dlmgr_Etherstub_getInfo(etherstub, NULL, 0,
+		    &linkinfo, &derrp);
+		if (status == RCE_OK) {
+			exists = _B_TRUE;
+			dlmgr__rad_dict_string_DLValue_free(linkinfo);
+		} else {
+			exists = _B_FALSE;
+			dlmgr_DatalinkError_free(derrp);
+		}
+	} else {
+		exists = _B_FALSE;
+	}
+
+	rc_instance_rele(etherstub);
+	return (exists);
+}
+
+static int
+flow_str2mac(const char *str, uchar_t *f, size_t maclen)
+{
+	uchar_t		*addr = NULL;
+	int		len, err = 0;
+
+	if ((addr = _link_aton(str, &len)) == NULL)
+		return ((len == -1) ? EINVAL : ENOMEM);
+
+	if (len != maclen) {
+		err = EINVAL;
+		goto done;
+	}
+
+	bcopy(addr, f, maclen);
+done:
+	free(addr);
+	return (err);
+}
+
+static int
+flow_str2addr(const char *str, in6_addr_t *f, int *afp)
+{
+	struct in_addr	v4addr;
+	struct in6_addr	v6addr;
+	int		af;
+
+	if (inet_pton(AF_INET, str, &v4addr.s_addr) == 1) {
+		af = AF_INET;
+	} else if (inet_pton(AF_INET6, str, v6addr.s6_addr) == 1) {
+		af = AF_INET6;
+	} else {
+		return (EINVAL);
+	}
+
+	if (af == AF_INET) {
+		IN6_INADDR_TO_V4MAPPED(&v4addr, f);
+	} else {
+		*f = v6addr;
+	}
+	*afp = af;
+	return (0);
+}
+
+static int
+solaris_flowinfo2linkname(dlmgr__rad_dict_string_DLValue_t *flowinfo,
+    char *linkname, size_t size)
+{
+	dlmgr__rad_dict_string_DLValue_t *fdict;
+	dlmgr_DLValue_t		*flist = NULL, *dlval = NULL;
+	rc_err_t		status;
+	int			err = 0;
+
+	status = dlmgr__rad_dict_string_DLValue_get(flowinfo, "filters",
+	    &flist);
+	if (status != RCE_OK)
+		return (EINVAL);
+
+	fdict = flist->ddlv_dlist[0]->ddld_map;
+
+	status = dlmgr__rad_dict_string_DLValue_get(fdict,
+	    "linkname", &dlval);
+	if (status != RCE_OK) {
+		err = EINVAL;
+		goto out;
+	}
+	(void) strlcpy(linkname, dlval->ddlv_sval, size);
+	dlmgr_DLValue_free(dlval);
+out:
+	dlmgr_DLValue_free(flist);
+	return (err);
+}
+
+static void
+flow_mac2str(uint8_t *f, uint8_t *m, char *buf, char *rbuf, size_t buf_len,
+    size_t rbuf_len)
+{
+	char *str = NULL;
+	char *pstr = NULL;
+
+	buf[0] = '\0';
+	rbuf[0] = '\0';
+
+	if (!eth_addr_is_zero(f) || !eth_addr_is_zero(m)) {
+		str = _link_ntoa(f, NULL, ETHERADDRL, IFT_ETHER);
+		if (str != NULL) {
+			pstr = _link_ntoa(m, NULL, ETHERADDRL, IFT_ETHER);
+			if (pstr != NULL) {
+				(void) snprintf(buf, buf_len, "%s", str);
+				(void) snprintf(rbuf, rbuf_len, "%s", pstr);
+				free(pstr);
+			}
+			free(str);
+		}
+	}
+}
+
+static void
+flow_addr2str(struct in6_addr *f6, struct in6_addr *m6, uint32_t f4,
+    uint32_t m4, char *buf, char *rbuf, size_t buf_len, size_t rbuf_len)
+{
+	struct in_addr ipaddr;
+	char abuf[INET6_ADDRSTRLEN], mbuf[INET6_ADDRSTRLEN];
+
+	buf[0] = '\0';
+	rbuf[0] = '\0';
+	if ((f6 != NULL) && (m6 != NULL) &&
+	    (!ipv6_addr_equals(f6, &in6addr_any) ||
+	    !ipv6_addr_equals(m6, &in6addr_any))) {
+		(void) inet_ntop(AF_INET6, f6, abuf, INET6_ADDRSTRLEN);
+		(void) inet_ntop(AF_INET6, m6, mbuf, INET6_ADDRSTRLEN);
+		(void) snprintf(buf, buf_len, "%s", abuf);
+		(void) snprintf(rbuf, rbuf_len, "%s", mbuf);
+	} else if (f4 != 0 || m4 != 0) {
+		ipaddr.s_addr = f4;
+		(void) strlcpy(abuf, inet_ntoa(ipaddr), sizeof (abuf));
+		ipaddr.s_addr = m4;
+		(void) strlcpy(mbuf, inet_ntoa(ipaddr), sizeof (mbuf));
+		(void) snprintf(buf, buf_len, "%s", abuf);
+		(void) snprintf(rbuf, rbuf_len, "%s", mbuf);
+	}
+}
+
+static int
+flow_ofports2propstr(uint32_t *ofports, int nofports, char ***ofportstr,
+    int *cnt)
+{
+	dladm_status_t		dlstatus;
+	mac_propval_range_t	*pv_range = NULL;
+	mac_propval_uint32_range_t *ur;
+	char			buf[DLADM_STRSIZE];
+	int			i, error = 0;
+	char			**strs;
+
+	*ofportstr = NULL;
+	*cnt = 0;
+	if (nofports != 0) {
+		/* Sort OF port list and convert it to a mac_propval_range */
+		dlstatus = dladm_list2range(ofports, nofports,
+		    MAC_PROPVAL_UINT32, &pv_range);
+		if (dlstatus != DLADM_STATUS_OK) {
+			error = EINVAL;
+			goto out;
+		}
+
+		strs = calloc(pv_range->mpr_count, sizeof (char *));
+		if (strs == NULL) {
+			error = ENOMEM;
+			goto out;
+		}
+		/*
+		 * Write ranges and individual elements into their own
+		 * buffer.
+		 */
+		for (i = 0; i < pv_range->mpr_count; i++, ur++) {
+			ur = &pv_range->mpr_range_uint32[i];
+			if (ur->mpur_min == ur->mpur_max) {
+				/* single element */
+				(void) snprintf(buf, sizeof (buf), "%u",
+				    ur->mpur_min);
+				strs[i] = strdup(buf);
+			} else {
+				/* range of elements */
+				(void) snprintf(buf, sizeof (buf), "%u-%u",
+				    ur->mpur_min, ur->mpur_max);
+				strs[i] = strdup(buf);
+			}
+			if (strs[i] == NULL) {
+				while (i >= 0) {
+					free(strs[i]);
+					i--;
+				}
+				free(strs);
+				error = ENOMEM;
+				goto out;
+			}
+		}
+		*cnt = pv_range->mpr_count;
+		*ofportstr = strs;
+	}
+out:
+	free(pv_range);
+	return (error);
+}
+
+static int
+dlmgr_DLValue_putstring(dlmgr__rad_dict_string_DLValue_t *ddvp,
+    const char *key, char *buf, char *dstr, size_t dstrlen)
+{
+	dlmgr_DLValue_t  *old_val = NULL;
+	dlmgr_DLValue_t  new_val;
+	rc_err_t status;
+
+	if (strlen(buf) != 0) {
+		bzero(&new_val, sizeof (new_val));
+		new_val.ddlv_type = DDLVT_STRING;
+		new_val.ddlv_sval = buf;
+		if ((status = dlmgr__rad_dict_string_DLValue_put(ddvp, key,
+		    &new_val, &old_val)) != RCE_OK) {
+			return (EINVAL);
+		}
+		snprintf(dstr, dstrlen, "%s,%s=%s", dstr, key, buf);
+		dlmgr_DLValue_free(old_val);
+	}
+	return (0);
+}
+
+static int
+dlmgr_DLValue_fm_putstring(dlmgr__rad_dict_string_DLValue_t *ddvp,
+    dlmgr__rad_dict_string_DLValue_t *ddmp, const char *key,
+    char *buf, char *rbuf, char *dstr, size_t dstrlen)
+{
+	dlmgr_DLValue_t  *old_val = NULL;
+	dlmgr_DLValue_t  new_val;
+	rc_err_t status;
+
+	if (strlen(buf) != 0) {
+		bzero(&new_val, sizeof (new_val));
+		new_val.ddlv_type = DDLVT_STRING;
+		new_val.ddlv_sval = buf;
+		if ((status = dlmgr__rad_dict_string_DLValue_put(ddvp, key,
+		    &new_val, &old_val)) != RCE_OK) {
+			return (EINVAL);
+		}
+		dlmgr_DLValue_free(old_val);
+		old_val = NULL;
+		new_val.ddlv_sval = rbuf;
+		if ((status = dlmgr__rad_dict_string_DLValue_put(ddmp, key,
+		    &new_val, &old_val)) != RCE_OK) {
+			return (EINVAL);
+		}
+		dlmgr_DLValue_free(old_val);
+
+		snprintf(dstr, dstrlen, "%s,%s=%s/%s", dstr, key, buf, rbuf);
+	}
+	return (0);
+}
+
+/*
+ * Caller allocated "pvals" is freed here
+ */
+static int
+dlmgr_DLValue_putstrings(dlmgr__rad_dict_string_DLValue_t *ddvp,
+    const char *key, char **pvals, int pvalcnt, char *dstr, size_t dstrlen)
+{
+	dlmgr_DLValue_t  *old_val = NULL;
+	dlmgr_DLValue_t  new_val;
+	rc_err_t status;
+	int i, err = 0;
+
+	if (pvalcnt != 0) {
+		bzero(&new_val, sizeof (new_val));
+		new_val.ddlv_type = DDLVT_STRINGS;
+		new_val.ddlv_slist = pvals;
+		new_val.ddlv_slist_count = pvalcnt;
+		status = dlmgr__rad_dict_string_DLValue_put(
+		    ddvp, key, &new_val, &old_val);
+		if (status != RCE_OK) {
+			err = EINVAL;
+			goto out;
+		}
+		for (i = 0; i < pvalcnt; i++)
+			snprintf(dstr, dstrlen, "%s,%s=%s", dstr, key,
+			    pvals[i]);
+		dlmgr_DLValue_free(old_val);
+out:
+		for (i = 0; i < pvalcnt; i++)
+			free(pvals[i]);
+		free(pvals);
+	}
+	return (err);
+}
+
+static int
+dlmgr_DLValue_putboolean(dlmgr__rad_dict_string_DLValue_t *ddvp,
+    const char *key, boolean_t val, char *dstr, size_t dstrlen)
+{
+	dlmgr_DLValue_t  *old_val = NULL;
+	dlmgr_DLValue_t  new_val;
+	rc_err_t status;
+
+	bzero(&new_val, sizeof (new_val));
+	new_val.ddlv_type = DDLVT_BOOLEAN;
+	new_val.ddlv_bval = &val;
+	if ((status = dlmgr__rad_dict_string_DLValue_put(ddvp, key, &new_val,
+	    &old_val)) != RCE_OK) {
+		return (EINVAL);
+	}
+	snprintf(dstr, dstrlen, "%s,%s=%s", dstr, key,
+	    val ? "true" : "false");
+	dlmgr_DLValue_free(old_val);
+	return (0);
+}
+
+static int
+dlmgr_DLValue_putulong(dlmgr__rad_dict_string_DLValue_t *ddvp, const char *key,
+    uint64_t val, char *dstr, size_t dstrlen)
+{
+	dlmgr_DLValue_t  *old_val = NULL;
+	dlmgr_DLValue_t  new_val;
+	unsigned long long ulval = val;
+	rc_err_t status;
+
+	bzero(&new_val, sizeof (new_val));
+	new_val.ddlv_type = DDLVT_ULONG;
+	new_val.ddlv_ulval = &ulval;
+	if ((status = dlmgr__rad_dict_string_DLValue_put(ddvp, key, &new_val,
+	    &old_val)) != RCE_OK) {
+		return (EINVAL);
+	}
+	snprintf(dstr, dstrlen, "%s,%s=%"PRIu64, dstr, key, val);
+	dlmgr_DLValue_free(old_val);
+	return (0);
+}
+
+static int
+dlmgr_DLValue_fm_putulong(dlmgr__rad_dict_string_DLValue_t *ddvp,
+    dlmgr__rad_dict_string_DLValue_t *ddmp, const char *key,
+    uint64_t f, uint64_t m, char *dstr, size_t dstrlen)
+{
+	dlmgr_DLValue_t  *old_val = NULL;
+	dlmgr_DLValue_t  new_val;
+	unsigned long long f_ulval = f;
+	unsigned long long m_ulval = m;
+	rc_err_t status;
+
+	bzero(&new_val, sizeof (new_val));
+	new_val.ddlv_type = DDLVT_ULONG;
+	new_val.ddlv_ulval = &f_ulval;
+	if ((status = dlmgr__rad_dict_string_DLValue_put(ddvp, key, &new_val,
+	    &old_val)) != RCE_OK) {
+		return (EINVAL);
+	}
+	dlmgr_DLValue_free(old_val);
+	old_val = NULL;
+	new_val.ddlv_ulval = &m_ulval;
+	if ((status = dlmgr__rad_dict_string_DLValue_put(ddmp, key, &new_val,
+	    &old_val)) != RCE_OK) {
+		return (EINVAL);
+	}
+	dlmgr_DLValue_free(old_val);
+	snprintf(dstr, dstrlen, "%s,%s=%"PRIu64"/%"PRIu64, dstr, key, f, m);
+	return (0);
+}
+
+static int
+solaris_flow_to_DLVal(struct flow *f, struct flow *m,
+    dlmgr__rad_dict_string_DLValue_t *ddvp,
+    dlmgr__rad_dict_string_DLValue_t *ddmp)
+{
+	char		buf[DLADM_STRSIZE], rbuf[DLADM_STRSIZE];
+	char		dstr[DLADM_STRSIZE];
+	int		err = 0;
+	boolean_t	is_arp = (ntohs(f->dl_type) == 0x806);
+
+	dstr[0] = '\0';
+	if (f->dl_type != htons(FLOW_DL_TYPE_NONE)) {
+		err = dlmgr_DLValue_fm_putulong(ddvp, ddmp, "sap",
+		    ntohs(f->dl_type), ntohs(m->dl_type), dstr, sizeof (dstr));
+		if (err != 0)
+			goto out;
+	}
+
+	if (is_arp) {
+		if (f->nw_proto != 0 || m->nw_proto != 0) {
+			err = dlmgr_DLValue_fm_putulong(ddvp, ddmp, "arp-op",
+			    f->nw_proto, m->nw_proto, dstr, sizeof (dstr));
+			if (err != 0)
+				goto out;
+		}
+
+		flow_mac2str(f->arp_sha, m->arp_sha, buf, rbuf, sizeof (buf),
+		    sizeof (rbuf));
+		if (strlen(buf) != 0) {
+			err = dlmgr_DLValue_fm_putstring(ddvp, ddmp,
+			    "arp-sender", buf, rbuf, dstr, sizeof (dstr));
+			if (err != 0)
+				goto out;
+		}
+
+		flow_mac2str(f->arp_tha, m->arp_tha, buf, rbuf, sizeof (buf),
+		    sizeof (rbuf));
+		if (strlen(buf) != 0) {
+			err = dlmgr_DLValue_fm_putstring(ddvp, ddmp,
+			    "arp-target", buf, rbuf, dstr, sizeof (dstr));
+			if (err != 0)
+				goto out;
+		}
+
+		flow_addr2str(NULL, NULL, f->nw_src, m->nw_src, buf, rbuf,
+		    sizeof (buf), sizeof (rbuf));
+		if (strlen(buf) != 0) {
+			err = dlmgr_DLValue_fm_putstring(ddvp, ddmp, "arp-sip",
+			    buf, rbuf, dstr, sizeof (dstr));
+			if (err != 0)
+				goto out;
+		}
+
+		flow_addr2str(NULL, NULL, f->nw_dst, m->nw_dst, buf, rbuf,
+		    sizeof (buf), sizeof (rbuf));
+		if (strlen(buf) != 0) {
+			err = dlmgr_DLValue_fm_putstring(ddvp, ddmp, "arp-tip",
+			    buf, rbuf,  dstr, sizeof (dstr));
+			if (err != 0)
+				goto out;
+		}
+	} else {
+		if (f->nw_proto != 0 || m->nw_proto != 0) {
+			err = dlmgr_DLValue_fm_putulong(ddvp, ddmp, "transport",
+			    f->nw_proto, m->nw_proto, dstr, sizeof (dstr));
+			if (err != 0)
+				goto out;
+		}
+
+		flow_addr2str(&f->ipv6_src, &m->ipv6_src, f->nw_src, m->nw_src,
+		    buf, rbuf, sizeof (buf), sizeof (rbuf));
+		if (strlen(buf) != 0) {
+			err = dlmgr_DLValue_fm_putstring(ddvp, ddmp, "local-ip",
+			    buf, rbuf, dstr, sizeof (dstr));
+			if (err != 0)
+				goto out;
+		}
+
+		flow_addr2str(&f->ipv6_dst, &m->ipv6_dst, f->nw_dst, m->nw_dst,
+		    buf, rbuf, sizeof (buf), sizeof (rbuf));
+		if (strlen(buf) != 0) {
+			err = dlmgr_DLValue_fm_putstring(ddvp, ddmp,
+			    "remote-ip", buf, rbuf, dstr, sizeof (dstr));
+			if (err != 0)
+				goto out;
+		}
+
+		if (f->tcp_flags != 0 || m->tcp_flags != 0) {
+			err = dlmgr_DLValue_fm_putulong(ddvp, ddmp,
+			    "tcp-flags", (uint8_t)(ntohs(f->tcp_flags)),
+			    (uint8_t)(ntohs(m->tcp_flags)), dstr,
+			    sizeof (dstr));
+			if (err != 0)
+				goto out;
+		}
+
+		if (f->nw_proto != IPPROTO_ICMP &&
+		    f->nw_proto != IPPROTO_ICMPV6) {
+			if (f->tp_src != 0 || m->tp_src != 0) {
+				err = dlmgr_DLValue_fm_putulong(ddvp, ddmp,
+				    "local-port", ntohs(f->tp_src),
+				    ntohs(m->tp_src), dstr, sizeof (dstr));
+				if (err != 0)
+					goto out;
+			}
+
+			if (f->tp_dst != 0 || m->tp_dst != 0) {
+				err = dlmgr_DLValue_fm_putulong(ddvp, ddmp,
+				    "remote-port", ntohs(f->tp_dst),
+				    ntohs(m->tp_dst), dstr, sizeof (dstr));
+				if (err != 0)
+					goto out;
+			}
+		} else {
+			if (f->tp_src != 0 || m->tp_src != 0) {
+				err = dlmgr_DLValue_fm_putulong(ddvp, ddmp,
+				    "icmp-type", ntohs(f->tp_src),
+				    ntohs(m->tp_src), dstr, sizeof (dstr));
+				if (err != 0)
+					goto out;
+			}
+			if (f->tp_dst != 0 || m->tp_dst != 0) {
+				err = dlmgr_DLValue_fm_putulong(ddvp, ddmp,
+				    "icmp-code", ntohs(f->tp_dst),
+				    ntohs(m->tp_dst), dstr, sizeof (dstr));
+				if (err != 0)
+					goto out;
+			}
+		}
+	}
+
+
+	flow_mac2str(f->dl_src, m->dl_src, buf, rbuf, sizeof (buf),
+	    sizeof (rbuf));
+	if (strlen(buf) != 0) {
+		err = dlmgr_DLValue_fm_putstring(ddvp, ddmp, "src-mac", buf,
+		    rbuf, dstr, sizeof (dstr));
+		if (err != 0)
+			goto out;
+	}
+
+	flow_mac2str(f->dl_dst, m->dl_dst, buf, rbuf, sizeof (buf),
+	    sizeof (rbuf));
+	if (strlen(buf) != 0) {
+		err = dlmgr_DLValue_fm_putstring(ddvp, ddmp, "dst-mac", buf,
+		    rbuf, dstr, sizeof (dstr));
+		if (err != 0)
+			goto out;
+	}
+
+	if (f->in_port.odp_port != ODPP_NONE) {
+		err = dlmgr_DLValue_putulong(ddvp, "srcport",
+		    f->in_port.odp_port, dstr, sizeof (dstr));
+		if (err != 0)
+			goto out;
+	}
+
+	if (f->vlan_tci != 0 || m->vlan_tci != 0) {
+		err = dlmgr_DLValue_fm_putulong(ddvp, ddmp,
+		    "vlan-tci", ntohs(f->vlan_tci),
+		    ntohs(m->vlan_tci), dstr, sizeof (dstr));
+		if (err != 0)
+			goto out;
+	}
+
+	if (f->nw_tos != 0 || m->nw_tos != 0) {
+		err = dlmgr_DLValue_fm_putulong(ddvp, ddmp,
+		    "dsfield", f->nw_tos, m->nw_tos, dstr, sizeof (dstr));
+		if (err != 0)
+			goto out;
+	}
+
+	if (f->nw_ttl != 0 || m->nw_ttl != 0) {
+		err = dlmgr_DLValue_fm_putulong(ddvp, ddmp,
+		    "ttl", f->nw_ttl, m->nw_ttl, dstr, sizeof (dstr));
+		if (err != 0)
+			goto out;
+	}
+
+out:
+	dpif_log(err, "solaris_flow_to_DLVal FLOWATTR: %s", dstr);
+	return (err);
+}
+
+static int
+solaris_outports_action_to_DLVal(dlmgr__rad_dict_string_DLValue_t *prop,
+    void *cookie, uint32_t ofports[], int nofports, uint32_t queueid,
+    char *dstr, size_t dstrlen)
+{
+	struct smap details;
+	const char *max_rate = NULL;
+	uint64_t maxbw;
+	char *endp = NULL;
+	char **pvals = NULL;
+	int  pvalcnt, err;
+
+	smap_init(&details);
+	if (queueid == UINT32_MAX || nofports != 1)
+		goto outport;
+
+	if ((err = dpif_solaris_get_priority_details(cookie, ofports[0],
+	    queueid, &details)) != 0) {
+		smap_destroy(&details);
+		goto outport;
+	}
+	/* min-rate and priority not currently used */
+	max_rate = smap_get(&details, "max-rate");
+	if (max_rate == NULL)
+		goto outport;
+
+	errno = 0;
+	maxbw = strtoull(max_rate, &endp, 10);
+	if (errno != 0 || *endp != '\0')
+		goto outport;
+
+	err = dlmgr_DLValue_putulong(prop, "max-bw", maxbw, dstr, dstrlen);
+
+outport:
+	err = flow_ofports2propstr(ofports, nofports, &pvals, &pvalcnt);
+	if (err == 0) {
+		err = dlmgr_DLValue_putstrings(prop, "outports", pvals,
+		    pvalcnt, dstr, dstrlen);
+	}
+	smap_destroy(&details);
+
+	dpif_log(err, "dpif_solaris_to_outport_action PROP: %s", dstr);
+	return (err);
+}
+
+static int
+solaris_setether_action_to_DLVal(dlmgr__rad_dict_string_DLValue_t *prop,
+    const struct ovs_key_ethernet *ek, char *ds, size_t dslen)
+{
+	char *sstr = NULL, *dstr = NULL;
+	char **pvals = NULL;
+	char buf[DLADM_STRSIZE];
+	int err;
+
+	sstr = _link_ntoa(ek->eth_src, NULL, ETHERADDRL, IFT_ETHER);
+	dstr = _link_ntoa(ek->eth_dst, NULL, ETHERADDRL, IFT_ETHER);
+	if (sstr != NULL && dstr != NULL) {
+		(void) snprintf(buf, sizeof (buf), "ether_src:%s,ether_dst:%s",
+		    sstr, dstr);
+	}
+	free(sstr);
+	free(dstr);
+	if (sstr == NULL || dstr == NULL) {
+		err = ENOMEM;
+		goto out;
+	}
+	pvals = calloc(1, sizeof (char *));
+	if (pvals == NULL) {
+		err = ENOMEM;
+		goto out;
+	}
+	pvals[0] = strdup(buf);
+	if (pvals[0] == NULL) {
+		err = ENOMEM;
+		free(pvals);
+		goto out;
+	}
+
+	/* TODO(gmoodalb): this should be two entries. fix later */
+	err = dlmgr_DLValue_putstrings(prop, "set-ether", pvals, 1, ds,
+	    dslen);
+
+out:
+	dpif_log(err, "solaris_setether_action_to_DLVal PROP: %s", ds);
+	return (err);
+}
+
+static int
+solaris_setipv4_action_to_DLVal(dlmgr__rad_dict_string_DLValue_t *prop,
+    const struct ovs_key_ipv4 *ipv4, char *dstr, size_t dstrlen)
+{
+	struct in_addr ipaddr;
+	char *cp;
+	char **pvals = NULL;
+	char buf[DLADM_STRSIZE];
+	int err;
+
+	ipaddr.s_addr = ipv4->ipv4_src;
+	cp = inet_ntoa(ipaddr);
+	(void) snprintf(buf, sizeof (buf), "src:%s,", cp);
+	ipaddr.s_addr = ipv4->ipv4_dst;
+	cp = inet_ntoa(ipaddr);
+	(void) snprintf(buf, sizeof (buf),
+	    "%sdst:%s,protocol:%s,tos:0x%x,hoplimit:%d",
+	    buf, cp, solaris_proto2str(ipv4->ipv4_proto), ipv4->ipv4_tos,
+	    ipv4->ipv4_ttl);
+
+	pvals = calloc(1, sizeof (char *));
+	if (pvals == NULL) {
+		err = ENOMEM;
+		goto out;
+	}
+	pvals[0] = strdup(buf);
+	if (pvals[0] == NULL) {
+		err = ENOMEM;
+		free(pvals);
+		goto out;
+	}
+
+	err = dlmgr_DLValue_putstrings(prop, "set-ipv4", pvals, 1, dstr,
+	    dstrlen);
+
+out:
+	dpif_log(err, "solaris_setipv4_action_to_DLVal PROP: %s", dstr);
+	return (err);
+}
+
+static int
+solaris_setipv6_action_to_DLVal(dlmgr__rad_dict_string_DLValue_t *prop,
+    const struct ovs_key_ipv6 *ipv6, char *dstr, size_t dstrlen)
+{
+	char abuf[INET6_ADDRSTRLEN];
+	char **pvals = NULL;
+	char buf[DLADM_STRSIZE];
+	int err;
+
+	(void) inet_ntop(AF_INET6, ipv6->ipv6_src, abuf, INET6_ADDRSTRLEN);
+	(void) snprintf(buf, sizeof (buf), "src:%s,", abuf);
+	(void) inet_ntop(AF_INET6, ipv6->ipv6_dst, abuf, INET6_ADDRSTRLEN);
+	(void) snprintf(buf, sizeof (buf),
+	    "%sdst:%s,label:0x%x,protocol:%s,tos:0x%x,hoplimit:%d", buf, abuf,
+	    ipv6->ipv6_label, solaris_proto2str(ipv6->ipv6_proto),
+	    ipv6->ipv6_tclass, ipv6->ipv6_hlimit);
+
+	pvals = calloc(1, sizeof (char *));
+	if (pvals == NULL) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	pvals[0] = strdup(buf);
+	if (pvals[0] == NULL) {
+		err = ENOMEM;
+		free(pvals);
+		goto out;
+	}
+
+	err = dlmgr_DLValue_putstrings(prop, "set-ipv6", pvals, 1, dstr,
+	    dstrlen);
+
+out:
+	dpif_log(err, "solaris_setipv6_action_to_DLVal PROP: %s", dstr);
+	return (err);
+}
+
+static int
+solaris_settransport_action_to_DLVal(dlmgr__rad_dict_string_DLValue_t *prop,
+    const char *key, uint16_t src, uint16_t dst, char *dstr, size_t dstrlen)
+{
+	char **pvals = NULL;
+	char buf[DLADM_STRSIZE];
+	int err;
+
+	(void) snprintf(buf, sizeof (buf), "sport:%d,dport:%d", src, dst);
+
+	pvals = calloc(1, sizeof (char *));
+	if (pvals == NULL) {
+		err = ENOMEM;
+		goto out;
+	}
+	pvals[0] = strdup(buf);
+	if (pvals[0] == NULL) {
+		err = ENOMEM;
+		free(pvals);
+		goto out;
+	}
+
+	err = dlmgr_DLValue_putstrings(prop, key, pvals, 1, dstr, dstrlen);
+
+out:
+	dpif_log(err, "solaris_settransport_action_to_DLVal PROP: %s %s", key,
+	    dstr);
+	return (err);
+}
+
+static int
+solaris_settnl_action_to_DLVal(dlmgr__rad_dict_string_DLValue_t *prop,
+    const struct flow_tnl *tnl, char *dstr, size_t dstrlen)
+{
+	struct in_addr ipaddr;
+	char *cp;
+	char **pvals = NULL;
+	char buf[DLADM_STRSIZE];
+	int err;
+
+	ipaddr.s_addr = tnl->ip_src;
+	cp = inet_ntoa(ipaddr);
+	(void) snprintf(buf, sizeof (buf), "src:%s,", cp);
+	ipaddr.s_addr = tnl->ip_dst;
+	cp = inet_ntoa(ipaddr);
+	(void) snprintf(buf, sizeof (buf),
+	    "%sdst:%s,tun_id:0x%"PRIx64",tos:0x%x,hoplimit:%d",
+	    buf, cp, ntohll(tnl->tun_id), tnl->ip_tos, tnl->ip_ttl);
+
+	pvals = calloc(1, sizeof (char *));
+	if (pvals == NULL) {
+		err = ENOMEM;
+		goto out;
+	}
+	pvals[0] = strdup(buf);
+	if (pvals[0] == NULL) {
+		err = ENOMEM;
+		free(pvals);
+		goto out;
+	}
+
+	err = dlmgr_DLValue_putstrings(prop, "set-tunnel", pvals, 1, dstr,
+	    dstrlen);
+
+out:
+	dpif_log(err, "solaris_setipv6_action_to_DLVal PROP: %s", dstr);
+	return (err);
+}
+
+static int
+solaris_nlattr_to_DLVal(void *cookie,
+    const struct nlattr *actions_nlattr, size_t actions_len,
+    dlmgr__rad_dict_string_DLValue_t **propp)
+{
+	const struct nlattr *a;
+	unsigned int left;
+	dlmgr__rad_dict_string_DLValue_t *prop = *propp;
+	char buf[DLADM_STRSIZE];
+	char dstr[DLADM_STRSIZE];
+	char **pvals;
+	int err = 0, nofports = 0;
+	uint32_t ofports[MAC_OF_MAXPORT];
+	enum ovs_action_attr type = -1, lasttype;
+	uint_t queueid = UINT32_MAX;
+
+	dstr[0] = '\0';
+	err = dlmgr_DLValue_putboolean(prop, "temporary", B_TRUE, dstr,
+	    sizeof (dstr));
+	if (err != 0)
+		goto out;
+
+	/* if actions_len == 0, then the action is drop */
+	if (actions_len == 0) {
+		pvals = calloc(1, sizeof (char *));
+		if (pvals == NULL) {
+			err = ENOMEM;
+			goto out;
+		}
+		(void) snprintf(buf, sizeof (buf), "drop");
+		pvals[0] = strdup(buf);
+		if (pvals[0] == NULL) {
+			err = ENOMEM;
+			free(pvals);
+			goto out;
+		}
+		err = dlmgr_DLValue_putstrings(prop, "outports", pvals, 1,
+		    dstr, sizeof (dstr));
+		goto out;
+	}
+
+	NL_ATTR_FOR_EACH_UNSAFE(a, left, actions_nlattr, actions_len) {
+		lasttype = type;
+		type = nl_attr_type(a);
+		if ((type != OVS_ACTION_ATTR_OUTPUT) ||
+		    (lasttype != OVS_ACTION_ATTR_OUTPUT)) {
+			if (lasttype == OVS_ACTION_ATTR_OUTPUT) {
+				dpif_log(0, "solaris_nlattr_to_DLVal outports "
+				    "total %d ports", nofports);
+				err = solaris_outports_action_to_DLVal(prop,
+				    cookie, ofports, nofports, queueid,
+				    dstr, sizeof (dstr));
+				if (err != 0)
+					goto out;
+			}
+			nofports = 0;
+		}
+
+		switch ((enum ovs_action_attr) type) {
+		/* These only make sense in the context of a datapath. */
+		case OVS_ACTION_ATTR_OUTPUT:
+			if (nofports + 1 > MAC_OF_MAXPORT) {
+				err = ENOBUFS;
+				break;
+			}
+			dpif_log(0, "solaris_nlattr_to_DLVal %d ports: %u",
+			    nofports+1, nl_attr_get_u32(a));
+			ofports[nofports++] = u32_to_odp(nl_attr_get_u32(a));
+			break;
+		case OVS_ACTION_ATTR_USERSPACE: {
+			const struct nlattr *userdata;
+			size_t userdata_len;
+			union user_action_cookie cookie;
+
+			userdata = nl_attr_find_nested(a,
+			    OVS_USERSPACE_ATTR_USERDATA);
+			userdata_len = nl_attr_get_size(userdata);
+			memcpy(&cookie, nl_attr_get(userdata), userdata_len);
+			if (userdata_len < sizeof (cookie.type) ||
+			    userdata_len > sizeof (cookie)) {
+				err = EINVAL;
+				dpif_log(err,
+				    "unexpected action size %"PRIuSIZE,
+				    userdata_len);
+				break;
+			}
+			if (userdata_len != MAX(8, sizeof (cookie.slow_path)) ||
+			    cookie.type != USER_ACTION_COOKIE_SLOW_PATH) {
+				err = EOPNOTSUPP;
+				dpif_log(err,
+				    "userspace action size %"PRIuSIZE" "
+				    "type unsupported %d", userdata_len,
+				    cookie.type);
+				break;
+			}
+			if (cookie.slow_path.reason != SLOW_CONTROLLER)
+				break;
+
+			pvals = calloc(1, sizeof (char *));
+			if (pvals == NULL) {
+				err = ENOMEM;
+				break;
+			}
+			(void) snprintf(buf, sizeof (buf), "%u",
+			    PORT_PF_PACKET_UPLINK);
+			pvals[0] = strdup(buf);
+			if (pvals[0] == NULL) {
+				err = ENOMEM;
+				free(pvals);
+				break;
+			}
+			err = dlmgr_DLValue_putstrings(prop, "controller",
+			    pvals, 1, dstr, sizeof (dstr));
+			break;
+		}
+		case OVS_ACTION_ATTR_SET: {
+			const struct nlattr *aset = nl_attr_get(a);
+
+			switch (nl_attr_type(aset)) {
+			case OVS_KEY_ATTR_PRIORITY:
+				queueid = nl_attr_get_u32(aset);
+				break;
+
+			case OVS_KEY_ATTR_ETHERNET: {
+				const struct ovs_key_ethernet *ek;
+
+				ek = nl_attr_get_unspec(aset,
+				    sizeof (struct ovs_key_ethernet));
+				err = solaris_setether_action_to_DLVal(prop,
+				    ek, dstr, sizeof (dstr));
+				break;
+			}
+			case OVS_KEY_ATTR_IPV4: {
+				const struct ovs_key_ipv4 *eip4;
+
+				eip4 = nl_attr_get_unspec(aset,
+				    sizeof (struct ovs_key_ipv4));
+				err = solaris_setipv4_action_to_DLVal(prop,
+				    eip4, dstr, sizeof (dstr));
+				break;
+			}
+			case OVS_KEY_ATTR_IPV6: {
+				const struct ovs_key_ipv6 *eip6;
+
+				eip6 = nl_attr_get_unspec(aset,
+				    sizeof (struct ovs_key_ipv6));
+				err = solaris_setipv6_action_to_DLVal(prop,
+				    eip6, dstr, sizeof (dstr));
+				break;
+			}
+			case OVS_KEY_ATTR_TCP: {
+				const struct ovs_key_tcp *etcp;
+				etcp = nl_attr_get_unspec(aset,
+				    sizeof (struct ovs_key_tcp));
+				err = solaris_settransport_action_to_DLVal(
+				    prop, "set-tcp", etcp->tcp_src,
+				    etcp->tcp_dst, dstr, sizeof (dstr));
+				break;
+			}
+			case OVS_KEY_ATTR_UDP: {
+				const struct ovs_key_udp *eudp;
+				eudp = nl_attr_get_unspec(aset,
+				    sizeof (struct ovs_key_udp));
+				err = solaris_settransport_action_to_DLVal(
+				    prop, "set-udp", eudp->udp_src,
+				    eudp->udp_dst, dstr, sizeof (dstr));
+				break;
+			}
+			case OVS_KEY_ATTR_SCTP: {
+				const struct ovs_key_sctp *esctp;
+				esctp = nl_attr_get_unspec(aset,
+				    sizeof (struct ovs_key_sctp));
+				err = solaris_settransport_action_to_DLVal(
+				    prop, "set-sctp", esctp->sctp_src,
+				    esctp->sctp_dst, dstr, sizeof (dstr));
+				break;
+			}
+			case OVS_KEY_ATTR_TUNNEL: {
+				struct flow_tnl tnl;
+				enum odp_key_fitness fitness;
+
+				memset(&tnl, 0, sizeof (tnl));
+				fitness = odp_tun_key_from_attr(aset, &tnl);
+				ovs_assert(fitness != ODP_FIT_ERROR);
+				err = solaris_settnl_action_to_DLVal(
+				    prop, &tnl, dstr, sizeof (dstr));
+				break;
+			}
+			default:
+				err = EOPNOTSUPP;
+				dpif_log(err, "solaris_nlattr_to_DLVal set "
+				    "%d not supported",
+				    nl_attr_type(nl_attr_get(a)));
+			}
+			break;
+		}
+		case OVS_ACTION_ATTR_PUSH_VLAN: {
+			const struct ovs_action_push_vlan *vlan;
+
+			vlan = nl_attr_get_unspec(a,
+			    sizeof (struct ovs_action_push_vlan));
+
+			(void) snprintf(buf, sizeof (buf), "%u",
+			    ntohs(vlan->vlan_tci));
+			err = dlmgr_DLValue_putstring(prop, "vlan-tag", buf,
+			    dstr, sizeof (dstr));
+			break;
+		}
+		case OVS_ACTION_ATTR_POP_VLAN:
+			(void) snprintf(buf, sizeof (buf), "on");
+			err = dlmgr_DLValue_putstring(prop,
+			    "vlan-strip", buf, dstr, sizeof (dstr));
+			break;
+		case OVS_ACTION_ATTR_RECIRC:
+		case OVS_ACTION_ATTR_HASH:
+		case OVS_ACTION_ATTR_PUSH_MPLS:
+		case OVS_ACTION_ATTR_POP_MPLS:
+		case OVS_ACTION_ATTR_SAMPLE:
+			/* TBD */
+			err = EOPNOTSUPP;
+			dpif_log(err, "solaris_nlattr_to_DLVal type %d "
+			    "not supported", type);
+		case OVS_ACTION_ATTR_UNSPEC:
+		case __OVS_ACTION_ATTR_MAX:
+			OVS_NOT_REACHED();
+		}
+		if (err != 0)
+			goto out;
+	}
+	if (type == OVS_ACTION_ATTR_OUTPUT) {
+		dpif_log(0, "solaris_nlattr_to_DLVal outports total %d ports",
+		    nofports);
+		err = solaris_outports_action_to_DLVal(prop, cookie, ofports,
+		    nofports, queueid, dstr, sizeof (dstr));
+	}
+out:
+	dpif_log(err, "solaris_nlattr_to_DLVal %s", dstr);
+	return (err);
+}
+
+int
+solaris_add_flow(void *cookie, const char *linkname,
+    const char *flowname, struct flow *f, struct flow *m,
+    const struct nlattr *actions_nlattr, size_t actions_len)
+{
+	dlmgr_DLDict_t dff, dfm, *dffp, *dfmp;
+	dlmgr__rad_dict_string_DLValue_t *prop = NULL;
+	rc_instance_t *link = NULL, *flow = NULL;
+	dlmgr_DatalinkError_t *derrp = NULL;
+	int err = 0;
+	rc_err_t status;
+
+	bzero(&dff, sizeof (dff));
+	bzero(&dfm, sizeof (dfm));
+	dffp = &dff;
+	dfmp = &dfm;
+	status = dlmgr_Datalink__rad_lookup(rad_conn, B_TRUE, &link, 1,
+	    "name", linkname);
+	if (status != RCE_OK) {
+		err = ENODEV;
+		goto out;
+	}
+
+	dff.ddld_map = dlmgr__rad_dict_string_DLValue_create(link);
+	if (dff.ddld_map == NULL) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	dfm.ddld_map = dlmgr__rad_dict_string_DLValue_create(link);
+	if (dfm.ddld_map == NULL) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	prop = dlmgr__rad_dict_string_DLValue_create(link);
+	if (prop == NULL) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	err = solaris_flow_to_DLVal(f, m, dff.ddld_map, dfm.ddld_map);
+	if (err != 0)
+		goto out;
+
+	err = solaris_nlattr_to_DLVal(cookie, actions_nlattr,
+	    actions_len, &prop);
+	if (err != 0)
+		goto out;
+
+	status = dlmgr_Datalink_addFlow(link, flowname, &dffp, 1, &dfmp, 1,
+	    prop, &flow, &derrp);
+	if (status != RCE_OK) {
+		err = -1;
+		if (status == RCE_SERVER_OBJECT) {
+			err = derrp->dde_err;
+			dpif_log(err,
+			    "failed Datalink_addFlow(%s, %s): %s",
+			    flowname, linkname, derrp->dde_errmsg);
+		}
+	}
+
+	rc_instance_rele(flow);
+	dlmgr_DatalinkError_free(derrp);
+
+out:
+	dlmgr__rad_dict_string_DLValue_free(dfm.ddld_map);
+	dlmgr__rad_dict_string_DLValue_free(dff.ddld_map);
+	dlmgr__rad_dict_string_DLValue_free(prop);
+	rc_instance_rele(link);
+	return (err);
+}
+
+int
+solaris_modify_flow(void *cookie, const char *flowname,
+    const struct nlattr *actions_nlattr, size_t actions_len)
+{
+	rc_instance_t *flow = NULL;
+	dlmgr__rad_dict_string_DLValue_t *prop = NULL;
+	dlmgr_DatalinkError_t *derrp = NULL;
+	rc_err_t status;
+	int err;
+
+	status = dlmgr_Flow__rad_lookup(rad_conn, B_TRUE, &flow, 1,
+	    "name", flowname);
+	if (status != RCE_OK) {
+		err = ENODEV;
+		goto out;
+	}
+
+	prop = dlmgr__rad_dict_string_DLValue_create(flow);
+	if (prop == NULL) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	err = solaris_nlattr_to_DLVal(cookie, actions_nlattr,
+	    actions_len, &prop);
+	if (err != 0)
+		goto out;
+
+	status = dlmgr_Flow_setProperties(flow, prop, &derrp);
+	if (status != RCE_OK) {
+		err = -1;
+		if (status == RCE_SERVER_OBJECT) {
+			err = derrp->dde_err;
+			dpif_log(err,
+			    "failed Flow_setProperties(%s): %s",
+			    flowname, derrp->dde_errmsg);
+		}
+	}
+
+	dlmgr_DatalinkError_free(derrp);
+
+out:
+	dlmgr__rad_dict_string_DLValue_free(prop);
+	rc_instance_rele(flow);
+	return (err);
+}
+
+int
+solaris_remove_flow(const char *linkname, const char *flowname)
+{
+	dlmgr__rad_dict_string_DLValue_t *prop = NULL;
+	dlmgr_DatalinkError_t		*derrp = NULL;
+	dlmgr_DLValue_t			*old_val = NULL;
+	dlmgr_DLValue_t			val;
+	rc_instance_t			*link = NULL;
+	rc_err_t			status;
+	int				error = 0;
+
+	status = dlmgr_Datalink__rad_lookup(rad_conn, B_TRUE, &link, 1,
+	    "name", linkname);
+	if (status != RCE_OK) {
+		error = ENODEV;
+		goto out;
+	}
+
+	prop = dlmgr__rad_dict_string_DLValue_create(link);
+	if (prop == NULL) {
+		error = EINVAL;
+		goto out;
+	}
+
+	bzero(&val, sizeof (val));
+	val.ddlv_type = DDLVT_BOOLEAN;
+	val.ddlv_bval = &b_true;
+	status = dlmgr__rad_dict_string_DLValue_put(prop, "temporary",
+	    &val, &old_val);
+	if (status != RCE_OK) {
+		error = EINVAL;
+		goto out;
+	}
+	dlmgr_DLValue_free(old_val);
+
+	status = dlmgr_Datalink_removeFlow(link, flowname, prop, &derrp);
+	if (status != RCE_OK) {
+		error = -1;
+		if (status == RCE_SERVER_OBJECT) {
+			error = derrp->dde_err;
+			dpif_log(error, "failed Datalink_removeFlow(%s): %s",
+			    flowname, derrp->dde_errmsg);
+		}
+	}
+	dlmgr_DatalinkError_free(derrp);
+out:
+	dlmgr__rad_dict_string_DLValue_free(prop);
+	rc_instance_rele(link);
+	return (error);
+}
+
+static rc_err_t
+solaris_flowinfo2flowmap(const char *key, dlmgr_DLValue_t *val, void *arg)
+{
+	struct flow *f = arg;
+	in6_addr_t fa;
+	struct in_addr v4;
+	int af, err = 0;
+
+	if (strcmp(key, "dst-mac") == 0) {
+		err = flow_str2mac(val->ddlv_sval, f->dl_dst, 6);
+	} else if (strcmp(key, "src-mac") == 0) {
+		err = flow_str2mac(val->ddlv_sval, f->dl_src, 6);
+	} else if (strcmp(key, "local-ip") == 0 ||
+	    strcmp(key, "remote-ip") == 0 || strcmp(key, "arp-sip") == 0 ||
+	    strcmp(key, "arp-tip") == 0) {
+		err = flow_str2addr(val->ddlv_sval, &fa, &af);
+		if (err != 0)
+			goto out;
+		if (af == AF_INET) {
+			IN6_V4MAPPED_TO_INADDR(&fa, &v4);
+			if (strcmp(key, "local-ip") == 0 ||
+			    strcmp(key, "arp-sip") == 0) {
+				f->nw_src = v4.s_addr;
+			} else {
+				f->nw_dst = v4.s_addr;
+			}
+		} else {
+			if (strcmp(key, "local-ip") == 0) {
+				bcopy(&fa, &f->ipv6_src, sizeof (f->ipv6_src));
+			} else if (strcmp(key, "remote-ip") == 0) {
+				bcopy(&fa, &f->ipv6_dst, sizeof (f->ipv6_dst));
+			} else {
+				err = EINVAL;
+			}
+		}
+	} else if (strcmp(key, "transport") == 0) {
+		f->nw_proto = (uint8_t)*val->ddlv_ulval;
+	} else if (strcmp(key, "local-port") == 0) {
+		f->tp_src = htons((uint16_t)*val->ddlv_ulval);
+	} else if (strcmp(key, "remote-port") == 0) {
+		f->tp_dst = htons((uint16_t)*val->ddlv_ulval);
+	} else if (strcmp(key, "dsfield") == 0) {
+		f->nw_tos = (uint8_t)*val->ddlv_ulval;
+	} else if (strcmp(key, "srcport") == 0) {
+		f->in_port.odp_port = (odp_port_t)*val->ddlv_ulval;
+	} else if (strcmp(key, "arp-target") == 0) {
+		err = flow_str2mac(val->ddlv_sval, f->arp_tha, 6);
+	} else if (strcmp(key, "arp-sender") == 0) {
+		err = flow_str2mac(val->ddlv_sval, f->arp_sha, 6);
+	} else if (strcmp(key, "arp-op") == 0) {
+		f->nw_proto = (uint8_t)*val->ddlv_ulval;
+	} else if (strcmp(key, "sap") == 0) {
+		f->dl_type = htons((uint16_t)*val->ddlv_ulval);
+	} else if (strcmp(key, "ttl") == 0) {
+		f->nw_ttl = (uint8_t)*val->ddlv_ulval;
+	} else if (strcmp(key, "vlan-tci") == 0) {
+		f->vlan_tci = htons((uint16_t)*val->ddlv_ulval);
+	} else if (strcmp(key, "icmp-type") == 0) {
+		f->tp_src = htons((uint16_t)*val->ddlv_ulval);
+	} else if (strcmp(key, "icmp-code") == 0) {
+		f->tp_dst = htons((uint16_t)*val->ddlv_ulval);
+	} else if (strcmp(key, "tcp-flags") == 0) {
+		f->tcp_flags = htons((uint16_t)*val->ddlv_ulval);
+	}
+
+out:
+	dlmgr_DLValue_free(val);
+	return (err == 0 ? RCE_OK : -1);
+}
+
+static int
+solaris_flowinfo2flow(dlmgr__rad_dict_string_DLValue_t *flowinfo,
+    struct flow *f, struct flow *m)
+{
+	dlmgr__rad_dict_string_DLValue_t *fdict, *mdict;
+	dlmgr_DLValue_t  *flist = NULL, *mlist = NULL;
+	rc_err_t status;
+
+	bzero(f, sizeof (*f));
+	bzero(m, sizeof (*m));
+
+	status = dlmgr__rad_dict_string_DLValue_get(flowinfo, "filters",
+	    &flist);
+	if (status != RCE_OK)
+		return (EINVAL);
+
+	fdict = flist->ddlv_dlist[0]->ddld_map;
+
+	status = dlmgr__rad_dict_string_DLValue_map(fdict,
+	    solaris_flowinfo2flowmap, f);
+	dlmgr_DLValue_free(flist);
+	if (status != RCE_OK)
+		return (EINVAL);
+
+	status = dlmgr__rad_dict_string_DLValue_get(flowinfo, "masks", &mlist);
+	if (status != RCE_OK)
+		return (EINVAL);
+
+	mdict = mlist->ddlv_dlist[0]->ddld_map;
+
+	status = dlmgr__rad_dict_string_DLValue_map(mdict,
+	    solaris_flowinfo2flowmap, m);
+	dlmgr_DLValue_free(mlist);
+	if (status != RCE_OK)
+		return (EINVAL);
+	return (0);
+}
+
+int
+solaris_get_flowattr(const char *flowname, struct flow *f, struct flow *m)
+{
+	dlmgr__rad_dict_string_DLValue_t *flowinfo = NULL;
+	dlmgr_DatalinkError_t	*derrp = NULL;
+	rc_instance_t		*flow = NULL;
+	rc_err_t		status;
+	int			err = 0;
+
+	status = dlmgr_Flow__rad_lookup(rad_conn, B_TRUE, &flow, 1,
+	    "name", flowname);
+	if (status != RCE_OK) {
+		return (ENODEV);
+	}
+
+	status = dlmgr_Flow_getInfo(flow, NULL, 0, &flowinfo, &derrp);
+	if (status != RCE_OK) {
+		err = -1;
+		if (status == RCE_SERVER_OBJECT) {
+			err = derrp->dde_err;
+			/*
+			 * XXX For now, log DDLSTATUS_NOT_FOUND as debug until
+			 * we can determine how to handle the RAD caching
+			 * issue.
+			 */
+			dpif_log(err == DDLSTATUS_NOT_FOUND ? 0 : err,
+			    "failed Flow_getInfo(%s): %s",
+			    flowname, derrp->dde_errmsg);
+		}
+	}
+
+	dlmgr_DatalinkError_free(derrp);
+	rc_instance_rele(flow);
+
+	if (err == 0) {
+		err = solaris_flowinfo2flow(flowinfo, f, m);
+		dlmgr__rad_dict_string_DLValue_free(flowinfo);
+	}
+
+	return (err);
+}
+
+static int
+flow_propval2action_drop(char **propvals, int nval,
+    struct ofpbuf *action OVS_UNUSED)
+{
+	if (nval == 1 && strcmp(propvals[0], "drop") == 0)
+		return (0);
+
+	return (EINVAL);
+}
+
+static int
+flow_propval2action_outports(char **propvals, int nval, struct ofpbuf *action)
+{
+	mac_propval_range_t *pv_range = NULL;
+	uint32_t ofports[MAC_OF_MAXPORT], i, nofports = MAC_OF_MAXPORT;
+	dladm_status_t status;
+	int err = 0;
+
+	status = dladm_strs2range(propvals, nval, MAC_PROPVAL_UINT32,
+	    &pv_range);
+	if (status != DLADM_STATUS_OK)
+		return (solaris_dladm_status2error(status));
+
+	/* Convert mac_propval_range to a single ofports list */
+	status = dladm_range2list(pv_range, ofports, &nofports);
+	if (status != DLADM_STATUS_OK) {
+		err = solaris_dladm_status2error(status);
+		goto out;
+	}
+	for (i = 0; i < nofports; i++)
+		nl_msg_put_u32(action, OVS_ACTION_ATTR_OUTPUT, ofports[i]);
+
+out:
+	free(pv_range);
+	return (err);
+}
+
+static int
+flow_propval2action_setpri(char **propvals OVS_UNUSED, int nval OVS_UNUSED,
+    struct ofpbuf *action OVS_UNUSED)
+{
+	/* TBD */
+	return (0);
+}
+
+void
+slowpath_to_actions(enum slow_path_reason reason, struct ofpbuf *buf)
+{
+	union user_action_cookie cookie;
+
+	cookie.type = USER_ACTION_COOKIE_SLOW_PATH;
+	cookie.slow_path.unused = 0;
+	cookie.slow_path.reason = reason;
+
+	odp_put_userspace_action(0, &cookie, sizeof (cookie.slow_path), buf);
+}
+
+static int
+flow_propval2action_controller(char **propvals OVS_UNUSED, int nval OVS_UNUSED,
+    struct ofpbuf *action)
+{
+	slowpath_to_actions(SLOW_CONTROLLER, action);
+	return (0);
+}
+
+static int
+flow_propval2action_pushvlan(char **propvals, int nval OVS_UNUSED,
+    struct ofpbuf *action)
+{
+	char *endp = NULL;
+	int64_t n;
+	struct ovs_action_push_vlan push;
+	int tpid = ETH_TYPE_VLAN;
+
+	errno = 0;
+	n = strtoull(propvals[0], &endp, 10);
+	if ((errno != 0) || *endp != '\0' || n > 0xffff)
+		return (EINVAL);
+
+	push.vlan_tpid = htons(tpid);
+	push.vlan_tci = htons((uint16_t)n);
+	nl_msg_put_unspec(action, OVS_ACTION_ATTR_PUSH_VLAN,
+	    &push, sizeof (push));
+	return (0);
+}
+
+static int
+flow_propval2action_popvlan(char **propvals OVS_UNUSED, int nval OVS_UNUSED,
+    struct ofpbuf *action)
+{
+	nl_msg_put_flag(action, OVS_ACTION_ATTR_POP_VLAN);
+	return (0);
+}
+
+static int
+flow_propval2action_setether(char **propvals, int nval, struct ofpbuf *action)
+{
+	char pval[DLADM_PROP_VAL_MAX];
+	struct ovs_key_ethernet eth_key;
+	uchar_t *etheraddr;
+	int etheraddrlen;
+	boolean_t src_set, dst_set;
+	char *sep;
+	size_t start_ofs;
+	uint_t i;
+	int err = 0;
+
+	/*
+	 * The property value is in the format of "ether_src:xxx"
+	 * "ether_dst:xxx"
+	 */
+	bzero(&eth_key, sizeof (eth_key));
+	src_set = dst_set = B_FALSE;
+	for (i = 0; i < nval; i++) {
+		bcopy(propvals[i], pval, strlen(propvals[i]) + 1);
+		if ((sep = strchr(pval, ':')) == NULL) {
+			err = EINVAL;
+			goto out;
+		}
+		*sep = '\0';
+		sep++;
+		if ((etheraddr = _link_aton(sep, &etheraddrlen)) == NULL) {
+			err = (etheraddrlen == -1) ? EINVAL : ENOMEM;
+			goto out;
+		}
+		/* Only ethernet address is supported */
+		if (etheraddrlen != ETHERADDRL) {
+			err = EINVAL;
+			free(etheraddr);
+			goto out;
+		}
+		if (strcmp(pval, "ether_src") == 0) {
+			if (src_set) {
+				err = EINVAL;
+			} else {
+				bcopy(etheraddr, &eth_key.eth_src, ETHERADDRL);
+				src_set = _B_TRUE;
+			}
+		} else if (strcmp(pval, "ether_dst") == 0) {
+			if (dst_set) {
+				err = EINVAL;
+			} else {
+				bcopy(etheraddr, &eth_key.eth_dst, ETHERADDRL);
+				dst_set = _B_TRUE;
+			}
+		} else {
+			err = EINVAL;
+		}
+		free(etheraddr);
+		if (err != 0)
+			goto out;
+	}
+	if (!src_set || !dst_set) {
+		err = EINVAL;
+		goto out;
+	}
+
+	start_ofs = nl_msg_start_nested(action, OVS_ACTION_ATTR_SET);
+	nl_msg_put_unspec(action, OVS_KEY_ATTR_ETHERNET, &eth_key,
+	    sizeof (eth_key));
+	nl_msg_end_nested(action, start_ofs);
+out:
+	return (err);
+}
+
+static int
+flow_propval2action_setipv4(char **propvals, int nval, struct ofpbuf *action)
+{
+	struct ovs_key_ipv4 ipv4;
+	char pval[DLADM_PROP_VAL_MAX];
+	char *sep, *endp;
+	uint_t i, value;
+	uint8_t protocol;
+	size_t start_ofs;
+	int err = EINVAL;
+	boolean_t tos_set = B_FALSE;
+
+	/*
+	 * The property value is in the format of "src:xxx" "dst:xxx"
+	 * "protocol:xxx" "tos:xxx" "hoplimit:xxx"
+	 */
+	bzero(&ipv4, sizeof (ipv4));
+	for (i = 0; i < nval; i++) {
+		bcopy(propvals[i], pval, strlen(propvals[i]) + 1);
+		if ((sep = strchr(pval, ':')) == NULL) {
+			err = EINVAL;
+			goto out;
+		}
+		*sep = '\0';
+		sep++;
+		if (strcmp(pval, "src") == 0) {
+			if (ipv4.ipv4_src != 0)
+				goto out;
+			if (inet_pton(AF_INET, sep, &ipv4.ipv4_src) != 1)
+				goto out;
+		} else if (strcmp(pval, "dst") == 0) {
+			if (ipv4.ipv4_dst != 0)
+				goto out;
+			if (inet_pton(AF_INET, sep, &ipv4.ipv4_dst) != 1)
+				goto out;
+		} else if (strcmp(pval, "protocol") == 0) {
+			if (ipv4.ipv4_proto != 0)
+				goto out;
+			protocol = solaris_str2proto(sep);
+			if (protocol == 0)
+				goto out;
+			ipv4.ipv4_proto = protocol;
+		} else if (strcmp(pval, "tos") == 0) {
+			if (tos_set)
+				goto out;
+			errno = 0;
+			endp = NULL;
+			value = strtoul(sep, &endp, 16);
+			if (errno != 0 || value > 0xff || *endp != '\0')
+				goto out;
+			tos_set = B_TRUE;
+			ipv4.ipv4_tos = value;
+		} else if (strcmp(pval, "hoplimit") == 0) {
+			if (ipv4.ipv4_ttl != 0)
+				goto out;
+			errno = 0;
+			endp = NULL;
+			value = strtoul(sep, &endp, 10);
+			if (errno != 0 || value == 0 || value > 0xff ||
+			    *endp != '\0')
+				goto out;
+			ipv4.ipv4_ttl = value;
+		}
+	}
+	start_ofs = nl_msg_start_nested(action, OVS_ACTION_ATTR_SET);
+	nl_msg_put_unspec(action, OVS_KEY_ATTR_IPV4, &ipv4, sizeof (ipv4));
+	nl_msg_end_nested(action, start_ofs);
+	err = 0;
+out:
+	return (err);
+}
+
+static int
+flow_propval2action_setipv6(char **propvals, int nval, struct ofpbuf *action)
+{
+	struct ovs_key_ipv6 ipv6;
+	char pval[DLADM_PROP_VAL_MAX];
+	char *sep, *endp;
+	uint_t i, value;
+	uint8_t protocol;
+	struct in6_addr in6;
+	size_t start_ofs;
+	int err = EINVAL;
+	boolean_t tos_set = B_FALSE;
+
+	/*
+	 * The property value is in the format of "src:xxx" "dst:xxx"
+	 * "label:0xxxxx" "protocol:xxx" "tos:xxx" "hoplimit:xxx"
+	 */
+	bzero(&ipv6, sizeof (ipv6));
+	for (i = 0; i < nval; i++) {
+		bcopy(propvals[i], pval, strlen(propvals[i]) + 1);
+		if ((sep = strchr(pval, ':')) == NULL)
+			goto out;
+		*sep = '\0';
+		sep++;
+		if (strcmp(pval, "src") == 0) {
+			bcopy(&ipv6.ipv6_src, &in6.s6_addr,
+			    sizeof (struct in6_addr));
+			if (!IN6_IS_ADDR_UNSPECIFIED(&in6))
+				goto out;
+			if (inet_pton(AF_INET6, sep, ipv6.ipv6_src) != 1)
+				goto out;
+		} else if (strcmp(pval, "dst") == 0) {
+			bcopy(&ipv6.ipv6_dst, &in6.s6_addr,
+			    sizeof (struct in6_addr));
+			if (!IN6_IS_ADDR_UNSPECIFIED(&in6))
+				goto out;
+			if (inet_pton(AF_INET6, sep, ipv6.ipv6_dst) != 1)
+				goto out;
+		} else if (strcmp(pval, "protocol") == 0) {
+			if (ipv6.ipv6_proto != 0)
+				goto out;
+			protocol = solaris_str2proto(sep);
+			if (protocol == 0)
+				goto out;
+			ipv6.ipv6_proto = protocol;
+		} else if (strcmp(pval, "tos") == 0) {
+			if (tos_set)
+				goto out;
+			errno = 0;
+			endp = NULL;
+			value = strtoul(sep, &endp, 16);
+			if (errno != 0 || value > 0xff || *endp != '\0')
+				goto out;
+			tos_set = B_TRUE;
+			ipv6.ipv6_tclass = value;
+		} else if (strcmp(pval, "ttl") == 0) {
+			if (ipv6.ipv6_hlimit != 0)
+				goto out;
+			errno = 0;
+			endp = NULL;
+			value = strtoul(sep, &endp, 10);
+			if (errno != 0 || value == 0 || value > 0xff ||
+			    *endp != '\0') {
+				goto out;
+			}
+			ipv6.ipv6_hlimit = value;
+		} else if (strcmp(pval, "label") == 0) {
+			if (ipv6.ipv6_label != 0)
+				goto out;
+			errno = 0;
+			endp = NULL;
+			value = strtoul(sep, &endp, 16);
+			if (errno != 0 || value == 0 || value > 0xff ||
+			    *endp != '\0') {
+				goto out;
+			}
+			ipv6.ipv6_label = value;
+		}
+	}
+	start_ofs = nl_msg_start_nested(action, OVS_ACTION_ATTR_SET);
+	nl_msg_put_unspec(action, OVS_KEY_ATTR_IPV6, &ipv6, sizeof (ipv6));
+	nl_msg_end_nested(action, start_ofs);
+	err = 0;
+out:
+	return (err);
+}
+
+static int
+flow_propval2action_settransport(char **propvals, int nval,
+    uint16_t *sportp, uint16_t *dportp)
+{
+	char pval[DLADM_PROP_VAL_MAX];
+	char *sep, *endp;
+	uint_t i, value;
+	int err = EINVAL;
+	uint16_t sport = 0, dport = 0;
+
+	/* The property value is in the format of "sport:xxx" "dport:xxx" */
+	for (i = 0; i < nval; i++) {
+		bcopy(propvals, pval, strlen(propvals[i]) + 1);
+		if ((sep = strchr(pval, ':')) == NULL)
+			goto out;
+		*sep = '\0';
+		sep++;
+		if (strcmp(pval, "sport") == 0) {
+			if (sport != 0)
+				goto out;
+			errno = 0;
+			endp = NULL;
+			value = strtoul(sep, &endp, 10);
+			if (errno != 0 || value == 0 || value > 0xffff ||
+			    *endp != '\0') {
+				goto out;
+			}
+			sport = value;
+		} else if (strcmp(pval, "dport") == 0) {
+			if (dport != 0)
+				return (DLADM_STATUS_DUPLICATE_ARG);
+			errno = 0;
+			endp = NULL;
+			value = strtoul(sep, &endp, 10);
+			if (errno != 0 || value == 0 || value > 0xffff ||
+			    *endp != '\0') {
+				goto out;
+			}
+			dport = value;
+		}
+	}
+	err = 0;
+out:
+	*sportp = sport;
+	*dportp = dport;
+	return (err);
+}
+
+static int
+flow_propval2action_settcp(char **propvals, int nval, struct ofpbuf *action)
+{
+	struct ovs_key_tcp tcp;
+	size_t start_ofs;
+	int err;
+
+	err = flow_propval2action_settransport(propvals, nval, &tcp.tcp_src,
+	    &tcp.tcp_dst);
+	if (err != 0)
+		return (err);
+
+	start_ofs = nl_msg_start_nested(action, OVS_ACTION_ATTR_SET);
+	nl_msg_put_unspec(action, OVS_KEY_ATTR_TCP, &tcp, sizeof (tcp));
+	nl_msg_end_nested(action, start_ofs);
+	return (0);
+}
+
+static int
+flow_propval2action_setudp(char **propvals, int nval, struct ofpbuf *action)
+{
+	struct ovs_key_udp udp;
+	size_t start_ofs;
+	int err;
+
+	err = flow_propval2action_settransport(propvals, nval, &udp.udp_src,
+	    &udp.udp_dst);
+	if (err != 0)
+		return (err);
+
+	start_ofs = nl_msg_start_nested(action, OVS_ACTION_ATTR_SET);
+	nl_msg_put_unspec(action, OVS_KEY_ATTR_UDP, &udp, sizeof (udp));
+	nl_msg_end_nested(action, start_ofs);
+	return (0);
+}
+
+static int
+flow_propval2action_setsctp(char **propvals, int nval, struct ofpbuf *action)
+{
+	struct ovs_key_sctp sctp;
+	size_t start_ofs;
+	int err;
+
+	err = flow_propval2action_settransport(propvals, nval, &sctp.sctp_src,
+	    &sctp.sctp_dst);
+	if (err != 0)
+		return (err);
+
+	start_ofs = nl_msg_start_nested(action, OVS_ACTION_ATTR_SET);
+	nl_msg_put_unspec(action, OVS_KEY_ATTR_SCTP, &sctp, sizeof (sctp));
+	nl_msg_end_nested(action, start_ofs);
+	return (0);
+}
+
+static int
+set_addr(char *pval, uint32_t *addrp)
+{
+	struct addrinfo		hints;
+	struct addrinfo		*ai;
+	struct addrinfo		*next_ai;
+	void			*ptr;
+
+	(void) memset(&hints, 0, sizeof (hints));
+	hints.ai_family = AF_UNSPEC;
+	if (getaddrinfo(pval, NULL, &hints, &ai) != 0)
+		return (EINVAL);
+
+	/* Check if hostname resolves to multiple addresses */
+	for (next_ai = ai->ai_next; next_ai != NULL;
+	    next_ai = next_ai->ai_next) {
+		if (next_ai->ai_addrlen != ai->ai_addrlen ||
+		    bcmp(next_ai->ai_addr, ai->ai_addr,
+		    ai->ai_addrlen) != 0) {
+			/* maps to more than one address */
+			freeaddrinfo(ai);
+			return (EINVAL);
+		}
+	}
+	if (ai->ai_family != AF_INET) {
+		freeaddrinfo(ai);
+		return (EINVAL);
+	}
+
+	ptr = ((uint8_t *)ai->ai_addr) +
+	    offsetof(struct sockaddr_in, sin_addr);
+	memcpy(addrp, ptr, sizeof (struct in_addr));
+	freeaddrinfo(ai);
+	return (0);
+}
+
+static int
+flow_propval2action_settnl(char **propvals, int nval, struct ofpbuf *action)
+{
+	char pval[DLADM_PROP_VAL_MAX];
+	char *sep, *endp;
+	uint_t i, value;
+	struct flow_tnl tnl;
+	size_t start_ofs;
+	boolean_t id_set, src_set, dst_set, tos_set, ttl_set;
+	int err = 0;
+
+	id_set = src_set = dst_set = tos_set = ttl_set = B_FALSE;
+
+	/*
+	 * The property value is in the format of "src:xxx" "dst:xxx"
+	 * "tun_id:0x%x" "tos:0x%x" "hoplimit:xxx"
+	 */
+	tnl.ip_tos = 0xff;
+	for (i = 0; i < nval; i++) {
+		bcopy(propvals[i], pval, strlen(propvals[i]) + 1);
+		if ((sep = strchr(pval, ':')) == NULL)
+			return (EINVAL);
+		*sep = '\0';
+		sep++;
+		if (strcmp(pval, "tun_id") == 0) {
+			if (id_set)
+				return (EINVAL);
+			errno = 0;
+			endp = NULL;
+			value = strtoull(sep, &endp, 16);
+			if (errno != 0 || *endp != '\0')
+				return (EINVAL);
+			tnl.tun_id = value;
+			id_set = B_TRUE;
+		} else if (strcmp(pval, "src") == 0) {
+			if (src_set)
+				return (EINVAL);
+			err = set_addr(sep, &tnl.ip_src);
+			if (err != 0)
+				return (err);
+			src_set = B_TRUE;
+		} else if (strcmp(pval, "dst") == 0) {
+			if (dst_set)
+				return (EINVAL);
+			err = set_addr(sep, &tnl.ip_dst);
+			if (err != 0)
+				return (err);
+			dst_set = B_TRUE;
+		} else if (strcmp(pval, "tos") == 0) {
+			if (tos_set)
+				return (EINVAL);
+			errno = 0;
+			endp = NULL;
+			value = strtoul(sep, &endp, 16);
+			if (errno != 0 || value > 0xff || *endp != '\0')
+				return (EINVAL);
+			tnl.ip_tos = value;
+			tos_set = B_TRUE;
+		} else if (strcmp(pval, "hoplimit") == 0) {
+			if (ttl_set)
+				return (EINVAL);
+			errno = 0;
+			endp = NULL;
+			value = strtoul(sep, &endp, 10);
+			if (errno != 0 || value == 0 || value > 0xff ||
+			    *endp != '\0') {
+				return (EINVAL);
+			}
+			tnl.ip_ttl = value;
+			ttl_set = B_TRUE;
+		}
+	}
+
+	start_ofs = nl_msg_start_nested(action, OVS_ACTION_ATTR_SET);
+	nl_msg_put_be64(action, OVS_TUNNEL_KEY_ATTR_ID, (uint64_t)tnl.tun_id);
+	nl_msg_put_be32(action, OVS_TUNNEL_KEY_ATTR_IPV4_SRC, tnl.ip_src);
+	nl_msg_put_be32(action, OVS_TUNNEL_KEY_ATTR_IPV4_DST, tnl.ip_dst);
+	nl_msg_put_u8(action, OVS_TUNNEL_KEY_ATTR_TOS, tnl.ip_tos);
+	nl_msg_put_u8(action, OVS_TUNNEL_KEY_ATTR_TTL, tnl.ip_ttl);
+	nl_msg_put_flag(action, OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT);
+	nl_msg_end_nested(action, start_ofs);
+	return (0);
+}
+
+static rc_err_t
+solaris_flowinfo2actionmap(const char *key, dlmgr_DLValue_t *val,
+    void *arg)
+{
+	struct ofpbuf *action = arg;
+	char **propvals, *buf = NULL;
+	int valcnt, err = 0, i;
+
+	buf = malloc((sizeof (char *) +
+	    DLADM_PROP_VAL_MAX) * DLADM_MAX_PROP_VALCNT);
+
+	propvals = (char **)(void *)buf;
+	for (i = 0; i < DLADM_MAX_PROP_VALCNT; i++) {
+		propvals[i] = buf + sizeof (char *) * DLADM_MAX_PROP_VALCNT +
+		    i * DLADM_PROP_VAL_MAX;
+	}
+
+	switch (val->ddlv_type) {
+	case DDLVT_STRING:
+		if (val->ddlv_sval == NULL)
+			goto out;
+		if (strlen(val->ddlv_sval) == 0)
+			goto out;
+		valcnt = 1;
+		memcpy(propvals[0], val->ddlv_sval, DLADM_PROP_VAL_MAX);
+		break;
+	case DDLVT_STRINGS:
+		if (val->ddlv_slist_count == 0)
+			goto out;
+		valcnt = val->ddlv_slist_count;
+		if (valcnt == 1 && strlen(val->ddlv_slist[0]) == 0)
+			goto out;
+		for (i = 0; i < val->ddlv_slist_count; i++) {
+			memcpy(propvals[i], val->ddlv_slist[i],
+			    DLADM_PROP_VAL_MAX);
+		}
+		break;
+	case DDLVT_ULONG:
+		if (val->ddlv_ulval == NULL || *val->ddlv_ulval == 0)
+			goto out;
+		valcnt = 1;
+		(void) snprintf(propvals[0], DLADM_PROP_VAL_MAX, "%llu",
+		    *val->ddlv_ulval);
+		break;
+	case DDLVT_BOOLEAN:
+	case DDLVT_BOOLEANS:
+	case DDLVT_LONG:
+	case DDLVT_LONGS:
+	case DDLVT_ULONGS:
+	case DDLVT_DICTIONARY:
+	case DDLVT_DICTIONARYS:
+	default:
+		goto out;
+	}
+	dpif_log(0, "solaris_flowinfo2actionmap %s:%d %s", key, valcnt,
+	    propvals[0]);
+
+	if (strcmp(key, "outports") == 0) {
+		err = flow_propval2action_drop(propvals, valcnt, action);
+		if (err == 0)
+			goto out;
+
+		err = flow_propval2action_outports(propvals, valcnt, action);
+	} else if (strcmp(key, "max-bw") == 0) {
+		err = flow_propval2action_setpri(propvals, valcnt, action);
+	} else if (strcmp(key, "controller") == 0) {
+		err = flow_propval2action_controller(propvals, valcnt, action);
+	} else if (strcmp(key, "vlan-tag") == 0) {
+		err = flow_propval2action_pushvlan(propvals, valcnt, action);
+	} else if (strcmp(key, "vlan-strip") == 0) {
+		err = flow_propval2action_popvlan(propvals, valcnt, action);
+	} else if (strcmp(key, "set-ether") == 0) {
+		err = flow_propval2action_setether(propvals, valcnt, action);
+	} else if (strcmp(key, "set-ipv4") == 0) {
+		err = flow_propval2action_setipv4(propvals, valcnt, action);
+	} else if (strcmp(key, "set-ipv6") == 0) {
+		err = flow_propval2action_setipv6(propvals, valcnt, action);
+	} else if (strcmp(key, "set-tcp") == 0) {
+		err = flow_propval2action_settcp(propvals, valcnt, action);
+	} else if (strcmp(key, "set-udp") == 0) {
+		err = flow_propval2action_setudp(propvals, valcnt, action);
+	} else if (strcmp(key, "set-sctp") == 0) {
+		err = flow_propval2action_setsctp(propvals, valcnt, action);
+	} else if (strcmp(key, "set-tunnel") == 0) {
+		err = flow_propval2action_settnl(propvals, valcnt, action);
+	}
+out:
+	dlmgr_DLValue_free(val);
+	free(buf);
+	return (err == 0 ? RCE_OK : -1);
+}
+
+static int
+solaris_flowinfo2action(dlmgr__rad_dict_string_DLValue_t *flowinfo,
+    struct ofpbuf *action)
+{
+	dlmgr__rad_dict_string_DLValue_t *fdict;
+	dlmgr_DLValue_t  *flist = NULL;
+	rc_err_t status;
+	int err = 0;
+
+	status = dlmgr__rad_dict_string_DLValue_get(flowinfo, "properties",
+	    &flist);
+	if (status != RCE_OK)
+		return (EINVAL);
+
+	fdict = flist->ddlv_dval;
+	status = dlmgr__rad_dict_string_DLValue_map(fdict,
+	    solaris_flowinfo2actionmap, action);
+	if (status != RCE_OK)
+		err = EINVAL;
+
+	dlmgr_DLValue_free(flist);
+	return (err);
+}
+
+int
+solaris_get_flowaction(const char *flowname, struct ofpbuf *action)
+{
+	dlmgr__rad_dict_string_DLValue_t *flowinfo = NULL;
+	dlmgr_DatalinkError_t	*derrp = NULL;
+	rc_instance_t		*flow = NULL;
+	rc_err_t		status;
+	int			err = 0;
+
+	status = dlmgr_Flow__rad_lookup(rad_conn, B_TRUE, &flow, 1,
+	    "name", flowname);
+	if (status != RCE_OK) {
+		return (ENODEV);
+	}
+
+	status = dlmgr_Flow_getInfo(flow, NULL, 0, &flowinfo, &derrp);
+	if (status != RCE_OK) {
+		err = -1;
+		if (status == RCE_SERVER_OBJECT) {
+			err = derrp->dde_err;
+			/*
+			 * XXX For now, log DDLSTATUS_NOT_FOUND as debug until
+			 * we can determine how to handle the RAD caching
+			 * issue.
+			 */
+			dpif_log(err == DDLSTATUS_NOT_FOUND ? 0 : err,
+			    "failed Flow_getInfo(%s): %s",
+			    flowname, derrp->dde_errmsg);
+		}
+	}
+
+	dlmgr_DatalinkError_free(derrp);
+	rc_instance_rele(flow);
+
+	if (err == 0) {
+		err = solaris_flowinfo2action(flowinfo, action);
+		dlmgr__rad_dict_string_DLValue_free(flowinfo);
+	}
+
+	return (err);
+}
+
+static int
+i_solaris_get_flowstats(rc_instance_t *flow, uint64_t *npackets,
+    uint64_t *nbytes, uint64_t *lastused)
+{
+	dlmgr__rad_dict_string_DLValue_t *flowstat = NULL;
+	dlmgr_DLValue_t		*dlval = NULL;
+	dlmgr_DatalinkError_t	*derrp = NULL;
+	rc_err_t		status;
+	int			err = 0;
+
+	status = dlmgr_Flow_getStatistics(flow, NULL, 0, &flowstat, &derrp);
+	if (status != RCE_OK) {
+		err = -1;
+		if (status == RCE_SERVER_OBJECT) {
+			err = derrp->dde_err;
+			dpif_log(err, "failed Flow_getStatistics(): %s",
+			    derrp->dde_errmsg);
+		}
+		goto out;
+	}
+
+#if _TBD_
+	uint16_t tcp_flags; /* bitmaps of tcp_flags */
+#endif
+	status = dlmgr__rad_dict_string_DLValue_get(flowstat, "ipackets",
+	    &dlval);
+	if (status != RCE_OK) {
+		err = EINVAL;
+		goto out;
+	}
+	*npackets = *dlval->ddlv_ulval;
+	dlmgr_DLValue_free(dlval);
+
+	status = dlmgr__rad_dict_string_DLValue_get(flowstat, "opackets",
+	    &dlval);
+	if (status != RCE_OK) {
+		err = EINVAL;
+		goto out;
+	}
+	*npackets += *dlval->ddlv_ulval;
+	dlmgr_DLValue_free(dlval);
+
+	status = dlmgr__rad_dict_string_DLValue_get(flowstat, "ibytes",
+	    &dlval);
+	if (status != RCE_OK) {
+		err = EINVAL;
+		goto out;
+	}
+	*nbytes = *dlval->ddlv_ulval;
+	dlmgr_DLValue_free(dlval);
+
+	status = dlmgr__rad_dict_string_DLValue_get(flowstat, "obytes",
+	    &dlval);
+	if (status != RCE_OK) {
+		err = EINVAL;
+		goto out;
+	}
+	*nbytes += *dlval->ddlv_ulval;
+	dlmgr_DLValue_free(dlval);
+
+	status = dlmgr__rad_dict_string_DLValue_get(flowstat, "lastused",
+	    &dlval);
+	if (status != RCE_OK) {
+		err = EINVAL;
+		goto out;
+	}
+	*lastused = *dlval->ddlv_ulval;
+	dlmgr_DLValue_free(dlval);
+out:
+	dlmgr__rad_dict_string_DLValue_free(flowstat);
+	dlmgr_DatalinkError_free(derrp);
+	return (err);
+}
+
+int
+solaris_get_flowstats(const char *flowname, uint64_t *npackets,
+    uint64_t *nbytes, uint64_t *lastused)
+{
+	rc_err_t status;
+	rc_instance_t *flow = NULL;
+	int err;
+
+	status = dlmgr_Flow__rad_lookup(rad_conn, B_TRUE, &flow, 1,
+	    "name", flowname);
+	if (status != RCE_OK) {
+		err = ENODEV;
+		goto out;
+	}
+
+	err = i_solaris_get_flowstats(flow, npackets, nbytes, lastused);
+out:
+	rc_instance_rele(flow);
+	return (err);
+}
+
+boolean_t
+kstat_handle_init(kstat2_handle_t *khandlep)
+{
+	kstat2_status_t	stat;
+
+	stat = kstat2_open(khandlep);
+	return (stat != KSTAT2_S_OK ? B_FALSE: B_TRUE);
+}
+
+boolean_t
+kstat_handle_update(kstat2_handle_t khandle)
+{
+	kstat2_status_t	stat;
+
+	stat = kstat2_update(khandle);
+	return (stat != KSTAT2_S_OK ? B_FALSE: B_TRUE);
+}
+
+void
+kstat_handle_close(kstat2_handle_t *khandlep)
+{
+	kstat2_close(khandlep);
+}
+
+uint64_t
+get_nvvt_int(kstat2_map_t map, char *name)
+{
+	kstat2_status_t stat;
+	kstat2_nv_t val;
+
+	stat = kstat2_map_get(map, name, &val);
+	if (stat != KSTAT2_S_OK) {
+		(void) printf("can't get value: %s\n",
+		    kstat2_status_string(stat));
+		return (0);
+	}
+
+	if (val->type != KSTAT2_NVVT_INT) {
+		(void) printf("%s is not KSTAT2_NVVT_INT type\n", name);
+		return (0);
+	}
+
+	return (val->kstat2_integer);
+}
+
+void
+solaris_port_walk(void *arg, void (*fn)(void *, const char *, char *,
+    odp_port_t))
+{
+	adr_name_t	**anamearr = NULL;
+	int		anamecnt = 0, i;
+	rc_err_t	rerr;
+
+	rerr = dlmgr_Datalink__rad_list(rad_conn, B_FALSE, NS_GLOB, &anamearr,
+	    &anamecnt, 0);
+	if (rerr != RCE_OK)
+		return;
+
+	if (anamecnt == 0) {
+		free(anamearr);
+		return;
+	}
+
+	for (i = 0; i < anamecnt; i++) {
+		dlmgr_DLDict_t		**dlist = NULL;
+		dlmgr_DLValue_t		*dlval = NULL;
+		const char		*props[1];
+		const char		*fields[1];
+		int			ndlist = 0;
+		rc_instance_t		*rip = NULL;
+		dlmgr_DatalinkError_t	*derrp = NULL;
+		rc_err_t 		rerr;
+
+		rerr = rc_lookup(rad_conn, anamearr[i], NULL, B_FALSE, &rip);
+		if (rerr != RCE_OK)
+			continue;
+
+		props[0] = "ofport";
+		fields[0] = "current";
+		rerr = dlmgr_Datalink_getProperties(rip, props, 1, fields, 1,
+		    &dlist, &ndlist, &derrp);
+		rc_instance_rele(rip);
+		if (rerr != RCE_OK) {
+			if (rerr == RCE_SERVER_OBJECT) {
+				dpif_log(derrp->dde_err,
+				    "failed Datalink_getProperties(%s, %s): %s",
+				    adr_name_key(anamearr[i], "name"),
+				    props[0], derrp->dde_errmsg);
+			}
+			dlmgr_DatalinkError_free(derrp);
+			continue;
+		}
+		rerr = dlmgr__rad_dict_string_DLValue_get((*dlist)->ddld_map,
+		    "current", &dlval);
+		if (rerr != RCE_OK || dlval->ddlv_sval == NULL) {
+			dlmgr_DatalinkError_free(derrp);
+			dlmgr_DLValue_free(dlval);
+			dlmgr_DLDict_array_free(dlist, ndlist);
+			continue;
+		}
+
+		if (dlval->ddlv_sval != NULL && dlval->ddlv_sval[0] != '\0') {
+			fn(arg, adr_name_key(anamearr[i], "name"),
+			    "system", atoi(dlval->ddlv_sval));
+		}
+		dlmgr_DatalinkError_free(derrp);
+		dlmgr_DLValue_free(dlval);
+		dlmgr_DLDict_array_free(dlist, ndlist);
+	}
+	for (i = 0; i < anamecnt; i++)
+		adr_name_rele(anamearr[i]);
+	free(anamearr);
+}
+
+uint64_t
+solaris_flow_walk(void *arg, struct ofpbuf *action, boolean_t no_default,
+    void (*fn)(void *, const char *, boolean_t, struct flow *, struct flow *,
+    struct ofpbuf *, uint64_t, uint64_t, uint64_t))
+{
+	adr_name_t	**anamearr = NULL;
+	int		anamecnt = 0, i;
+	rc_err_t	rerr;
+	int		err = 0;
+	uint64_t	n_flows = 0;
+
+	rerr = dlmgr_Flow__rad_list(rad_conn, B_FALSE, NS_GLOB, &anamearr,
+	    &anamecnt, 0);
+	if (rerr != RCE_OK)
+		return (0);
+
+	if (anamecnt == 0) {
+		free(anamearr);
+		return (0);
+	}
+
+	for (i = 0; i < anamecnt; i++) {
+		dlmgr__rad_dict_string_DLValue_t *flowinfo;
+		char			linkname[MAXLINKNAMELEN];
+		rc_instance_t		*rip = NULL;
+		dlmgr_DLDict_t		**dlist;
+		dlmgr_DLValue_t		*dlval;
+		const char		*props[1];
+		const char		*fields[1];
+		struct flow		f, m;
+		int			ndlist = 0;
+		uint64_t		npackets, nbytes, lastused;
+		boolean_t		is_default;
+		dlmgr_DatalinkError_t	*derrp = NULL;
+
+		flowinfo = NULL;
+		dlist = NULL;
+		dlval = NULL;
+		is_default = B_FALSE;
+		if (strstr(adr_name_key(anamearr[i], "name"), "defflow") !=
+		    NULL) {
+			if (no_default)
+				continue;
+			else
+				is_default = B_TRUE;
+		}
+		rerr = rc_lookup(rad_conn, anamearr[i], NULL, B_FALSE, &rip);
+		if (rerr != RCE_OK)
+			continue;
+
+		if ((err = i_solaris_get_flowstats(rip, &npackets, &nbytes,
+		    &lastused)) != 0) {
+			dpif_log(err, "solaris_flow_walk get_flowstats "
+			    "failed for %s: %d", adr_name_key(anamearr[i],
+			    "name"), err);
+			rc_instance_rele(rip);
+			goto done;
+		}
+
+		rerr = dlmgr_Flow_getInfo(rip, NULL, 0, &flowinfo, &derrp);
+		rc_instance_rele(rip);
+		if (rerr != RCE_OK) {
+			err = -1;
+			if (rerr == RCE_SERVER_OBJECT) {
+				err = derrp->dde_err;
+				/*
+				 * XXX For now, log DDLSTATUS_NOT_FOUND as
+				 * debug until we can determine how to handle
+				 * the RAD caching issue.
+				 */
+				dpif_log(err == DDLSTATUS_NOT_FOUND ? 0 : err,
+				    "failed Flow_getInfo(%s): %s",
+				    adr_name_key(anamearr[i], "name"),
+				    derrp->dde_errmsg);
+			}
+			dlmgr_DatalinkError_free(derrp);
+			goto done;
+		}
+
+		/* See whether this flow is created over of enabled link */
+		err = solaris_flowinfo2linkname(flowinfo, linkname,
+		    MAXLINKNAMELEN);
+		if (err != 0) {
+			goto done;
+		}
+
+		rerr = dlmgr_Datalink__rad_lookup(rad_conn, B_TRUE, &rip,
+		    1, "name", linkname);
+		if (rerr != RCE_OK) {
+			goto done;
+		}
+
+		props[0] = "openvswitch";
+		fields[0] = "current";
+		rerr = dlmgr_Datalink_getProperties(rip, props, 1, fields, 1,
+		    &dlist, &ndlist, &derrp);
+		rc_instance_rele(rip);
+		if (rerr != RCE_OK) {
+			err = -1;
+			if (rerr == RCE_SERVER_OBJECT) {
+				err = derrp->dde_err;
+				dpif_log(err,
+				    "failed Datalink_getProperties(%s, %s): %s",
+				    linkname, props[0], derrp->dde_errmsg);
+			}
+			dlmgr_DatalinkError_free(derrp);
+			goto done;
+		}
+		rerr = dlmgr__rad_dict_string_DLValue_get((*dlist)->ddld_map,
+		    "current", &dlval);
+		if (rerr != RCE_OK)
+			goto done;
+
+		if (!dlval->ddlv_bval)
+			goto done;
+
+		err = solaris_flowinfo2flow(flowinfo, &f, &m);
+		if (err != 0) {
+			dpif_log(err, "solaris_flow_walk flowinfo2flow "
+			    "failed for %s: %d", adr_name_key(anamearr[i],
+			    "name"), err);
+			goto done;
+		}
+
+		if (action != NULL) {
+			err = solaris_flowinfo2action(flowinfo, action);
+			if (err != 0) {
+				dpif_log(err, "solaris_flow_walk "
+				    "flowinfo2flow failed for %s: %d",
+				    adr_name_key(anamearr[i], "name"), err);
+				goto done;
+			}
+		}
+
+		if (fn != NULL) {
+			fn(arg, adr_name_key(anamearr[i], "name"), is_default,
+			    &f, &m, action, npackets, nbytes, lastused);
+		}
+		n_flows++;
+done:
+		dlmgr__rad_dict_string_DLValue_free(flowinfo);
+		dlmgr_DLValue_free(dlval);
+		dlmgr_DLDict_array_free(dlist, ndlist);
+	}
+
+	for (i = 0; i < anamecnt; i++)
+		adr_name_rele(anamearr[i]);
+	free(anamearr);
+	return (n_flows);
+}
+
+int
+solaris_dladm_status2error(dladm_status_t status)
+{
+	int error;
+
+	if (status == DLADM_STATUS_NOMEM) {
+		error = ENOMEM;
+	} else if (status == DLADM_STATUS_DENIED) {
+		error = EPERM;
+	} else if (status == DLADM_STATUS_OK) {
+		error = 0;
+	} else if (status == DLADM_STATUS_IOERR) {
+		error = EIO;
+	} else {
+		error = EINVAL;
+	}
+	return (error);
+}
+
+boolean_t
+solaris_is_uplink_class(const char *class)
+{
+	return (strcmp("phys", class) == 0 ||
+	    strcmp("aggr", class) == 0 ||
+	    strcmp("etherstub", class) == 0 ||
+	    strcmp("vxlan", class) == 0 ||
+	    strcmp("simnet", class) == 0);
+}
+
+/*
+ * This is a copy of dlparse_zonelinkname() function in libinetutil. libinetutil
+ * is not a public interface, therefore we make a copy here.
+ *
+ * Given a linkname that can be specified using a zonename prefix retrieve
+ * the optional linkname and/or zone ID value. If no zonename prefix was
+ * specified we set the optional linkname and set optional zone ID return
+ * value to ALL_ZONES.
+ */
+boolean_t
+solaris_dlparse_zonelinkname(const char *name, char *link_name,
+    zoneid_t *zoneidp)
+{
+	char buffer[MAXLINKNAMESPECIFIER];
+	char *search = "/";
+	char *zonetoken;
+	char *linktoken;
+	char *last;
+	size_t namelen;
+
+	if (link_name != NULL)
+		link_name[0] = '\0';
+	if (zoneidp != NULL)
+		*zoneidp = ALL_ZONES;
+
+	if ((namelen = strlcpy(buffer, name, sizeof (buffer))) >=
+	    sizeof (buffer))
+		return (_B_FALSE);
+
+	if ((zonetoken = strtok_r(buffer, search, &last)) == NULL)
+		return (_B_FALSE);
+
+	/* If there are no other strings, return given name as linkname */
+	if ((linktoken = strtok_r(NULL, search, &last)) == NULL) {
+		if (namelen >= MAXLINKNAMELEN)
+			return (_B_FALSE);
+		if (link_name != NULL)
+			(void) strlcpy(link_name, name, MAXLINKNAMELEN);
+		return (_B_TRUE);
+	}
+
+	/* First token is the zonename. Check zone and link lengths */
+	if (strlen(zonetoken) >= ZONENAME_MAX || strlen(linktoken) >=
+	    MAXLINKNAMELEN)
+		return (_B_FALSE);
+	/*
+	 * If there are more '/' separated strings in the input
+	 * name  then we return failure. We only support a single
+	 * zone prefix or a devnet directory (f.e. net/bge0).
+	 */
+	if (strtok_r(NULL, search, &last) != NULL)
+		return (_B_FALSE);
+
+	if (link_name != NULL)
+		(void) strlcpy(link_name, linktoken, MAXLINKNAMELEN);
+	if (zoneidp != NULL) {
+		if ((*zoneidp = getzoneidbyname(zonetoken)) < MIN_ZONEID)
+			return (_B_FALSE);
+	}
+
+	return (_B_TRUE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/lib/util-solaris.h	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,107 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#ifndef	UTIL_SOLARIS_H
+#define	UTIL_SOLARIS_H
+
+#include <libdladm.h>
+#include <linux/openvswitch.h>
+#include <lib/packets.h>
+#include <sys/types.h>
+#include <kstat2.h>
+#include "odp-util.h"
+
+extern kstat2_handle_t	khandle;
+
+/*
+ * These functions are Solaris specific, so they should be used directly
+ * only by Solaris-specific code.
+ */
+
+struct action_output {
+	int nofports;
+	uint32_t ofports[MAC_OF_MAXPORT];
+};
+
+struct action_set {
+	uint_t	flags;
+	uint_t	priority;
+};
+
+#define	FLOW_SET_PRIORITY 	0x1
+#define	MAX_FLOW_ACTIONS	64
+
+struct flow;
+struct nlattr;
+
+int solaris_dladm_status2error(dladm_status_t);
+int solaris_if_enabled(int, const char *, uint64_t *);
+int solaris_init_rad(void);
+int solaris_get_dlclass(const char *, char *, size_t);
+int solaris_get_devname(const char *, char *, size_t);
+int solaris_get_dllower(const char *, char *, size_t);
+int solaris_get_dlprop(const char *, const char *, const char *, char *,
+    size_t);
+int solaris_set_dlprop_ulong(const char *, const char *, void *);
+int solaris_set_dlprop_boolean(const char *, const char *, void *);
+int solaris_set_dlprop_string(const char *, const char *, void *);
+int solaris_plumb_if(int, const char *, sa_family_t);
+int solaris_unplumb_if(int, const char *, sa_family_t);
+boolean_t dlparse_drvppa(const char *, char *, uint_t, uint_t *);
+
+int solaris_create_vnic(const char *, const char *);
+int solaris_delete_vnic(const char *);
+int solaris_modify_vnic(const char *, const char *);
+int solaris_create_etherstub(const char *);
+int solaris_delete_etherstub(const char *);
+boolean_t solaris_etherstub_exists(const char *);
+
+/* void * since struct dpif causes conflict with the list implementation */
+int solaris_add_flow(void *, const char *, const char *, struct flow *,
+    struct flow *, const struct nlattr *, size_t);
+int solaris_modify_flow(void *, const char *, const struct nlattr *, size_t);
+int solaris_remove_flow(const char *, const char *);
+int solaris_get_flowattr(const char *, struct flow *, struct flow *);
+int solaris_get_flowaction(const char *, struct ofpbuf *);
+void slowpath_to_actions(enum slow_path_reason, struct ofpbuf *);
+int solaris_get_flowstats(const char *, uint64_t *, uint64_t *, uint64_t *);
+
+boolean_t kstat_handle_init(kstat2_handle_t *);
+boolean_t kstat_handle_update(kstat2_handle_t);
+void kstat_handle_close(kstat2_handle_t *);
+uint64_t get_nvvt_int(kstat2_map_t, char *);
+
+void solaris_port_walk(void *, void (*)(void *, const char *, char *,
+    odp_port_t));
+uint64_t solaris_flow_walk(void *, struct ofpbuf *, boolean_t,
+    void (*)(void *, const char *, boolean_t, struct flow *, struct flow *,
+    struct ofpbuf *, uint64_t, uint64_t, uint64_t));
+
+boolean_t solaris_is_uplink_class(const char *);
+boolean_t solaris_dlparse_zonelinkname(const char *, char *, zoneid_t *);
+
+
+#define	SOLARIS_MAX_BUFSIZE	1024
+#endif	/* UTIL_SOLARIS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/ovs-clean.py	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,197 @@
+#!/usr/bin/python2.7
+#
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+
+#
+# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+#
+
+from rad.bindings.com.oracle.solaris.rad.dlmgr import *
+import rad.client as radcli
+import rad.connect as radcon
+
+def dlvalue_to_propval(adict):
+    pval = None
+    for key, value in adict.iteritems():
+        for k, v in vars(value).iteritems():
+            if k == 'type':
+                continue
+            if v is not None:
+                pval = v
+                break
+    return pval
+
+def printError(ex):
+    dlerr = ex.get_payload()
+    if dlerr != None:
+        print "Error: %s" % dlerr.errmsg
+    else:
+        print "Exception: %s" % ex
+
+def deleteVNIC(argv, numargs, dm):
+    if numargs !=  3:
+        print "usage: %s delete-vnic vnicname" % (argv[0])
+        return False
+
+    try:
+        dm.deleteVNIC(argv[2])
+    except radcli.ObjectError as ex:
+        printError(ex)
+        return False
+
+    return True
+
+def resetOFPorts(argv, numargs, rc):
+    if numargs != 2:
+        print "usage: %s reset-ofports" % (argv[0])
+        return False
+
+    # Enumerate all the datalinks and reset any ofports that are set
+    rval = True
+    datalink_name_list = rc.list_objects(Datalink())
+    for datalink_name in datalink_name_list:
+        datalink = rc.get_object(datalink_name)
+
+        try:
+            props = datalink.getProperties(["ofport", "temporary"], ["current"])
+        except radcli.ObjectError as ex:
+            dlerr = ex.get_payload()
+            # ignore if not a supported property
+	    if dlerr.err == DLSTATUS.INVALID_ARGUMENT._value:
+                continue
+            printError(ex)
+            rval = False
+            continue
+
+        prop = props[0]
+        pval =  dlvalue_to_propval(prop.map)
+        if pval == None or pval == 0:
+            continue
+        prop = props[1]
+        pval =  dlvalue_to_propval(prop.map)
+        try:
+            datalink.setProperties({
+                'temporary': DLValue(type=DLValueType.BOOLEAN, bval=pval),
+                'ofport': DLValue(type=DLValueType.ULONG, ulval=None)
+            })
+        except radcli.ObjectError as ex:
+            dlerr = ex.get_payload()
+            printError(ex)
+            rval = False
+
+    return rval
+
+def removeFlows(argv, numargs, rc):
+    if numargs != 2:
+        print "usage: %s remove-flows" % (argv[0])
+        return False
+
+    # Enumerate all the datalinks and look for OF links
+    rval = True
+    datalink_list = rc.list_objects(Datalink())
+    for datalink_name in datalink_list:
+        datalink = rc.get_object(datalink_name)
+        linkname = datalink_name._kvpairs["name"]
+
+	try:
+            props = datalink.getProperties(["openvswitch"], ["current"])
+        except radcli.ObjectError as ex:
+            dlerr = ex.get_payload()
+            # ignore if not a supported property
+	    if dlerr.err == DLSTATUS.INVALID_ARGUMENT._value:
+                continue
+            printError(ex)
+            rval = False
+            continue
+
+        prop = props[0]
+        pval =  dlvalue_to_propval(prop.map)
+        if pval == str("off"):
+            continue
+
+        # Enumerate all the flows for the datalink and remove them
+        flow_list = rc.list_objects(Flow(), ADRGlobPattern({'linkname': linkname}))
+        for flow_name in flow_list:
+            try:
+                datalink.removeFlow(flow_name._kvpairs["name"])
+            except radcli.ObjectError as ex:
+                dlerr = ex.get_payload()
+                # ignore if the flow is no longer found
+                if dlerr.err == DLSTATUS.NOT_FOUND._value:
+                    continue
+                printError(ex)
+                rval = False
+
+    return rval
+
+def deleteEtherstub(argv, numargs, rc, dm):
+    if numargs != 3:
+        print "usage: %s delete-etherstub etherstub-name" % (argv[0])
+        return False
+
+    name = argv[2]
+    try:
+        etherstub = rc.get_object(Etherstub(), ADRGlobPattern({'name': name}))
+        etherstub.setProperties({
+            'temporary': DLValue(type=DLValueType.BOOLEAN, bval="True"),
+            'openvswitch': DLValue(type=DLValueType.BOOLEAN, bval=None)
+            })
+        dm.deleteEtherstub(name)
+    except radcli.NotFoundError as ex:
+        pass
+    except radcli.ObjectError as ex:
+        dlerr = ex.get_payload()
+        printError(ex)
+        return False
+
+    return True
+
+def main():
+    numargs = len(sys.argv)
+    if numargs < 2:
+        print "usage: %s command [args]" % (sys.argv[0])
+        print "       %s delete-vnic vnic-name" % (sys.argv[0])
+        print "       %s reset-ofports" % (sys.argv[0])
+        print "       %s remove-flows" % (sys.argv[0])
+        print "       %s delete-etherstub etherstub-name" % (sys.argv[0])
+        sys.exit(1)
+
+    rc = radcon.connect_unix()
+    dm = rc.get_object(DatalinkManager())
+
+    if sys.argv[1] == "delete-vnic":
+        rval = deleteVNIC(sys.argv, numargs, dm)
+    elif sys.argv[1] == "reset-ofports":
+        rval = resetOFPorts(sys.argv, numargs, rc)
+    elif sys.argv[1] == "remove-flows":
+        rval = removeFlows(sys.argv, numargs, rc)
+    elif sys.argv[1] == "delete-etherstub":
+        rval = deleteEtherstub(sys.argv, numargs, rc, dm)
+    else:
+        print "\nInvalid ovs shutdown function"
+        rval = False;
+
+    sys.exit(rval == False)
+
+if __name__ == "__main__":
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/ovs-svc	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,201 @@
+#!/sbin/sh
+#
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+
+#
+# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+#
+
+. /lib/svc/share/smf_include.sh
+. /lib/svc/share/net_include.sh
+
+typeset -r OVS_OVSDB_FMRI=svc:/application/openvswitch/ovsdb-server:default
+typeset -r OVS_VSWITCHD_FMRI=svc:/application/openvswitch/vswitch-server:default
+
+typeset -r OVS_USRLIB_DIR=/usr/lib/ovs
+typeset -r OVS_VARLIB_DIR=/var/lib/ovs
+typeset -r OVS_TMP_DIR=/var/run/ovs
+typeset -r OVS_LOG_DIR=/var/log/ovs
+
+typeset -r OVS_USER=_ovs
+typeset -r OVS_GROUP=_ovs
+
+typeset -r OVS_SBIN_DIR=/usr/sbin
+typeset -r OVSDB_TOOL=${OVS_SBIN_DIR}/ovsdb-tool
+typeset -r OVS_VSCTL=${OVS_SBIN_DIR}/ovs-vsctl
+
+typeset -r OVS_VSWITCHD=ovs-vswitchd
+typeset -r OVSDB_SERVER=ovsdb-server
+typeset -r OVS_CLEAN=ovs-clean.py
+typeset -r OVSDB_SERVER_PATH=${OVS_USRLIB_DIR}/${OVSDB_SERVER}
+typeset -r OVS_VSWITCHD_PATH=${OVS_USRLIB_DIR}/${OVS_VSWITCHD}
+typeset -r OVS_CLEAN_PATH=${OVS_USRLIB_DIR}/${OVS_CLEAN}
+
+typeset -r OVSDB_REMOTE=${OVS_TMP_DIR}/db.sock
+
+typeset -r PFEXEC=/usr/bin/pfexec
+typeset -r MKDIR=/usr/bin/mkdir
+typeset -r CHOWN=/usr/bin/chown
+typeset -r PKILL=/usr/bin/pkill
+typeset -r DLADM=/usr/sbin/dladm
+typeset -r FLOWADM=/usr/sbin/flowadm
+
+errlog () {
+	echo $1 >&2
+}
+
+create_ovs_tempdir() {
+	if [[ ! -d ${OVS_TMP_DIR} ]]; then
+		${PFEXEC} ${MKDIR} -m 775 ${OVS_TMP_DIR} || exit $SMF_EXIT_ERR_CONFIG
+                ${PFEXEC} ${CHOWN} ${OVS_USER}:${OVS_GROUP} ${OVS_TMP_DIR}
+	fi
+}
+
+start_ovsdb_server() {
+	typeset -i OVSDB_INIT=0
+	typeset -r OVSDB_DATABASE=${OVS_VARLIB_DIR}/etc/conf.db
+
+	create_ovs_tempdir
+
+	if [[ ! -f ${OVSDB_DATABASE} ]]; then
+		typeset -r OVSDB_SCHEMA=${OVS_USRLIB_DIR}/share/vswitch.ovsschema
+
+		echo "Creating ${OVSDB_DATABASE} from ${OVSDB_SCHEMA}"
+		${OVSDB_TOOL} create ${OVSDB_DATABASE} ${OVSDB_SCHEMA}
+		if [ $? -ne 0 ]; then
+			errlog "Error creating database, exiting"
+			return 1
+		fi
+		OVSDB_INIT=1
+	fi
+
+	typeset -r OVSDB_LOGFILE=${OVS_LOG_DIR}/ovsdb-server.log
+	typeset -r OVSDB_PIDFILE=${OVS_TMP_DIR}/ovsdb-server.pid
+
+	${OVSDB_SERVER_PATH} ${OVSDB_DATABASE} \
+	    -vconsole:emer -vsyslog:err -vfile:info \
+	    --remote=punix:${OVSDB_REMOTE} \
+	    --remote=db:Open_vSwitch,Open_vSwitch,manager_options \
+	    --private-key=db:Open_vSwitch,SSL,private_key \
+	    --certificate=db:Open_vSwitch,SSL,certificate \
+	    --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert \
+	    --no-chdir --log-file=${OVSDB_LOGFILE} \
+	    --pidfile=${OVSDB_PIDFILE} \
+	    --detach
+	if [ $? -ne 0 ]; then
+		errlog "${OVSDB_SERVER_PATH} failed with $?"
+		exit $SMF_EXIT_ERR_FATAL
+	fi
+
+	if [ ${OVSDB_INIT} -ne 0 ]; then
+		echo "Initializing OVSDB database"
+		${OVS_VSCTL} --no-wait init
+		if [ $? -ne 0 ]; then
+			errlog "${OVSDB_VSCTL} failed with $?"
+			exit $SMF_EXIT_ERR_FATAL
+		fi
+	fi
+}
+
+stop_vswitch_server() {
+        typeset BRIDGENAME
+
+        $PKILL -x -u ${OVS_USER} -z `smf_zonename` ${OVS_VSWITCHD}
+        if [[ $? -ne 0 && $? -ne 1 ]]; then
+                errlog "pkill of ${OVS_VSWITCHD} failed with $?"
+        fi
+
+        ${OVS_VSCTL} list-br |
+                while read BRIDGENAME; do
+                        ${OVS_CLEAN_PATH} delete-vnic ${BRIDGENAME}
+                        if [ $? -ne 0 ]; then
+                                errlog "Error $? removing ${BRIDGENAME} VNIC"
+                        fi
+                done
+        ${OVS_CLEAN_PATH} reset-ofports
+	if [ $? -ne 0 ]; then
+        	errlog "Error $? resetting OF ports"
+	fi
+
+        ${OVS_CLEAN_PATH} remove-flows
+        if [ $? -ne 0 ]; then
+                errlog "Error $? removing OF flows"
+        fi
+
+        ${OVS_CLEAN_PATH} delete-etherstub ovs.etherstub0
+        if [ $? -ne 0 ]; then
+                errlog "Error $? deleting the OVS etherstub"
+        fi
+
+}
+
+start_vswitch_server() {
+	typeset -r VSWITCHD_LOGFILE=${OVS_LOG_DIR}/ovs-vswitchd.log
+	typeset -r VSWITCHD_PIDFILE=${OVS_TMP_DIR}/ovs-vswitchd.pid
+
+	create_ovs_tempdir
+
+	${OVS_VSWITCHD_PATH} unix:${OVSDB_REMOTE} \
+	    -vconsole:emer -vsyslog:err -vfile:info --mlockall --no-chdir \
+	    --log-file=${VSWITCHD_LOGFILE} \
+	    --pidfile=${VSWITCHD_PIDFILE} \
+	    --detach
+	if [ $? -ne 0 ]; then
+		errlog "${OVS_VSWITCHD} failed with $?"
+		exit $SMF_EXIT_ERR_FATAL
+	fi
+}
+
+case "$1" in
+'start')
+	case "$SMF_FMRI" in
+	"${OVS_OVSDB_FMRI}")
+		start_ovsdb_server
+		;;
+	"${OVS_VSWITCHD_FMRI}")
+		start_vswitch_server
+		;;
+	*)
+		echo "$SMF_FMRI does not support start method"
+		exit $SMF_EXIT_ERR_FATAL
+		;;
+	esac
+	;;
+'stop')
+	case "$SMF_FMRI" in
+	"${OVS_VSWITCHD_FMRI}")
+		stop_vswitch_server
+		;;
+	*)
+		echo "$SMF_FMRI does not support stop method"
+		exit $SMF_EXIT_ERR_FATAL
+		;;
+	esac 
+	;;
+*)
+	echo "Service must be invoked from within SMF"
+	exit $SMF_EXIT_ERR_FATAL
+	;;
+esac
+
+exit $SMF_EXIT_OK
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/ovs.auth_attr	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,3 @@
+solaris.smf.manage.ovs:RO::Manage Open Virtual Switch States::
+solaris.smf.value.ovs:RO::Change Values of Open Virtual Switch Properties::
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/ovs.exec_attr	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,21 @@
+ovs-agent:solaris:cmd:RO::/usr/lib/rad/module/mod_dlmgr.so.1:\
+privs=sys_dl_config
+
+ovs-agent:solaris:cmd:::/usr/bin/mkdir:privs={zone}\:/system/volatile/ovs
+ovs-agent:solaris:cmd:::/usr/bin/chown:privs={zone}\:/system/volatile/ovs
+
+OVS Administration:solaris:cmd:RO::/usr/sbin/ovs-ofctl:\
+privs={file_dac_write}\:/system/volatile/ovs/*,\
+{file_dac_read}\:/system/volatile/ovs/*
+
+OVS Administration:solaris:cmd:RO::/usr/sbin/ovs-vsctl:\
+privs={file_dac_write}\:/system/volatile/ovs/*,\
+{file_dac_read}\:/system/volatile/ovs/*
+
+OVS Administration:solaris:cmd:RO::/usr/sbin/ovs-appctl:\
+privs={file_dac_write}\:/system/volatile/ovs/*,\
+{file_dac_read}\:/system/volatile/ovs/*
+
+OVS Administration:solaris:cmd:RO::/usr/sbin/ovsdb-client:\
+privs={file_dac_write}\:/system/volatile/ovs/*,\
+{file_dac_read}\:/system/volatile/ovs/*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/ovs.prof_attr	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,10 @@
+OVS Administration:RO::\
+Administer Privileged OVS Operations:\
+auths=solaris.smf.manage.ovs,solaris.smf.value.ovs;\
+defaultpriv={file_dac_search}\:/var/log/ovs,\
+{file_dac_read}\:/var/log/ovs/*
+
+ovs-agent:RO::\
+Do not assign to users. \
+Authorizations required by vswitch-server:\
+auths=solaris.network.interface.config,solaris.smf.modify
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/ovs.user_attr	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,1 @@
+_ovs::RO::profiles=ovs-agent
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/ovsdb.xml	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
+<!--
+CDDL HEADER START
+
+The contents of this file are subject to the terms of the
+Common Development and Distribution License (the "License").
+You may not use this file except in compliance with the License.
+
+You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+or http://www.opensolaris.org/os/licensing.
+See the License for the specific language governing permissions
+and limitations under the License.
+
+When distributing Covered Code, include this CDDL HEADER in each
+file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+If applicable, add the following below this CDDL HEADER, with the
+fields enclosed by brackets "[]" replaced with your own identifying
+information: Portions Copyright [yyyy] [name of copyright owner]
+
+CDDL HEADER END
+
+ Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+
+ NOTE:  This service manifest is not editable; its contents will
+ be overwritten by package or patch operations, including
+ operating system upgrade.  Make customizations in a different
+ file.
+-->
+
+<service_bundle type="manifest" name="ovsdb">
+
+    <service name="application/openvswitch/ovsdb-server" type="service" version="1">
+        <dependency
+            name='multiuser'
+            grouping='require_all'
+            restart_on='error'
+            type='service'>
+            <service_fmri value='svc:/milestone/multi-user:default'/>
+        </dependency>
+
+        <instance name='default' enabled='true'>
+            <exec_method
+                type="method"
+                name="start"
+                timeout_seconds="60" 
+                exec="/lib/svc/method/ovs-svc %m">
+
+                <method_context>
+                    <method_credential user='_ovs' group='_ovs'/>
+                </method_context>
+            </exec_method>
+
+            <exec_method
+                type="method" 
+                name="stop"
+                timeout_seconds="60"
+                exec=":kill"/>
+
+            <!-- to start/stop/refresh the service -->
+            <property_group name='general' type='framework'>
+                <propval
+                    name='action_authorization'
+                    type='astring'
+                    value='solaris.smf.manage.ovs'/>
+
+                <propval
+                    name='value_authorization'
+                    type='astring'
+                    value='solaris.smf.value.ovs'/>
+            </property_group>
+
+            <template>
+                <common_name>
+                    <loctext xml:lang="C">
+                        Open Virtual Switch Database Server
+                    </loctext>
+                </common_name>
+                <description>
+                    <loctext xml:lang="C">
+                        Server to manage Open Virtual Switch Database
+                    </loctext>
+                </description>
+                <documentation>
+		    <external_logfile path='/var/log/ovs/ovsdb-server.log' />
+                </documentation>
+            </template>
+        </instance>
+    </service>
+</service_bundle>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/files/vswitch.xml	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
+<!--
+CDDL HEADER START
+
+The contents of this file are subject to the terms of the
+Common Development and Distribution License (the "License").
+You may not use this file except in compliance with the License.
+
+You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+or http://www.opensolaris.org/os/licensing.
+See the License for the specific language governing permissions
+and limitations under the License.
+
+When distributing Covered Code, include this CDDL HEADER in each
+file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+If applicable, add the following below this CDDL HEADER, with the
+fields enclosed by brackets "[]" replaced with your own identifying
+information: Portions Copyright [yyyy] [name of copyright owner]
+
+CDDL HEADER END
+
+ Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+
+ NOTE:  This service manifest is not editable; its contents will
+ be overwritten by package or patch operations, including
+ operating system upgrade.  Make customizations in a different
+ file.
+-->
+
+<service_bundle type="manifest" name="vswitch">
+
+    <service name="application/openvswitch/vswitch-server" type="service" version="1">
+        <dependency
+            name='multiuser'
+            grouping='require_all'
+            restart_on='error'
+            type='service'>
+            <service_fmri value='svc:/milestone/multi-user:default'/>
+        </dependency>
+
+        <dependency
+            name='ovsdb-server'
+            grouping='require_all'
+            restart_on='none'
+            type='service'>
+            <service_fmri
+                value='svc:/application/openvswitch/ovsdb-server:default'/>
+        </dependency>
+
+        <instance name='default' enabled='true'>
+            <exec_method
+                type="method"
+                name="start"
+                exec="/lib/svc/method/ovs-svc %m"
+                timeout_seconds="60">
+
+                <method_context>
+                    <method_credential
+                        user='_ovs'
+                        group='_ovs'
+                        privileges='basic,net_rawaccess,proc_lock_memory'/>
+                </method_context>
+            </exec_method>
+
+            <exec_method
+                type="method" 
+                name="stop"
+                timeout_seconds="60"
+                exec="/lib/svc/method/ovs-svc %m">
+            </exec_method>
+
+            <!-- to start/stop/refresh the service -->
+            <property_group name='general' type='framework'>
+                <propval
+                    name='action_authorization'
+                    type='astring'
+                    value='solaris.smf.manage.ovs'/>
+
+                <propval
+                    name='value_authorization'
+                    type='astring'
+                    value='solaris.smf.value.ovs'/>
+            </property_group>
+
+            <template>
+                <common_name>
+                    <loctext xml:lang="C">
+                        Open Virtual Switch Daemon
+                    </loctext>
+                </common_name>
+                <description>
+                    <loctext xml:lang="C">
+                        Daemon that manages Open Virtual Switch switches
+                    </loctext>
+                </description>
+                <documentation>
+		    <external_logfile path='/var/log/ovs/ovs-vswitchd.log' />
+                </documentation>
+            </template>
+        </instance>
+    </service>
+</service_bundle>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openvswitch/openvswitch.license	Mon Nov 16 16:49:19 2015 -0500
@@ -0,0 +1,211 @@
+The following applies to all products licensed under the Apache 2.0 License:
+
+You may not use the identified files except in compliance with the Apache License, Version 2.0 (the "License.")
+ 
+You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.  A copy of the license is also reproduced below.
+
+Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+See the License for the specific language governing permissions and limitations under the License.
+
+		                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.