netdev-linux: Don't restrict policing to IPv4 and don't call "tc".
[sliver-openvswitch.git] / lib / netdev-linux.c
index cc930e1..a100898 100644 (file)
 #include <arpa/inet.h>
 #include <inttypes.h>
 #include <linux/gen_stats.h>
+#include <linux/if_ether.h>
 #include <linux/if_tun.h>
 #include <linux/ip.h>
 #include <linux/types.h>
 #include <linux/ethtool.h>
 #include <linux/mii.h>
+#include <linux/pkt_cls.h>
 #include <linux/pkt_sched.h>
 #include <linux/rtnetlink.h>
 #include <linux/sockios.h>
@@ -37,9 +39,7 @@
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <netpacket/packet.h>
-#include <net/ethernet.h>
 #include <net/if.h>
-#include <linux/if_tunnel.h>
 #include <net/if_arp.h>
 #include <net/if_packet.h>
 #include <net/route.h>
@@ -73,7 +73,6 @@
 
 VLOG_DEFINE_THIS_MODULE(netdev_linux);
 
-COVERAGE_DEFINE(netdev_get_vlan_vid);
 COVERAGE_DEFINE(netdev_set_policing);
 COVERAGE_DEFINE(netdev_arp_lookup);
 COVERAGE_DEFINE(netdev_get_ifindex);
@@ -105,7 +104,7 @@ COVERAGE_DEFINE(netdev_ethtool);
 #define TC_RTAB_SIZE 1024
 #endif
 
