netdev: Fall back to /proc/net/dev on kernels that don't support RTM_GETLINK.
authorBen Pfaff <blp@nicira.com>
Thu, 5 Mar 2009 23:09:00 +0000 (15:09 -0800)
committerBen Pfaff <blp@nicira.com>
Thu, 5 Mar 2009 23:09:00 +0000 (15:09 -0800)
RTM_GETLINK is the best way to get network device statistics, but it was
only enabled in all kernels in 2.6.19.  So now test for support at startup
and fall back to reading /proc/net/dev if RTM_GETLINK does not work.

lib/netdev.c

index 2e577962bef097d8b12841ff4823eb6f07345fdb..95d3d2c3ef7b7f5ce0a95ce31931f216faa62044 100644 (file)
@@ -130,6 +130,10 @@ static int af_inet_sock = -1;
 /* NETLINK_ROUTE socket. */
 static struct nl_sock *rtnl_sock;
 
+/* Can we use RTM_GETLINK to get network device statistics?  (In pre-2.6.19
+ * kernels, this was only available if wireless extensions were enabled.) */
+static bool use_netlink_stats;
+
 /* This is set pretty low because we probably won't learn anything from the
  * additional log messages. */
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
@@ -993,22 +997,16 @@ netdev_arp_lookup(const struct netdev *netdev,
     return retval;
 }
 
-int
-netdev_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
+static int
+get_stats_via_netlink(int ifindex, struct netdev_stats *stats)
 {
     struct ofpbuf request;
     struct ofpbuf *reply;
     struct ifinfomsg *ifi;
     const struct rtnl_link_stats *rtnl_stats;
     struct nlattr *attrs[ARRAY_SIZE(rtnlgrp_link_policy)];
-    int ifindex;
     int error;
 
-    error = get_ifindex(netdev, &ifindex);
-    if (error) {
-        goto error;
-    }
-
     ofpbuf_init(&request, 0);
     nl_msg_put_nlmsghdr(&request, rtnl_sock, sizeof *ifi,
                         RTM_GETLINK, NLM_F_REQUEST);
@@ -1018,21 +1016,19 @@ netdev_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
     error = nl_sock_transact(rtnl_sock, &request, &reply);
     ofpbuf_uninit(&request);
     if (error) {
-        goto error;
+        return error;
     }
 
     if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
                          rtnlgrp_link_policy,
                          attrs, ARRAY_SIZE(rtnlgrp_link_policy))) {
         ofpbuf_delete(reply);
-        error = EPROTO;
-        goto error;
+        return EPROTO;
     }
 
     if (!attrs[IFLA_STATS]) {
         VLOG_WARN_RL(&rl, "RTM_GETLINK reply lacks stats");
-        error = EPROTO;
-        goto error;
+        return EPROTO;
     }
 
     rtnl_stats = nl_attr_get(attrs[IFLA_STATS]);
@@ -1059,9 +1055,84 @@ netdev_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
     stats->tx_window_errors = rtnl_stats->tx_window_errors;
 
     return 0;
+}
 
-error:
-    memset(stats, 0xff, sizeof *stats);
+static int
+get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats)
+{
+    static const char fn[] = "/proc/net/dev";
+    char line[1024];
+    FILE *stream;
+    int ln;
+
+    stream = fopen(fn, "r");
+    if (!stream) {
+        VLOG_WARN_RL(&rl, "%s: open failed: %s", fn, strerror(errno));
+        return errno;
+    }
+
+    ln = 0;
+    while (fgets(line, sizeof line, stream)) {
+        if (++ln >= 3) {
+            char devname[16];
+#define X64 "%"SCNu64
+            if (sscanf(line,
+                       " %15[^:]:"
+                       X64 X64 X64 X64 X64 X64 X64 "%*u"
+                       X64 X64 X64 X64 X64 X64 X64 "%*u",
+                       devname,
+                       &stats->rx_bytes,
+                       &stats->rx_packets,
+                       &stats->rx_errors,
+                       &stats->rx_dropped,
+                       &stats->rx_fifo_errors,
+                       &stats->rx_frame_errors,
+                       &stats->multicast,
+                       &stats->tx_bytes,
+                       &stats->tx_packets,
+                       &stats->tx_errors,
+                       &stats->tx_dropped,
+                       &stats->tx_fifo_errors,
+                       &stats->collisions,
+                       &stats->tx_carrier_errors) != 15) {
+                VLOG_WARN_RL(&rl, "%s:%d: parse error", fn, ln);
+            } else if (!strcmp(devname, netdev_name)) {
+                stats->rx_length_errors = UINT64_MAX;
+                stats->rx_over_errors = UINT64_MAX;
+                stats->rx_crc_errors = UINT64_MAX;
+                stats->rx_missed_errors = UINT64_MAX;
+                stats->tx_aborted_errors = UINT64_MAX;
+                stats->tx_heartbeat_errors = UINT64_MAX;
+                stats->tx_window_errors = UINT64_MAX;
+                fclose(stream);
+                return 0;
+            }
+        }
+    }
+    VLOG_WARN_RL(&rl, "%s: no stats for %s", fn, netdev_name);
+    fclose(stream);
+    return ENODEV;
+}
+
+int
+netdev_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
+{
+    int error;
+
+    if (use_netlink_stats) {
+        int ifindex;
+
+        error = get_ifindex(netdev, &ifindex);
+        if (!error) {
+            error = get_stats_via_netlink(ifindex, stats);
+        }
+    } else {
+        error = get_stats_via_proc(netdev->name, stats);
+    }
+
+    if (error) {
+        memset(stats, 0xff, sizeof *stats);
+    }
     return error;
 }
 
@@ -1134,6 +1205,7 @@ init_netdev(void)
 {
     static bool inited;
     if (!inited) {
+        int ifindex;
         int error;
 
         inited = true;
@@ -1149,6 +1221,27 @@ init_netdev(void)
         if (error) {
             ofp_fatal(error, "socket(AF_NETLINK, NETLINK_ROUTE)");
         }
+
+        /* Decide on the netdev_get_stats() implementation to use.  Netlink is
+         * preferable, so if that works, we'll use it. */
+        ifindex = do_get_ifindex("lo");
+        if (ifindex < 0) {
+            VLOG_WARN("failed to get ifindex for lo, "
+                      "obtaining netdev stats from proc");
+            use_netlink_stats = false;
+        } else {
+            struct netdev_stats stats;
+            error = get_stats_via_netlink(ifindex, &stats);
+            if (!error) {
+                VLOG_DBG("obtaining netdev stats via rtnetlink");
+                use_netlink_stats = true;
+            } else {
+                VLOG_INFO("RTM_GETLINK failed (%s), obtaining netdev stats "
+                          "via proc (you are probably running a pre-2.6.19 "
+                          "kernel)", strerror(error));
+                use_netlink_stats = false;
+            }
+        }
     }
 }