--- /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 */
+};