-static struct nln_notifier netdev_linux_cache_notifier;
+static struct nln_notifier *netdev_linux_cache_notifier = NULL;
 static int cache_notifier_refcount;
 
 enum {
@@ -114,9 +113,8 @@ enum {
     VALID_IN4               = 1 << 2,
     VALID_IN6               = 1 << 3,
     VALID_MTU               = 1 << 4,
-    VALID_CARRIER           = 1 << 5,
-    VALID_POLICING          = 1 << 6,
-    VALID_HAVE_VPORT_STATS  = 1 << 7
+    VALID_POLICING          = 1 << 5,
+    VALID_HAVE_VPORT_STATS  = 1 << 6
 };
 
 struct tap_state {
@@ -329,6 +327,9 @@ static unsigned int tc_buffer_per_jiffy(unsigned int rate);
 static struct tcmsg *tc_make_request(const struct netdev *, int type,
                                      unsigned int flags, struct ofpbuf *);
 static int tc_transact(struct ofpbuf *request, struct ofpbuf **replyp);
+static int tc_add_del_ingress_qdisc(struct netdev *netdev, bool add);
+static int tc_add_policer(struct netdev *netdev, int kbits_rate,
+                          int kbits_burst);
 
 static int tc_parse_qdisc(const struct ofpbuf *, const char **kind,
                           struct nlattr **options);
@@ -367,7 +368,8 @@ struct netdev_dev_linux {
     struct in_addr address, netmask;
     struct in6_addr in6;
     int mtu;
-    int carrier;
+    bool carrier;
+    long long int carrier_resets;
     uint32_t kbits_rate;        /* Policing data. */
     uint32_t kbits_burst;
     bool have_vport_stats;
@@ -413,6 +415,7 @@ static int set_etheraddr(const char *netdev_name, int hwaddr_family,
                          const uint8_t[ETH_ADDR_LEN]);
 static int get_stats_via_netlink(int ifindex, struct netdev_stats *stats);
 static int get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats);
+static int get_carrier_via_sysfs(const char *name, bool *carrier);
 static int af_packet_sock(void);
 static void netdev_linux_miimon_run(void);
 static void netdev_linux_miimon_wait(void);
@@ -503,6 +506,12 @@ netdev_linux_cache_cb(const struct rtnetlink_link_change *change,
 
             if (is_netdev_linux_class(netdev_class)) {
                 dev = netdev_dev_linux_cast(base_dev);
+
+                if (dev->carrier != change->running) {
+                    dev->carrier = change->running;
+                    dev->carrier_resets++;
+                }
+
                 netdev_dev_linux_changed(dev);
             }
         }
@@ -513,13 +522,51 @@ netdev_linux_cache_cb(const struct rtnetlink_link_change *change,
         shash_init(&device_shash);
         netdev_dev_get_devices(&netdev_linux_class, &device_shash);
         SHASH_FOR_EACH (node, &device_shash) {
+            bool carrier;
+
             dev = node->data;
+
+            get_carrier_via_sysfs(node->name, &carrier);
+            if (dev->carrier != carrier) {
+                dev->carrier = carrier;
+                dev->carrier_resets++;
+            }
+
             netdev_dev_linux_changed(dev);
         }
         shash_destroy(&device_shash);
     }
 }
 
+static int
+cache_notifier_ref(void)
+{
+    if (!cache_notifier_refcount) {
+        assert(!netdev_linux_cache_notifier);
+
+        netdev_linux_cache_notifier =
+            rtnetlink_link_notifier_create(netdev_linux_cache_cb, NULL);
+
+        if (!netdev_linux_cache_notifier) {
+            return EINVAL;
+        }
+    }
+    cache_notifier_refcount++;
+
+    return 0;
+}
+
+static void
+cache_notifier_unref(void)
+{
+    assert(cache_notifier_refcount > 0);
+    if (!--cache_notifier_refcount) {
+        assert(netdev_linux_cache_notifier);
+        rtnetlink_link_notifier_destroy(netdev_linux_cache_notifier);
+        netdev_linux_cache_notifier = NULL;
+    }
+}
+
 /* Creates system and internal devices. */
 static int
 netdev_linux_create(const struct netdev_class *class, const char *name,
@@ -528,18 +575,15 @@ netdev_linux_create(const struct netdev_class *class, const char *name,
     struct netdev_dev_linux *netdev_dev;
     int error;
 
-    if (!cache_notifier_refcount) {
-        error = rtnetlink_link_notifier_register(&netdev_linux_cache_notifier,
-                                                 netdev_linux_cache_cb, NULL);
-        if (error) {
-            return error;
-        }
+    error = cache_notifier_ref();
+    if (error) {
+        return error;
     }
-    cache_notifier_refcount++;
 
     netdev_dev = xzalloc(sizeof *netdev_dev);
     netdev_dev->change_seq = 1;
     netdev_dev_init(&netdev_dev->netdev_dev, name, class);
+    get_carrier_via_sysfs(name, &netdev_dev->carrier);
 
     *netdev_devp = &netdev_dev->netdev_dev;
     return 0;
@@ -564,12 +608,17 @@ netdev_linux_create_tap(const struct netdev_class *class OVS_UNUSED,
     netdev_dev = xzalloc(sizeof *netdev_dev);
     state = &netdev_dev->state.tap;
 
+    error = cache_notifier_ref();
+    if (error) {
+        goto error;
+    }
+
     /* Open tap device. */
     state->fd = open(tap_dev, O_RDWR);
     if (state->fd < 0) {
         error = errno;
         VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error));
-        goto error;
+        goto error_unref_notifier;
     }
 
     /* Create tap device. */
@@ -579,19 +628,21 @@ netdev_linux_create_tap(const struct netdev_class *class OVS_UNUSED,
         VLOG_WARN("%s: creating tap device failed: %s", name,
                   strerror(errno));
         error = errno;
-        goto error;
+        goto error_unref_notifier;
     }
 
     /* Make non-blocking. */
     error = set_nonblocking(state->fd);
     if (error) {
-        goto error;
+        goto error_unref_notifier;
     }
 
     netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_tap_class);
     *netdev_devp = &netdev_dev->netdev_dev;
     return 0;
 
+error_unref_notifier:
+    cache_notifier_unref();
 error:
     free(netdev_dev);
     return error;
@@ -618,19 +669,12 @@ netdev_linux_destroy(struct netdev_dev *netdev_dev_)
         netdev_dev->tc->ops->tc_destroy(netdev_dev->tc);
     }
 
-    if (class == &netdev_linux_class || class == &netdev_internal_class) {
-        cache_notifier_refcount--;
-
-        if (!cache_notifier_refcount) {
-            rtnetlink_link_notifier_unregister(&netdev_linux_cache_notifier);
-        }
-    } else if (class == &netdev_tap_class) {
+    if (class == &netdev_tap_class) {
         destroy_tap(netdev_dev);
-    } else {
-        NOT_REACHED();
     }
-
     free(netdev_dev);
+
+    cache_notifier_unref();
 }
 
 static int
@@ -692,28 +736,6 @@ netdev_linux_close(struct netdev *netdev_)
     free(netdev);
 }
 
