+/* Stores the features supported by 'netdev' into each of '*current',
+ * '*advertised', '*supported', and '*peer' that are non-null. Each value is a
+ * bitmap of "enum ofp_port_features" bits, in host byte order. Returns 0 if
+ * successful, otherwise a positive errno value. */
+static int
+netdev_linux_get_features(const struct netdev *netdev,
+ uint32_t *current, uint32_t *advertised,
+ uint32_t *supported, uint32_t *peer)
+{
+ struct ethtool_cmd ecmd;
+ int error;
+
+ memset(&ecmd, 0, sizeof ecmd);
+ error = netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
+ ETHTOOL_GSET, "ETHTOOL_GSET");
+ if (error) {
+ return error;
+ }
+
+ /* Supported features. */
+ *supported = 0;
+ if (ecmd.supported & SUPPORTED_10baseT_Half) {
+ *supported |= OFPPF_10MB_HD;
+ }
+ if (ecmd.supported & SUPPORTED_10baseT_Full) {
+ *supported |= OFPPF_10MB_FD;
+ }
+ if (ecmd.supported & SUPPORTED_100baseT_Half) {
+ *supported |= OFPPF_100MB_HD;
+ }
+ if (ecmd.supported & SUPPORTED_100baseT_Full) {
+ *supported |= OFPPF_100MB_FD;
+ }
+ if (ecmd.supported & SUPPORTED_1000baseT_Half) {
+ *supported |= OFPPF_1GB_HD;
+ }
+ if (ecmd.supported & SUPPORTED_1000baseT_Full) {
+ *supported |= OFPPF_1GB_FD;
+ }
+ if (ecmd.supported & SUPPORTED_10000baseT_Full) {
+ *supported |= OFPPF_10GB_FD;
+ }
+ if (ecmd.supported & SUPPORTED_TP) {
+ *supported |= OFPPF_COPPER;
+ }
+ if (ecmd.supported & SUPPORTED_FIBRE) {
+ *supported |= OFPPF_FIBER;
+ }
+ if (ecmd.supported & SUPPORTED_Autoneg) {
+ *supported |= OFPPF_AUTONEG;
+ }
+ if (ecmd.supported & SUPPORTED_Pause) {
+ *supported |= OFPPF_PAUSE;
+ }
+ if (ecmd.supported & SUPPORTED_Asym_Pause) {
+ *supported |= OFPPF_PAUSE_ASYM;
+ }
+
+ /* Advertised features. */
+ *advertised = 0;
+ if (ecmd.advertising & ADVERTISED_10baseT_Half) {
+ *advertised |= OFPPF_10MB_HD;
+ }
+ if (ecmd.advertising & ADVERTISED_10baseT_Full) {
+ *advertised |= OFPPF_10MB_FD;
+ }
+ if (ecmd.advertising & ADVERTISED_100baseT_Half) {
+ *advertised |= OFPPF_100MB_HD;
+ }
+ if (ecmd.advertising & ADVERTISED_100baseT_Full) {
+ *advertised |= OFPPF_100MB_FD;
+ }
+ if (ecmd.advertising & ADVERTISED_1000baseT_Half) {
+ *advertised |= OFPPF_1GB_HD;
+ }
+ if (ecmd.advertising & ADVERTISED_1000baseT_Full) {
+ *advertised |= OFPPF_1GB_FD;
+ }
+ if (ecmd.advertising & ADVERTISED_10000baseT_Full) {
+ *advertised |= OFPPF_10GB_FD;
+ }
+ if (ecmd.advertising & ADVERTISED_TP) {
+ *advertised |= OFPPF_COPPER;
+ }
+ if (ecmd.advertising & ADVERTISED_FIBRE) {
+ *advertised |= OFPPF_FIBER;
+ }
+ if (ecmd.advertising & ADVERTISED_Autoneg) {
+ *advertised |= OFPPF_AUTONEG;
+ }
+ if (ecmd.advertising & ADVERTISED_Pause) {
+ *advertised |= OFPPF_PAUSE;
+ }
+ if (ecmd.advertising & ADVERTISED_Asym_Pause) {
+ *advertised |= OFPPF_PAUSE_ASYM;
+ }
+
+ /* Current settings. */
+ if (ecmd.speed == SPEED_10) {
+ *current = ecmd.duplex ? OFPPF_10MB_FD : OFPPF_10MB_HD;
+ } else if (ecmd.speed == SPEED_100) {
+ *current = ecmd.duplex ? OFPPF_100MB_FD : OFPPF_100MB_HD;
+ } else if (ecmd.speed == SPEED_1000) {
+ *current = ecmd.duplex ? OFPPF_1GB_FD : OFPPF_1GB_HD;
+ } else if (ecmd.speed == SPEED_10000) {
+ *current = OFPPF_10GB_FD;
+ } else {
+ *current = 0;
+ }
+
+ if (ecmd.port == PORT_TP) {
+ *current |= OFPPF_COPPER;
+ } else if (ecmd.port == PORT_FIBRE) {
+ *current |= OFPPF_FIBER;
+ }
+
+ if (ecmd.autoneg) {
+ *current |= OFPPF_AUTONEG;
+ }
+
+ /* Peer advertisements. */
+ *peer = 0; /* XXX */
+
+ return 0;
+}
+
+/* Set the features advertised by 'netdev' to 'advertise'. */
+static int
+netdev_linux_set_advertisements(struct netdev *netdev, uint32_t advertise)
+{
+ struct ethtool_cmd ecmd;
+ int error;
+
+ memset(&ecmd, 0, sizeof ecmd);
+ error = netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
+ ETHTOOL_GSET, "ETHTOOL_GSET");
+ if (error) {
+ return error;
+ }
+
+ ecmd.advertising = 0;
+ if (advertise & OFPPF_10MB_HD) {
+ ecmd.advertising |= ADVERTISED_10baseT_Half;
+ }
+ if (advertise & OFPPF_10MB_FD) {
+ ecmd.advertising |= ADVERTISED_10baseT_Full;
+ }
+ if (advertise & OFPPF_100MB_HD) {
+ ecmd.advertising |= ADVERTISED_100baseT_Half;
+ }
+ if (advertise & OFPPF_100MB_FD) {
+ ecmd.advertising |= ADVERTISED_100baseT_Full;
+ }
+ if (advertise & OFPPF_1GB_HD) {
+ ecmd.advertising |= ADVERTISED_1000baseT_Half;
+ }
+ if (advertise & OFPPF_1GB_FD) {
+ ecmd.advertising |= ADVERTISED_1000baseT_Full;
+ }
+ if (advertise & OFPPF_10GB_FD) {
+ ecmd.advertising |= ADVERTISED_10000baseT_Full;
+ }
+ if (advertise & OFPPF_COPPER) {
+ ecmd.advertising |= ADVERTISED_TP;
+ }
+ if (advertise & OFPPF_FIBER) {
+ ecmd.advertising |= ADVERTISED_FIBRE;
+ }
+ if (advertise & OFPPF_AUTONEG) {
+ ecmd.advertising |= ADVERTISED_Autoneg;
+ }
+ if (advertise & OFPPF_PAUSE) {
+ ecmd.advertising |= ADVERTISED_Pause;
+ }
+ if (advertise & OFPPF_PAUSE_ASYM) {
+ ecmd.advertising |= ADVERTISED_Asym_Pause;
+ }
+ return netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
+ ETHTOOL_SSET, "ETHTOOL_SSET");
+}
+
+/* If 'netdev_name' is the name of a VLAN network device (e.g. one created with
+ * vconfig(8)), sets '*vlan_vid' to the VLAN VID associated with that device
+ * and returns 0. Otherwise returns a errno value (specifically ENOENT if
+ * 'netdev_name' is the name of a network device that is not a VLAN device) and
+ * sets '*vlan_vid' to -1. */
+static int
+netdev_linux_get_vlan_vid(const struct netdev *netdev, int *vlan_vid)
+{
+ const char *netdev_name = netdev_get_name(netdev);
+ struct ds line = DS_EMPTY_INITIALIZER;
+ FILE *stream = NULL;
+ int error;
+ char *fn;
+
+ COVERAGE_INC(netdev_get_vlan_vid);
+ fn = xasprintf("/proc/net/vlan/%s", netdev_name);
+ stream = fopen(fn, "r");
+ if (!stream) {
+ error = errno;
+ goto done;
+ }
+
+ if (ds_get_line(&line, stream)) {
+ if (ferror(stream)) {
+ error = errno;
+ VLOG_ERR_RL(&rl, "error reading \"%s\": %s", fn, strerror(errno));
+ } else {
+ error = EPROTO;
+ VLOG_ERR_RL(&rl, "unexpected end of file reading \"%s\"", fn);
+ }
+ goto done;
+ }
+
+ if (!sscanf(ds_cstr(&line), "%*s VID: %d", vlan_vid)) {
+ error = EPROTO;
+ VLOG_ERR_RL(&rl, "parse error reading \"%s\" line 1: \"%s\"",
+ fn, ds_cstr(&line));
+ goto done;
+ }
+
+ error = 0;
+
+done:
+ free(fn);
+ if (stream) {
+ fclose(stream);
+ }
+ ds_destroy(&line);
+ if (error) {
+ *vlan_vid = -1;
+ }
+ return error;
+}
+
+#define POLICE_ADD_CMD "/sbin/tc qdisc add dev %s handle ffff: ingress"
+#define POLICE_CONFIG_CMD "/sbin/tc filter add dev %s parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate %dkbit burst %dk mtu 65535 drop flowid :1"
+
+/* Remove ingress policing from 'netdev'. Returns 0 if successful, otherwise a
+ * positive errno value.
+ *
+ * This function is equivalent to running
+ * /sbin/tc qdisc del dev %s handle ffff: ingress
+ * but it is much, much faster.
+ */
+static int
+netdev_linux_remove_policing(struct netdev *netdev)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ const char *netdev_name = netdev_get_name(netdev);
+
+ struct ofpbuf request;
+ struct tcmsg *tcmsg;
+ int error;
+
+ tcmsg = tc_make_request(netdev, RTM_DELQDISC, 0, &request);
+ if (!tcmsg) {
+ return ENODEV;
+ }
+ tcmsg->tcm_handle = tc_make_handle(0xffff, 0);
+ tcmsg->tcm_parent = TC_H_INGRESS;
+ nl_msg_put_string(&request, TCA_KIND, "ingress");
+ nl_msg_put_unspec(&request, TCA_OPTIONS, NULL, 0);
+
+ error = tc_transact(&request, NULL);
+ if (error && error != ENOENT && error != EINVAL) {
+ VLOG_WARN_RL(&rl, "%s: removing policing failed: %s",
+ netdev_name, strerror(error));
+ return error;
+ }
+
+ netdev_dev->kbits_rate = 0;
+ netdev_dev->kbits_burst = 0;
+ netdev_dev->cache_valid |= VALID_POLICING;
+ return 0;
+}
+
+/* Attempts to set input rate limiting (policing) policy. */
+static int
+netdev_linux_set_policing(struct netdev *netdev,
+ uint32_t kbits_rate, uint32_t kbits_burst)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ const char *netdev_name = netdev_get_name(netdev);
+ char command[1024];
+
+ COVERAGE_INC(netdev_set_policing);
+
+ kbits_burst = (!kbits_rate ? 0 /* Force to 0 if no rate specified. */
+ : !kbits_burst ? 1000 /* Default to 1000 kbits if 0. */
+ : kbits_burst); /* Stick with user-specified value. */
+
+ if (netdev_dev->cache_valid & VALID_POLICING
+ && netdev_dev->kbits_rate == kbits_rate
+ && netdev_dev->kbits_burst == kbits_burst) {
+ /* Assume that settings haven't changed since we last set them. */
+ return 0;
+ }
+
+ netdev_linux_remove_policing(netdev);
+ if (kbits_rate) {
+ snprintf(command, sizeof(command), POLICE_ADD_CMD, netdev_name);
+ if (system(command) != 0) {
+ VLOG_WARN_RL(&rl, "%s: problem adding policing", netdev_name);
+ return -1;
+ }
+
+ snprintf(command, sizeof(command), POLICE_CONFIG_CMD, netdev_name,
+ kbits_rate, kbits_burst);
+ if (system(command) != 0) {
+ VLOG_WARN_RL(&rl, "%s: problem configuring policing",
+ netdev_name);
+ return -1;
+ }
+
+ netdev_dev->kbits_rate = kbits_rate;
+ netdev_dev->kbits_burst = kbits_burst;
+ netdev_dev->cache_valid |= VALID_POLICING;
+ }
+
+ return 0;
+}
+
+static int
+netdev_linux_get_qos_types(const struct netdev *netdev OVS_UNUSED,
+ struct sset *types)
+{
+ const struct tc_ops **opsp;
+
+ 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 const struct tc_ops *
+tc_lookup_ovs_name(const char *name)
+{
+ const struct tc_ops **opsp;
+
+ for (opsp = tcs; *opsp != NULL; opsp++) {
+ const struct tc_ops *ops = *opsp;
+ if (!strcmp(name, ops->ovs_name)) {
+ return ops;
+ }
+ }
+ return NULL;
+}
+
+static const struct tc_ops *
+tc_lookup_linux_name(const char *name)
+{
+ const struct tc_ops **opsp;
+
+ for (opsp = tcs; *opsp != NULL; opsp++) {
+ const struct tc_ops *ops = *opsp;
+ if (ops->linux_name && !strcmp(name, ops->linux_name)) {
+ return ops;
+ }
+ }
+ return NULL;
+}
+
+static struct tc_queue *
+tc_find_queue__(const struct netdev *netdev, unsigned int queue_id,
+ size_t hash)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ struct tc_queue *queue;
+
+ HMAP_FOR_EACH_IN_BUCKET (queue, hmap_node, hash, &netdev_dev->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
+netdev_linux_get_qos_capabilities(const struct netdev *netdev OVS_UNUSED,
+ const char *type,
+ struct netdev_qos_capabilities *caps)
+{
+ const struct tc_ops *ops = tc_lookup_ovs_name(type);
+ if (!ops) {
+ return EOPNOTSUPP;
+ }
+ caps->n_queues = ops->n_queues;
+ return 0;
+}
+
+static int
+netdev_linux_get_qos(const struct netdev *netdev,
+ const char **typep, struct shash *details)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ }
+
+ *typep = netdev_dev->tc->ops->ovs_name;
+ return (netdev_dev->tc->ops->qdisc_get
+ ? netdev_dev->tc->ops->qdisc_get(netdev, details)
+ : 0);
+}
+
+static int
+netdev_linux_set_qos(struct netdev *netdev,
+ const char *type, const struct shash *details)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ const struct tc_ops *new_ops;
+ int error;
+
+ new_ops = tc_lookup_ovs_name(type);
+ if (!new_ops || !new_ops->tc_install) {
+ return EOPNOTSUPP;
+ }
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ }
+
+ if (new_ops == netdev_dev->tc->ops) {
+ return new_ops->qdisc_set ? new_ops->qdisc_set(netdev, details) : 0;
+ } else {
+ /* Delete existing qdisc. */
+ error = tc_del_qdisc(netdev);
+ if (error) {
+ return error;
+ }
+ assert(netdev_dev->tc == NULL);
+
+ /* Install new qdisc. */
+ error = new_ops->tc_install(netdev, details);
+ assert((error == 0) == (netdev_dev->tc != NULL));
+
+ return error;
+ }
+}
+
+static int
+netdev_linux_get_queue(const struct netdev *netdev,
+ unsigned int queue_id, struct shash *details)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else {
+ struct tc_queue *queue = tc_find_queue(netdev, queue_id);
+ return (queue
+ ? netdev_dev->tc->ops->class_get(netdev, queue, details)
+ : ENOENT);
+ }
+}
+
+static int
+netdev_linux_set_queue(struct netdev *netdev,
+ unsigned int queue_id, const struct shash *details)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else if (queue_id >= netdev_dev->tc->ops->n_queues
+ || !netdev_dev->tc->ops->class_set) {
+ return EINVAL;
+ }
+
+ return netdev_dev->tc->ops->class_set(netdev, queue_id, details);
+}
+
+static int
+netdev_linux_delete_queue(struct netdev *netdev, unsigned int queue_id)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else if (!netdev_dev->tc->ops->class_delete) {
+ return EINVAL;
+ } else {
+ struct tc_queue *queue = tc_find_queue(netdev, queue_id);
+ return (queue
+ ? netdev_dev->tc->ops->class_delete(netdev, queue)
+ : ENOENT);
+ }
+}
+
+static int
+netdev_linux_get_queue_stats(const struct netdev *netdev,
+ unsigned int queue_id,
+ struct netdev_queue_stats *stats)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else if (!netdev_dev->tc->ops->class_get_stats) {
+ return EOPNOTSUPP;
+ } else {
+ const struct tc_queue *queue = tc_find_queue(netdev, queue_id);
+ return (queue
+ ? netdev_dev->tc->ops->class_get_stats(netdev, queue, stats)
+ : ENOENT);
+ }
+}
+
+static bool
+start_queue_dump(const struct netdev *netdev, struct nl_dump *dump)
+{
+ struct ofpbuf request;
+ struct tcmsg *tcmsg;
+
+ tcmsg = tc_make_request(netdev, RTM_GETTCLASS, 0, &request);
+ if (!tcmsg) {
+ return false;