datapath: Consider tunnels to have no MTU, fixing jumbo frame support.
[sliver-openvswitch.git] / lib / netdev-linux.c
index ed97eb3..1428ce6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010 Nicira Networks.
+ * Copyright (c) 2009, 2010, 2011 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
 #include <linux/ip.h>
 #include <linux/types.h>
 #include <linux/ethtool.h>
+#include <linux/mii.h>
 #include <linux/pkt_sched.h>
 #include <linux/rtnetlink.h>
 #include <linux/sockios.h>
@@ -46,6 +47,7 @@
 #include <unistd.h>
 
 #include "coverage.h"
+#include "dpif-linux.h"
 #include "dynamic-string.h"
 #include "fatal-signal.h"
 #include "hash.h"
@@ -59,6 +61,7 @@
 #include "packets.h"
 #include "poll-loop.h"
 #include "rtnetlink.h"
+#include "rtnetlink-link.h"
 #include "socket-util.h"
 #include "shash.h"
 #include "svec.h"
@@ -444,7 +447,7 @@ netdev_linux_init(void)
 
         /* Create rtnetlink socket. */
         if (!status) {
-            status = nl_sock_create(NETLINK_ROUTE, 0, 0, 0, &rtnl_sock);
+            status = nl_sock_create(NETLINK_ROUTE, &rtnl_sock);
             if (status) {
                 VLOG_ERR_RL(&rl, "failed to create rtnetlink socket: %s",
                             strerror(status));
@@ -457,17 +460,17 @@ netdev_linux_init(void)
 static void
 netdev_linux_run(void)
 {
-    rtnetlink_notifier_run();
+    rtnetlink_link_notifier_run();
 }
 
 static void
 netdev_linux_wait(void)
 {
-    rtnetlink_notifier_wait();
+    rtnetlink_link_notifier_wait();
 }
 
 static void
-netdev_linux_cache_cb(const struct rtnetlink_change *change,
+netdev_linux_cache_cb(const struct rtnetlink_link_change *change,
                       void *aux OVS_UNUSED)
 {
     struct netdev_dev_linux *dev;
@@ -511,8 +514,8 @@ netdev_linux_create(const struct netdev_class *class,
     }
 
     if (!cache_notifier_refcount) {
-        error = rtnetlink_notifier_register(&netdev_linux_cache_notifier,
-                                            netdev_linux_cache_cb, NULL);
+        error = rtnetlink_link_notifier_register(&netdev_linux_cache_notifier,
+                                                 netdev_linux_cache_cb, NULL);
         if (error) {
             return error;
         }
@@ -520,7 +523,7 @@ netdev_linux_create(const struct netdev_class *class,
     cache_notifier_refcount++;
 
     netdev_dev = xzalloc(sizeof *netdev_dev);
-    netdev_dev_init(&netdev_dev->netdev_dev, name, class);
+    netdev_dev_init(&netdev_dev->netdev_dev, name, args, class);
 
     *netdev_devp = &netdev_dev->netdev_dev;
     return 0;
@@ -574,7 +577,7 @@ netdev_linux_create_tap(const struct netdev_class *class OVS_UNUSED,
         goto error;
     }
 
-    netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_tap_class);
+    netdev_dev_init(&netdev_dev->netdev_dev, name, args, &netdev_tap_class);
     *netdev_devp = &netdev_dev->netdev_dev;
     return 0;
 
@@ -598,20 +601,22 @@ static void
 netdev_linux_destroy(struct netdev_dev *netdev_dev_)
 {
     struct netdev_dev_linux *netdev_dev = netdev_dev_linux_cast(netdev_dev_);
-    const char *type = netdev_dev_get_type(netdev_dev_);
+    const struct netdev_class *class = netdev_dev_get_class(netdev_dev_);
 
     if (netdev_dev->tc && netdev_dev->tc->ops->tc_destroy) {
         netdev_dev->tc->ops->tc_destroy(netdev_dev->tc);
     }
 
-    if (!strcmp(type, "system")) {
+    if (class == &netdev_linux_class || class == &netdev_internal_class) {
         cache_notifier_refcount--;
 
         if (!cache_notifier_refcount) {
-            rtnetlink_notifier_unregister(&netdev_linux_cache_notifier);
+            rtnetlink_link_notifier_unregister(&netdev_linux_cache_notifier);
         }
-    } else if (!strcmp(type, "tap")) {
+    } else if (class == &netdev_tap_class) {
         destroy_tap(netdev_dev);
+    } else {
+        NOT_REACHED();
     }
 
     free(netdev_dev);
@@ -1004,6 +1009,66 @@ exit:
     return error;
 }
 
+static int
+netdev_linux_do_miimon(const struct netdev *netdev, int cmd,
+                       const char *cmd_name, struct mii_ioctl_data *data)
+{
+    struct ifreq ifr;
+    int error;
+
+    memset(&ifr, 0, sizeof ifr);
+    memcpy(&ifr.ifr_data, data, sizeof *data);
+    error = netdev_linux_do_ioctl(netdev_get_name(netdev),
+                                  &ifr, cmd, cmd_name);
+    memcpy(data, &ifr.ifr_data, sizeof *data);
+
+    return error;
+}
+
+static int
+netdev_linux_get_miimon(const struct netdev *netdev, bool *miimon)
+{
+    const char *name = netdev_get_name(netdev);
+    struct mii_ioctl_data data;
+    int error;
+
+    *miimon = false;
+
+    memset(&data, 0, sizeof data);
+    error = netdev_linux_do_miimon(netdev, SIOCGMIIPHY, "SIOCGMIIPHY", &data);
+    if (!error) {
+        /* data.phy_id is filled out by previous SIOCGMIIPHY miimon call. */
+        data.reg_num = MII_BMSR;
+        error = netdev_linux_do_miimon(netdev, SIOCGMIIREG, "SIOCGMIIREG",
+                                       &data);
+
+        if (!error) {
+            *miimon = !!(data.val_out & BMSR_LSTATUS);
+        } else {
+            VLOG_WARN_RL(&rl, "%s: failed to query MII", name);
+        }
+    } else {
+        struct ethtool_cmd ecmd;
+
+        VLOG_DBG_RL(&rl, "%s: failed to query MII, falling back to ethtool",
+                    name);
+
+        memset(&ecmd, 0, sizeof ecmd);
+        error = netdev_linux_do_ethtool(name, &ecmd, ETHTOOL_GLINK,
+                                        "ETHTOOL_GLINK");
+        if (!error) {
+            struct ethtool_value eval;
+
+            memcpy(&eval, &ecmd, sizeof eval);
+            *miimon = !!eval.data;
+        } else {
+            VLOG_WARN_RL(&rl, "%s: ethtool link status failed", name);
+        }
+    }
+
+    return error;
+}
+
 /* Check whether we 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. */
@@ -1041,22 +1106,8 @@ netdev_linux_update_is_pseudo(struct netdev_dev_linux *netdev_dev)
         const char *type = netdev_dev_get_type(&netdev_dev->netdev_dev);
 
         netdev_dev->is_tap = !strcmp(type, "tap");
-        netdev_dev->is_internal = false;
-        if (!netdev_dev->is_tap) {
-            struct ethtool_drvinfo drvinfo;
-            int error;
-
-            memset(&drvinfo, 0, sizeof drvinfo);
-            error = netdev_linux_do_ethtool(name,
-                                            (struct ethtool_cmd *)&drvinfo,
-                                            ETHTOOL_GDRVINFO,
-                                            "ETHTOOL_GDRVINFO");
-
-            if (!error && !strcmp(drvinfo.driver, "openvswitch")) {
-                netdev_dev->is_internal = true;
-            }
-        }
-
+        netdev_dev->is_internal = (!netdev_dev->is_tap
+                                   && dpif_linux_is_internal_device(name));
         netdev_dev->cache_valid |= VALID_IS_PSEUDO;
     }
 }
@@ -1137,7 +1188,7 @@ netdev_linux_get_stats(const struct netdev *netdev_,
  * 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(struct netdev *netdev,
+netdev_linux_get_features(const struct netdev *netdev,
                           uint32_t *current, uint32_t *advertised,
                           uint32_t *supported, uint32_t *peer)
 {
@@ -1961,6 +2012,26 @@ netdev_linux_get_next_hop(const struct in_addr *host, struct in_addr *next_hop,
     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,
@@ -2047,7 +2118,7 @@ poll_notify(struct list *list)
 }
 
 static void
-netdev_linux_poll_cb(const struct rtnetlink_change *change,
+netdev_linux_poll_cb(const struct rtnetlink_link_change *change,
                      void *aux OVS_UNUSED)
 {
     if (change) {
@@ -2074,8 +2145,9 @@ netdev_linux_poll_add(struct netdev *netdev,
     struct list *list;
 
     if (shash_is_empty(&netdev_linux_notifiers)) {
-        int error = rtnetlink_notifier_register(&netdev_linux_poll_notifier,
-                                                   netdev_linux_poll_cb, NULL);
+        int error;
+        error = rtnetlink_link_notifier_register(&netdev_linux_poll_notifier,
+                                                 netdev_linux_poll_cb, NULL);
         if (error) {
             return error;
         }
@@ -2115,7 +2187,7 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_)
 
     /* If that was the last notifier, unregister. */
     if (shash_is_empty(&netdev_linux_notifiers)) {
-        rtnetlink_notifier_unregister(&netdev_linux_poll_notifier);
+        rtnetlink_link_notifier_unregister(&netdev_linux_poll_notifier);
     }
 }
 
@@ -2129,7 +2201,7 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_)
                                                                 \
     CREATE,                                                     \
     netdev_linux_destroy,                                       \
-    NULL,                       /* reconfigure */               \
+    NULL,                       /* set_config */                \
                                                                 \
     netdev_linux_open,                                          \
     netdev_linux_close,                                         \
@@ -2148,6 +2220,7 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_)
     netdev_linux_get_mtu,                                       \
     netdev_linux_get_ifindex,                                   \
     netdev_linux_get_carrier,                                   \
+    netdev_linux_get_miimon,                                    \
     netdev_linux_get_stats,                                     \
     SET_STATS,                                                  \
                                                                 \
@@ -2172,6 +2245,7 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_)
     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,                                  \
@@ -2291,6 +2365,11 @@ htb_setup_class__(struct netdev *netdev, unsigned int handle,
     int mtu;
 
     netdev_get_mtu(netdev, &mtu);
+    if (mtu == INT_MAX) {
+        VLOG_WARN_RL(&rl, "cannot set up HTB on device %s that lacks MTU",
+                     netdev_get_name(netdev));
+        return EINVAL;
+    }
 
     memset(&opt, 0, sizeof opt);
     tc_fill_rate(&opt.rate, class->min_rate, mtu);
@@ -2410,6 +2489,13 @@ htb_parse_class_details__(struct netdev *netdev,
     const char *priority_s = shash_find_data(details, "priority");
     int mtu;
 
+    netdev_get_mtu(netdev, &mtu);
+    if (mtu == INT_MAX) {
+        VLOG_WARN_RL(&rl, "cannot parse HTB class on device %s that lacks MTU",
+                     netdev_get_name(netdev));
+        return EINVAL;
+    }
+
     /* min-rate.  Don't allow a min-rate below 1500 bytes/s. */
     if (!min_rate_s) {
         /* min-rate is required. */
@@ -2435,7 +2521,6 @@ htb_parse_class_details__(struct netdev *netdev,
      * doesn't include the Ethernet header, we need to add at least 14 (18?) to
      * the MTU.  We actually add 64, instead of 14, as a guard against
      * additional headers get tacked on somewhere that we're not aware of. */
-    netdev_get_mtu(netdev, &mtu);
     hc->burst = burst_s ? strtoull(burst_s, NULL, 10) / 8 : 0;
     hc->burst = MAX(hc->burst, mtu + 64);