-/* Initializes 'sset' with a list of the names of all known network devices. */
-static int
-netdev_linux_enumerate(struct sset *sset)
-{
-    struct if_nameindex *names;
-
-    names = if_nameindex();
-    if (names) {
-        size_t i;
-
-        for (i = 0; names[i].if_name != NULL; i++) {
-            sset_add(sset, names[i].if_name);
-        }
-        if_freenameindex(names);
-        return 0;
-    } else {
-        VLOG_WARN("could not obtain list of network device names: %s",
-                  strerror(errno));
-        return errno;
-    }
-}
-
 static int
 netdev_linux_listen(struct netdev *netdev_)
 {
@@ -1036,62 +1058,20 @@ netdev_linux_get_carrier(const struct netdev *netdev_, bool *carrier)
 {
     struct netdev_dev_linux *netdev_dev =
                                 netdev_dev_linux_cast(netdev_get_dev(netdev_));
-    int error = 0;
-    char *fn = NULL;
-    int fd = -1;
 
     if (netdev_dev->miimon_interval > 0) {
         *carrier = netdev_dev->miimon;
-        return 0;
+    } else {
+        *carrier = netdev_dev->carrier;
     }
 
-    if (!(netdev_dev->cache_valid & VALID_CARRIER)) {
-        char line[8];
-        int retval;
-
-        fn = xasprintf("/sys/class/net/%s/carrier",
-                       netdev_get_name(netdev_));
-        fd = open(fn, O_RDONLY);
-        if (fd < 0) {
-            error = errno;
-            VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(error));
-            goto exit;
-        }
-
-        retval = read(fd, line, sizeof line);
-        if (retval < 0) {
-            error = errno;
-            if (error == EINVAL) {
-                /* This is the normal return value when we try to check carrier
-                 * if the network device is not up. */
-            } else {
-                VLOG_WARN_RL(&rl, "%s: read failed: %s", fn, strerror(error));
-            }
-            goto exit;
-        } else if (retval == 0) {
-            error = EPROTO;
-            VLOG_WARN_RL(&rl, "%s: unexpected end of file", fn);
-            goto exit;
-        }
-
-        if (line[0] != '0' && line[0] != '1') {
-            error = EPROTO;
-            VLOG_WARN_RL(&rl, "%s: value is %c (expected 0 or 1)",
-                         fn, line[0]);
-            goto exit;
-        }
-        netdev_dev->carrier = line[0] != '0';
-        netdev_dev->cache_valid |= VALID_CARRIER;
-    }
-    *carrier = netdev_dev->carrier;
-    error = 0;
+    return 0;
+}
 
-exit:
-    if (fd >= 0) {
-        close(fd);
-    }
-    free(fn);
-    return error;
+static long long int
+netdev_linux_get_carrier_resets(const struct netdev *netdev)
+{
+    return netdev_dev_linux_cast(netdev_get_dev(netdev))->carrier_resets;
 }
 
 static int
@@ -1588,104 +1568,8 @@ netdev_linux_set_advertisements(struct netdev *netdev, uint32_t advertise)
                                    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. */
+/* Attempts to set input rate limiting (policing) policy.  Returns 0 if
+ * successful, otherwise a positive errno value. */
 static int
 netdev_linux_set_policing(struct netdev *netdev,
                           uint32_t kbits_rate, uint32_t kbits_burst)
@@ -1693,7 +1577,7 @@ netdev_linux_set_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);
-    char command[1024];
+    int error;
 
     COVERAGE_INC(netdev_set_policing);
 
@@ -1708,27 +1592,34 @@ netdev_linux_set_policing(struct netdev *netdev,
         return 0;
     }
 
-    netdev_linux_remove_policing(netdev);
+    /* Remove any existing ingress qdisc. */
+    error = tc_add_del_ingress_qdisc(netdev, false);
+    if (error) {
+        VLOG_WARN_RL(&rl, "%s: removing policing failed: %s",
+                     netdev_name, strerror(error));
+        return error;
+    }
+
     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;
+        error = tc_add_del_ingress_qdisc(netdev, true);
+        if (error) {
+            VLOG_WARN_RL(&rl, "%s: adding policing qdisc failed: %s",
+                         netdev_name, strerror(error));
+            return error;
         }
 
