+ /* If this port is an internal port then the transmit and receive stats
+ * will appear to be swapped relative to the other ports since we are the
+ * one sending the data, not a remote computer. For consistency, we swap
+ * them back here. This does not apply if we are getting stats from the
+ * vport layer because it always tracks stats from the perspective of the
+ * switch. */
+ if (!netdev_dev->have_vport_stats) {
+ *stats = dev_stats;
+ swap_uint64(&stats->rx_packets, &stats->tx_packets);
+ swap_uint64(&stats->rx_bytes, &stats->tx_bytes);
+ swap_uint64(&stats->rx_errors, &stats->tx_errors);
+ swap_uint64(&stats->rx_dropped, &stats->tx_dropped);
+ stats->rx_length_errors = 0;
+ stats->rx_over_errors = 0;
+ stats->rx_crc_errors = 0;
+ stats->rx_frame_errors = 0;
+ stats->rx_fifo_errors = 0;
+ stats->rx_missed_errors = 0;
+ stats->tx_aborted_errors = 0;
+ stats->tx_carrier_errors = 0;
+ stats->tx_fifo_errors = 0;
+ stats->tx_heartbeat_errors = 0;
+ stats->tx_window_errors = 0;
+ } else {
+ stats->rx_dropped += dev_stats.tx_dropped;
+ stats->tx_dropped += dev_stats.rx_dropped;
+
+ stats->rx_errors += dev_stats.tx_errors;
+ stats->tx_errors += dev_stats.rx_errors;
+
+ stats->multicast += dev_stats.multicast;
+ stats->collisions += dev_stats.collisions;
+ }
+ return 0;
+}
+
+/* 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;
+ }
+ tcmsg->tcm_parent = 0;
+ nl_dump_start(dump, rtnl_sock, &request);
+ ofpbuf_uninit(&request);
+ return true;
+}
+
+static int
+netdev_linux_dump_queues(const struct netdev *netdev,
+ netdev_dump_queues_cb *cb, void *aux)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ struct tc_queue *queue;
+ struct shash details;
+ int last_error;
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else if (!netdev_dev->tc->ops->class_get) {
+ return EOPNOTSUPP;
+ }
+
+ last_error = 0;
+ shash_init(&details);
+ HMAP_FOR_EACH (queue, hmap_node, &netdev_dev->tc->queues) {
+ shash_clear(&details);
+
+ error = netdev_dev->tc->ops->class_get(netdev, queue, &details);
+ if (!error) {
+ (*cb)(queue->queue_id, &details, aux);
+ } else {
+ last_error = error;
+ }
+ }
+ shash_destroy(&details);
+
+ return last_error;
+}
+
+static int
+netdev_linux_dump_queue_stats(const struct netdev *netdev,
+ netdev_dump_queue_stats_cb *cb, void *aux)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ struct nl_dump dump;
+ struct ofpbuf msg;
+ int last_error;
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else if (!netdev_dev->tc->ops->class_dump_stats) {
+ return EOPNOTSUPP;
+ }
+
+ last_error = 0;
+ if (!start_queue_dump(netdev, &dump)) {
+ return ENODEV;
+ }
+ while (nl_dump_next(&dump, &msg)) {
+ error = netdev_dev->tc->ops->class_dump_stats(netdev, &msg, cb, aux);
+ if (error) {
+ last_error = error;
+ }
+ }
+
+ error = nl_dump_done(&dump);
+ return error ? error : last_error;
+}
+
+static int
+netdev_linux_get_in4(const struct netdev *netdev_,
+ struct in_addr *address, struct in_addr *netmask)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev_));
+
+ if (!(netdev_dev->cache_valid & VALID_IN4)) {
+ int error;
+
+ error = netdev_linux_get_ipv4(netdev_, &netdev_dev->address,
+ SIOCGIFADDR, "SIOCGIFADDR");
+ if (error) {
+ return error;
+ }
+
+ error = netdev_linux_get_ipv4(netdev_, &netdev_dev->netmask,
+ SIOCGIFNETMASK, "SIOCGIFNETMASK");
+ if (error) {
+ return error;
+ }
+
+ netdev_dev->cache_valid |= VALID_IN4;
+ }
+ *address = netdev_dev->address;
+ *netmask = netdev_dev->netmask;
+ return address->s_addr == INADDR_ANY ? EADDRNOTAVAIL : 0;
+}
+
+static int
+netdev_linux_set_in4(struct netdev *netdev_, struct in_addr address,
+ struct in_addr netmask)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev_));
+ int error;
+
+ error = do_set_addr(netdev_, SIOCSIFADDR, "SIOCSIFADDR", address);
+ if (!error) {
+ netdev_dev->cache_valid |= VALID_IN4;
+ netdev_dev->address = address;
+ netdev_dev->netmask = netmask;
+ if (address.s_addr != INADDR_ANY) {
+ error = do_set_addr(netdev_, SIOCSIFNETMASK,
+ "SIOCSIFNETMASK", netmask);
+ }
+ }
+ return error;
+}
+
+static bool
+parse_if_inet6_line(const char *line,
+ struct in6_addr *in6, char ifname[16 + 1])
+{
+ uint8_t *s6 = in6->s6_addr;
+#define X8 "%2"SCNx8
+ return sscanf(line,
+ " "X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8 X8
+ "%*x %*x %*x %*x %16s\n",
+ &s6[0], &s6[1], &s6[2], &s6[3],
+ &s6[4], &s6[5], &s6[6], &s6[7],
+ &s6[8], &s6[9], &s6[10], &s6[11],
+ &s6[12], &s6[13], &s6[14], &s6[15],
+ ifname) == 17;
+}
+
+/* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address (if
+ * 'in6' is non-null) and returns true. Otherwise, returns false. */
+static int
+netdev_linux_get_in6(const struct netdev *netdev_, struct in6_addr *in6)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev_));
+ if (!(netdev_dev->cache_valid & VALID_IN6)) {
+ FILE *file;
+ char line[128];
+
+ netdev_dev->in6 = in6addr_any;
+
+ file = fopen("/proc/net/if_inet6", "r");
+ if (file != NULL) {
+ const char *name = netdev_get_name(netdev_);
+ while (fgets(line, sizeof line, file)) {
+ struct in6_addr in6_tmp;
+ char ifname[16 + 1];
+ if (parse_if_inet6_line(line, &in6_tmp, ifname)
+ && !strcmp(name, ifname))
+ {
+ netdev_dev->in6 = in6_tmp;
+ break;
+ }
+ }
+ fclose(file);
+ }
+ netdev_dev->cache_valid |= VALID_IN6;
+ }
+ *in6 = netdev_dev->in6;
+ return 0;
+}
+
+static void
+make_in4_sockaddr(struct sockaddr *sa, struct in_addr addr)
+{
+ struct sockaddr_in sin;
+ memset(&sin, 0, sizeof sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr = addr;
+ sin.sin_port = 0;
+
+ memset(sa, 0, sizeof *sa);
+ memcpy(sa, &sin, sizeof sin);
+}
+
+static int
+do_set_addr(struct netdev *netdev,
+ int ioctl_nr, const char *ioctl_name, struct in_addr addr)
+{
+ struct ifreq ifr;
+ ovs_strzcpy(ifr.ifr_name, netdev_get_name(netdev), sizeof ifr.ifr_name);
+ make_in4_sockaddr(&ifr.ifr_addr, addr);
+
+ return netdev_linux_do_ioctl(netdev_get_name(netdev), &ifr, ioctl_nr,
+ ioctl_name);
+}
+
+/* Adds 'router' as a default IP gateway. */
+static int
+netdev_linux_add_router(struct netdev *netdev OVS_UNUSED, struct in_addr router)
+{
+ struct in_addr any = { INADDR_ANY };
+ struct rtentry rt;
+ int error;
+
+ memset(&rt, 0, sizeof rt);
+ make_in4_sockaddr(&rt.rt_dst, any);
+ make_in4_sockaddr(&rt.rt_gateway, router);
+ make_in4_sockaddr(&rt.rt_genmask, any);
+ rt.rt_flags = RTF_UP | RTF_GATEWAY;
+ error = ioctl(af_inet_sock, SIOCADDRT, &rt) < 0 ? errno : 0;
+ if (error) {
+ VLOG_WARN("ioctl(SIOCADDRT): %s", strerror(error));
+ }
+ return error;
+}
+
+static int
+netdev_linux_get_next_hop(const struct in_addr *host, struct in_addr *next_hop,
+ char **netdev_name)
+{
+ static const char fn[] = "/proc/net/route";
+ FILE *stream;
+ char line[256];
+ int ln;
+
+ *netdev_name = NULL;
+ stream = fopen(fn, "r");
+ if (stream == NULL) {
+ VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(errno));
+ return errno;
+ }
+
+ ln = 0;
+ while (fgets(line, sizeof line, stream)) {
+ if (++ln >= 2) {
+ char iface[17];
+ ovs_be32 dest, gateway, mask;
+ int refcnt, metric, mtu;
+ unsigned int flags, use, window, irtt;
+
+ if (sscanf(line,
+ "%16s %"SCNx32" %"SCNx32" %04X %d %u %d %"SCNx32
+ " %d %u %u\n",
+ iface, &dest, &gateway, &flags, &refcnt,
+ &use, &metric, &mask, &mtu, &window, &irtt) != 11) {
+
+ VLOG_WARN_RL(&rl, "%s: could not parse line %d: %s",
+ fn, ln, line);
+ continue;
+ }
+ if (!(flags & RTF_UP)) {
+ /* Skip routes that aren't up. */
+ continue;
+ }
+
+ /* The output of 'dest', 'mask', and 'gateway' were given in
+ * network byte order, so we don't need need any endian
+ * conversions here. */
+ if ((dest & mask) == (host->s_addr & mask)) {
+ if (!gateway) {
+ /* The host is directly reachable. */
+ next_hop->s_addr = 0;
+ } else {
+ /* To reach the host, we must go through a gateway. */
+ next_hop->s_addr = gateway;
+ }
+ *netdev_name = xstrdup(iface);
+ fclose(stream);
+ return 0;
+ }
+ }
+ }
+
+ fclose(stream);
+ return ENXIO;
+}
+
+static int
+netdev_linux_get_status(const struct netdev *netdev, struct shash *sh)
+{
+ struct ethtool_drvinfo drvinfo;
+ int error;
+
+ memset(&drvinfo, 0, sizeof drvinfo);
+ error = netdev_linux_do_ethtool(netdev_get_name(netdev),
+ (struct ethtool_cmd *)&drvinfo,
+ ETHTOOL_GDRVINFO,
+ "ETHTOOL_GDRVINFO");
+ if (!error) {
+ shash_add(sh, "driver_name", xstrdup(drvinfo.driver));
+ shash_add(sh, "driver_version", xstrdup(drvinfo.version));
+ shash_add(sh, "firmware_version", xstrdup(drvinfo.fw_version));
+ }
+
+ 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_linux_arp_lookup(const struct netdev *netdev,
+ ovs_be32 ip, uint8_t mac[ETH_ADDR_LEN])
+{
+ struct arpreq r;
+ struct sockaddr_in sin;
+ int retval;
+
+ memset(&r, 0, sizeof r);
+ memset(&sin, 0, sizeof sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = ip;
+ sin.sin_port = 0;
+ memcpy(&r.arp_pa, &sin, sizeof sin);
+ r.arp_ha.sa_family = ARPHRD_ETHER;
+ r.arp_flags = 0;
+ ovs_strzcpy(r.arp_dev, netdev_get_name(netdev), sizeof r.arp_dev);
+ COVERAGE_INC(netdev_arp_lookup);
+ retval = ioctl(af_inet_sock, SIOCGARP, &r) < 0 ? errno : 0;
+ if (!retval) {
+ memcpy(mac, r.arp_ha.sa_data, ETH_ADDR_LEN);
+ } else if (retval != ENXIO) {
+ VLOG_WARN_RL(&rl, "%s: could not look up ARP entry for "IP_FMT": %s",
+ netdev_get_name(netdev), IP_ARGS(&ip), strerror(retval));
+ }
+ return retval;
+}
+
+static int
+nd_to_iff_flags(enum netdev_flags nd)
+{
+ int iff = 0;
+ if (nd & NETDEV_UP) {
+ iff |= IFF_UP;
+ }
+ if (nd & NETDEV_PROMISC) {
+ iff |= IFF_PROMISC;
+ }
+ return iff;
+}
+
+static int
+iff_to_nd_flags(int iff)
+{
+ enum netdev_flags nd = 0;
+ if (iff & IFF_UP) {
+ nd |= NETDEV_UP;
+ }
+ if (iff & IFF_PROMISC) {
+ nd |= NETDEV_PROMISC;
+ }
+ return nd;
+}
+
+static int
+netdev_linux_update_flags(struct netdev *netdev, enum netdev_flags off,
+ enum netdev_flags on, enum netdev_flags *old_flagsp)
+{
+ int old_flags, new_flags;
+ int error;
+
+ error = get_flags(netdev, &old_flags);
+ if (!error) {
+ *old_flagsp = iff_to_nd_flags(old_flags);
+ new_flags = (old_flags & ~nd_to_iff_flags(off)) | nd_to_iff_flags(on);
+ if (new_flags != old_flags) {
+ error = set_flags(netdev, new_flags);
+ }
+ }
+ return error;
+}
+
+static unsigned int
+netdev_linux_change_seq(const struct netdev *netdev)
+{
+ return netdev_dev_linux_cast(netdev_get_dev(netdev))->change_seq;
+}
+
+#define NETDEV_LINUX_CLASS(NAME, CREATE, ENUMERATE, GET_STATS, SET_STATS) \
+{ \
+ NAME, \
+ \
+ netdev_linux_init, \
+ netdev_linux_run, \
+ netdev_linux_wait, \
+ \
+ CREATE, \
+ netdev_linux_destroy, \
+ NULL, /* get_config */ \
+ NULL, /* set_config */ \
+ \
+ netdev_linux_open, \
+ netdev_linux_close, \
+ \
+ ENUMERATE, \
+ \
+ netdev_linux_listen, \
+ netdev_linux_recv, \
+ netdev_linux_recv_wait, \
+ netdev_linux_drain, \
+ \
+ netdev_linux_send, \
+ netdev_linux_send_wait, \
+ \
+ netdev_linux_set_etheraddr, \
+ netdev_linux_get_etheraddr, \
+ netdev_linux_get_mtu, \
+ netdev_linux_set_mtu, \
+ netdev_linux_get_ifindex, \
+ netdev_linux_get_carrier, \
+ netdev_linux_set_miimon_interval, \
+ GET_STATS, \
+ SET_STATS, \
+ \
+ netdev_linux_get_features, \
+ netdev_linux_set_advertisements, \
+ netdev_linux_get_vlan_vid, \
+ \
+ netdev_linux_set_policing, \
+ netdev_linux_get_qos_types, \
+ netdev_linux_get_qos_capabilities, \
+ netdev_linux_get_qos, \
+ netdev_linux_set_qos, \
+ netdev_linux_get_queue, \
+ netdev_linux_set_queue, \
+ netdev_linux_delete_queue, \
+ netdev_linux_get_queue_stats, \
+ netdev_linux_dump_queues, \
+ netdev_linux_dump_queue_stats, \
+ \
+ netdev_linux_get_in4, \
+ netdev_linux_set_in4, \
+ netdev_linux_get_in6, \
+ netdev_linux_add_router, \
+ netdev_linux_get_next_hop, \
+ netdev_linux_get_status, \
+ netdev_linux_arp_lookup, \
+ \
+ netdev_linux_update_flags, \
+ \
+ netdev_linux_change_seq \
+}
+
+const struct netdev_class netdev_linux_class =
+ NETDEV_LINUX_CLASS(
+ "system",
+ netdev_linux_create,
+ netdev_linux_enumerate,
+ netdev_linux_get_stats,
+ NULL); /* set_stats */
+
+const struct netdev_class netdev_tap_class =
+ NETDEV_LINUX_CLASS(
+ "tap",
+ netdev_linux_create_tap,
+ NULL, /* enumerate */
+ netdev_pseudo_get_stats,
+ NULL); /* set_stats */
+
+const struct netdev_class netdev_internal_class =
+ NETDEV_LINUX_CLASS(
+ "internal",
+ netdev_linux_create,
+ NULL, /* enumerate */
+ netdev_pseudo_get_stats,
+ netdev_vport_set_stats);
+\f
+/* 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. */
+ unsigned int priority; /* Lower values are higher priorities. */
+};
+
+static struct htb *
+htb_get__(const struct netdev *netdev)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ return CONTAINER_OF(netdev_dev->tc, struct htb, tc);
+}
+