-        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;
+        error = tc_add_policer(netdev, kbits_rate, kbits_burst);
+        if (error){
+            VLOG_WARN_RL(&rl, "%s: adding policing action failed: %s",
+                    netdev_name, strerror(error));
+            return error;
         }
-
-        netdev_dev->kbits_rate = kbits_rate;
-        netdev_dev->kbits_burst = kbits_burst;
-        netdev_dev->cache_valid |= VALID_POLICING;
     }
 
+    netdev_dev->kbits_rate = kbits_rate;
+    netdev_dev->kbits_burst = kbits_burst;
+    netdev_dev->cache_valid |= VALID_POLICING;
+
     return 0;
 }
 
@@ -2338,7 +2229,7 @@ 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)  \
+#define NETDEV_LINUX_CLASS(NAME, CREATE, GET_STATS, SET_STATS)  \
 {                                                               \
     NAME,                                                       \
                                                                 \
@@ -2354,8 +2245,6 @@ netdev_linux_change_seq(const struct netdev *netdev)
     netdev_linux_open,                                          \
     netdev_linux_close,                                         \
                                                                 \
-    ENUMERATE,                                                  \
-                                                                \
     netdev_linux_listen,                                        \
     netdev_linux_recv,                                          \
     netdev_linux_recv_wait,                                     \
@@ -2370,13 +2259,13 @@ netdev_linux_change_seq(const struct netdev *netdev)
     netdev_linux_set_mtu,                                       \
     netdev_linux_get_ifindex,                                   \
     netdev_linux_get_carrier,                                   \
+    netdev_linux_get_carrier_resets,                            \
     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,                                 \
@@ -2407,7 +2296,6 @@ const struct netdev_class netdev_linux_class =
     NETDEV_LINUX_CLASS(
         "system",
         netdev_linux_create,
-        netdev_linux_enumerate,
         netdev_linux_get_stats,
         NULL);                  /* set_stats */
 
@@ -2415,7 +2303,6 @@ const struct netdev_class netdev_tap_class =
     NETDEV_LINUX_CLASS(
         "tap",
         netdev_linux_create_tap,
-        NULL,                   /* enumerate */
         netdev_pseudo_get_stats,
         NULL);                  /* set_stats */
 
@@ -2423,7 +2310,6 @@ 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
@@ -3574,6 +3460,107 @@ tc_transact(struct ofpbuf *request, struct ofpbuf **replyp)
     return error;
 }
 
+/* Adds or deletes a root ingress qdisc on 'netdev'.  We use this for
+ * policing configuration.
+ *
+ * This function is equivalent to running the following when 'add' is true:
+ *     /sbin/tc qdisc add dev <devname> handle ffff: ingress
+ *
+ * This function is equivalent to running the following when 'add' is false:
+ *     /sbin/tc qdisc del dev <devname> handle ffff: ingress
+ *
+ * The configuration and stats may be seen with the following command:
+ *     /sbin/tc -s qdisc show dev <devname>
+ *
+ * Returns 0 if successful, otherwise a positive errno value.
+ */
+static int
+tc_add_del_ingress_qdisc(struct netdev *netdev, bool add)
+{
+    struct ofpbuf request;
+    struct tcmsg *tcmsg;
+    int error;
+    int type = add ? RTM_NEWQDISC : RTM_DELQDISC;
+    int flags = add ? NLM_F_EXCL | NLM_F_CREATE : 0;
+
+    tcmsg = tc_make_request(netdev, type, flags, &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) {
+        /* If we're deleting the qdisc, don't worry about some of the
+         * error conditions. */
+        if (!add && (error == ENOENT || error == EINVAL)) {
+            return 0;
+        }
+        return error;
+    }
+
+    return 0;
+}
+
+/* Adds a policer to 'netdev' with a rate of 'kbits_rate' and a burst size
+ * of 'kbits_burst'.
+ *
+ * This function is equivalent to running:
+ *     /sbin/tc filter add dev <devname> parent ffff: protocol all prio 49
+ *              basic police rate <kbits_rate>kbit burst <kbits_burst>k
+ *              mtu 65535 drop
+ *
+ * The configuration and stats may be seen with the following command:
+ *     /sbin/tc -s filter show <devname> eth0 parent ffff:
+ *
+ * Returns 0 if successful, otherwise a positive errno value.
+ */
+static int
+tc_add_policer(struct netdev *netdev, int kbits_rate, int kbits_burst)
+{
+    struct tc_police tc_police;
+    struct ofpbuf request;
+    struct tcmsg *tcmsg;
+    size_t basic_offset;
+    size_t police_offset;
+    int error;
+    int mtu = 65535;
+
+    memset(&tc_police, 0, sizeof tc_police);
+    tc_police.action = TC_POLICE_SHOT;
+    tc_police.mtu = mtu;
+    tc_fill_rate(&tc_police.rate, kbits_rate/8 * 1000, mtu);
+    tc_police.burst = tc_bytes_to_ticks(tc_police.rate.rate,
+                                        kbits_burst * 1024);
+
+    tcmsg = tc_make_request(netdev, RTM_NEWTFILTER,
+                            NLM_F_EXCL | NLM_F_CREATE, &request);
+    if (!tcmsg) {
+        return ENODEV;
+    }
+    tcmsg->tcm_parent = tc_make_handle(0xffff, 0);
+    tcmsg->tcm_info = tc_make_handle(49,
+                                     (OVS_FORCE uint16_t) htons(ETH_P_ALL));
+
+    nl_msg_put_string(&request, TCA_KIND, "basic");
+    basic_offset = nl_msg_start_nested(&request, TCA_OPTIONS);
+    police_offset = nl_msg_start_nested(&request, TCA_BASIC_POLICE);
+    nl_msg_put_unspec(&request, TCA_POLICE_TBF, &tc_police, sizeof tc_police);
+    tc_put_rtab(&request, TCA_POLICE_RATE, &tc_police.rate);
+    nl_msg_end_nested(&request, police_offset);
+    nl_msg_end_nested(&request, basic_offset);
+
+    error = tc_transact(&request, NULL);
+    if (error) {
+        return error;
+    }
+
+    return 0;
+}
+
 static void
 read_psched(void)
 {
@@ -4034,6 +4021,63 @@ tc_calc_buffer(unsigned int Bps, int mtu, uint64_t burst_bytes)
     return tc_bytes_to_ticks(Bps, MAX(burst_bytes, min_burst));
 }
 \f
+/* Linux-only functions declared in netdev-linux.h  */
+
+/* Returns a fd for an AF_INET socket or a negative errno value. */
+int
+netdev_linux_get_af_inet_sock(void)
+{
+    int error = netdev_linux_init();
+    return error ? -error : af_inet_sock;
+}
+
+/* Modifies the 'flag' bit in ethtool's flags field for 'netdev'.  If
+ * 'enable' is true, the bit is set.  Otherwise, it is cleared. */
+int
+netdev_linux_ethtool_set_flag(struct netdev *netdev, uint32_t flag,
+                              const char *flag_name, bool enable)
+{
+    const char *netdev_name = netdev_get_name(netdev);
+    struct ethtool_value evalue;
+    uint32_t new_flags;
+    int error;
+
+    memset(&evalue, 0, sizeof evalue);
+    error = netdev_linux_do_ethtool(netdev_name,
+                                    (struct ethtool_cmd *)&evalue,
+                                    ETHTOOL_GFLAGS, "ETHTOOL_GFLAGS");
+    if (error) {
+        return error;
+    }
+
+    evalue.data = new_flags = (evalue.data & ~flag) | (enable ? flag : 0);
+    error = netdev_linux_do_ethtool(netdev_name,
+                                    (struct ethtool_cmd *)&evalue,
+                                    ETHTOOL_SFLAGS, "ETHTOOL_SFLAGS");
+    if (error) {
+        return error;
+    }
+
+    memset(&evalue, 0, sizeof evalue);
+    error = netdev_linux_do_ethtool(netdev_name,
+                                    (struct ethtool_cmd *)&evalue,
+                                    ETHTOOL_GFLAGS, "ETHTOOL_GFLAGS");
+    if (error) {
+        return error;
+    }
+
+    if (new_flags != evalue.data) {
+        VLOG_WARN_RL(&rl, "attempt to %s ethtool %s flag on network "
+                     "device %s failed", enable ? "enable" : "disable",
+                     flag_name, netdev_name);
+        return EOPNOTSUPP;
+    }
+
+    return 0;
+}
+\f
+/* Utility functions. */
+
 /* Copies 'src' into 'dst', performing format conversion in the process. */
 static void
 netdev_stats_from_rtnl_link_stats(struct netdev_stats *dst,
@@ -4062,9 +4106,6 @@ netdev_stats_from_rtnl_link_stats(struct netdev_stats *dst,
     dst->tx_window_errors = src->tx_window_errors;
 }
 
-\f
-/* Utility functions. */
-
 static int
 get_stats_via_netlink(int ifindex, struct netdev_stats *stats)
 {
@@ -4172,6 +4213,58 @@ get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats)
     return ENODEV;
 }
 
+static int
+get_carrier_via_sysfs(const char *name, bool *carrier)
+{
+    char line[8];
+    int retval;
+
+    int error = 0;
+    char *fn = NULL;
+    int fd = -1;
+
+    *carrier = false;
+
+    fn = xasprintf("/sys/class/net/%s/carrier", name);
+    fd = open(fn, O_RDONLY);
+    if (fd < 0) {
+        error = errno;
+        VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(error));
+        goto exit;
+    }
+
+    retval = read(fd, line, sizeof line);
+    if (retval < 0) {
+        error = errno;
+        if (error == EINVAL) {
+            /* This is the normal return value when we try to check carrier if
+             * the network device is not up. */
+        } else {
+            VLOG_WARN_RL(&rl, "%s: read failed: %s", fn, strerror(error));
+        }
+        goto exit;
+    } else if (retval == 0) {
+        error = EPROTO;
+        VLOG_WARN_RL(&rl, "%s: unexpected end of file", fn);
+        goto exit;
+    }
+
+    if (line[0] != '0' && line[0] != '1') {
+        error = EPROTO;
+        VLOG_WARN_RL(&rl, "%s: value is %c (expected 0 or 1)", fn, line[0]);
+        goto exit;
+    }
+    *carrier = line[0] != '0';
+    error = 0;
+
+exit:
+    if (fd >= 0) {
+        close(fd);
+    }
+    free(fn);
+    return error;
+}
+
 static int
 get_flags(const struct netdev *netdev, int *flags)
 {
@@ -4299,51 +4392,6 @@ netdev_linux_do_ethtool(const char *name, struct ethtool_cmd *ecmd,
     }
 }
 
-/* Modifies the 'flag' bit in ethtool's flags field for 'netdev'.  If
- * 'enable' is true, the bit is set.  Otherwise, it is cleared. */
-int
-netdev_linux_ethtool_set_flag(struct netdev *netdev, uint32_t flag,
-                              const char *flag_name, bool enable)
-{
-    const char *netdev_name = netdev_get_name(netdev);
-    struct ethtool_value evalue;
-    uint32_t new_flags;
-    int error;
-
-    memset(&evalue, 0, sizeof evalue);
-    error = netdev_linux_do_ethtool(netdev_name,
-                                    (struct ethtool_cmd *)&evalue,
-                                    ETHTOOL_GFLAGS, "ETHTOOL_GFLAGS");
-    if (error) {
-        return error;
-    }
-
-    evalue.data = new_flags = (evalue.data & ~flag) | (enable ? flag : 0);
-    error = netdev_linux_do_ethtool(netdev_name,
-                                    (struct ethtool_cmd *)&evalue,
-                                    ETHTOOL_SFLAGS, "ETHTOOL_SFLAGS");
-    if (error) {
-        return error;
-    }
-
-    memset(&evalue, 0, sizeof evalue);
-    error = netdev_linux_do_ethtool(netdev_name,
-                                    (struct ethtool_cmd *)&evalue,
-                                    ETHTOOL_GFLAGS, "ETHTOOL_GFLAGS");
-    if (error) {
-        return error;
-    }
-
-    if (new_flags != evalue.data) {
-        VLOG_WARN_RL(&rl, "attempt to %s ethtool %s flag on network "
-                     "device %s failed", enable ? "enable" : "disable",
-                     flag_name, netdev_name);
-        return EOPNOTSUPP;
-    }
-
-    return 0;
-}
-
 static int
 netdev_linux_do_ioctl(const char *name, struct ifreq *ifr, int cmd,
                       const char *cmd_name)