gre: Add support for destroying GRE devices.
authorJesse Gross <jesse@nicira.com>
Tue, 12 Jan 2010 00:00:17 +0000 (19:00 -0500)
committerJesse Gross <jesse@nicira.com>
Fri, 15 Jan 2010 16:34:34 +0000 (11:34 -0500)
This allows GRE tunnel devices to be torn down on graceful exit
of vswitch and cleaned up on restart for non-graceful exits.

datapath/linux-2.6/compat-2.6/ip_gre.c
lib/netdev-linux.c
lib/netdev.c

index 5a4b98b..48d7d6c 100644 (file)
@@ -24,6 +24,7 @@
  */
 
 #include <linux/capability.h>
+#include <linux/ethtool.h>
 #include <linux/module.h>
 #include <linux/types.h>
 #include <linux/kernel.h>
@@ -1299,6 +1300,18 @@ static int ipgre_close(struct net_device *dev)
 
 #endif
 
+static void ethtool_getinfo(struct net_device *dev,
+                           struct ethtool_drvinfo *info)
+{
+       strcpy(info->driver, "ip_gre");
+       strcpy(info->version, "Open vSwitch "VERSION BUILDNR);
+       strcpy(info->bus_info, dev->type == ARPHRD_ETHER ? "gretap" : "gre");
+}
+
+static struct ethtool_ops ethtool_ops = {
+       .get_drvinfo = ethtool_getinfo,
+};
+
 #ifdef HAVE_NET_DEVICE_OPS
 static const struct net_device_ops ipgre_netdev_ops = {
        .ndo_init               = ipgre_tunnel_init,
@@ -1341,6 +1354,8 @@ static void ipgre_tunnel_setup(struct net_device *dev)
        dev->addr_len           = 4;
        dev->features           |= NETIF_F_NETNS_LOCAL;
        dev->priv_flags         &= ~IFF_XMIT_DST_RELEASE;
+
+       SET_ETHTOOL_OPS(dev, &ethtool_ops);
 }
 
 static int ipgre_tunnel_init(struct net_device *dev)
@@ -1547,6 +1562,8 @@ static void ipgre_tap_setup(struct net_device *dev)
 
        dev->iflink             = 0;
        dev->features           |= NETIF_F_NETNS_LOCAL;
+
+       SET_ETHTOOL_OPS(dev, &ethtool_ops);
 }
 
 #ifndef GRE_IOCTL_ONLY
index e5e4adb..53eb28b 100644 (file)
@@ -155,7 +155,8 @@ static struct rtnetlink_notifier netdev_linux_poll_notifier;
  * additional log messages. */
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
 
-static int netdev_linux_do_ethtool(const struct netdev *, struct ethtool_cmd *,
+static int destroy_gre(const char *name);
+static int netdev_linux_do_ethtool(const char *name, struct ethtool_cmd *,
                                    int cmd, const char *cmd_name);
 static int netdev_linux_do_ioctl(const char *name, struct ifreq *, int cmd,
                                  const char *cmd_name);
@@ -256,12 +257,13 @@ setup_gre_netlink(const char *name UNUSED, struct gre_config *config UNUSED,
     uint16_t oflags = 0;
     uint8_t pmtudisc = 0;
 
+    VLOG_DBG("%s: attempting to create gre device using netlink", name);
+
     if (!gre_descriptors.nl_sock) {
         error = nl_sock_create(NETLINK_ROUTE, 0, 0, 0,
                                &gre_descriptors.nl_sock);
         if (error) {
-            VLOG_WARN("couldn't create netlink socket: %s\n", strerror(error));
-            gre_descriptors.nl_sock = NULL;
+            VLOG_WARN("couldn't create netlink socket: %s", strerror(error));
             goto error;
         }
     }
@@ -326,7 +328,7 @@ setup_gre_netlink(const char *name UNUSED, struct gre_config *config UNUSED,
     error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
     ofpbuf_uninit(&request);
     if (error) {
-        VLOG_WARN("couldn't transact netlink socket: %s\n", strerror(error));
+        VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
         goto error;
     }
     ofpbuf_delete(reply);
@@ -342,6 +344,8 @@ setup_gre_ioctl(const char *name, struct gre_config *config, bool create)
     struct ip_tunnel_parm p;
     struct ifreq ifr;
 
+    VLOG_DBG("%s: attempting to create gre device using ioctl", name);
+
     memset(&p, 0, sizeof p);
 
     strncpy(p.name, name, IFNAMSIZ);
@@ -374,7 +378,7 @@ setup_gre_ioctl(const char *name, struct gre_config *config, bool create)
     if (!gre_descriptors.ioctl_fd) {
         gre_descriptors.ioctl_fd = socket(AF_INET, SOCK_DGRAM, 0);
         if (gre_descriptors.ioctl_fd < 0) {
-            VLOG_WARN("couldn't create gre ioctl socket: %s\n", strerror(errno));
+            VLOG_WARN("couldn't create gre ioctl socket: %s", strerror(errno));
             gre_descriptors.ioctl_fd = 0;
             return errno;
         }
@@ -382,13 +386,94 @@ setup_gre_ioctl(const char *name, struct gre_config *config, bool create)
 
     if (ioctl(gre_descriptors.ioctl_fd, create ? SIOCADDGRETAP : SIOCCHGGRETAP,
               &ifr) < 0) {
-        VLOG_WARN("couldn't do gre ioctl: %s\n", strerror(errno));
+        VLOG_WARN("couldn't do gre ioctl: %s", strerror(errno));
         return errno;
     }
 
     return 0;
 }
 
+/* The arguments are marked as unused to prevent warnings on platforms where
+ * the Netlink interface isn't supported. */
+static bool
+check_gre_device_netlink(const char *name UNUSED)
+{
+#ifdef GRE_IOCTL_ONLY
+    return false;
+#else
+    static const struct nl_policy getlink_policy[] = {
+        [IFLA_LINKINFO] = { .type = NL_A_NESTED, .optional = false },
+    };
+
+    static const struct nl_policy linkinfo_policy[] = {
+        [IFLA_INFO_KIND] = { .type = NL_A_STRING, .optional = false },
+    };
+
+    int error;
+    bool ret = false;
+    struct ofpbuf request, *reply;
+    struct ifinfomsg ifinfomsg;
+    struct nlattr *getlink_attrs[ARRAY_SIZE(getlink_policy)];
+    struct nlattr *linkinfo_attrs[ARRAY_SIZE(linkinfo_policy)];
+    struct ofpbuf linkinfo;
+    const char *device_kind;
+
+    ofpbuf_init(&request, 0);
+
+    nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock,
+                        NLMSG_LENGTH(sizeof ifinfomsg), RTM_GETLINK,
+                        NLM_F_REQUEST);
+
+    memset(&ifinfomsg, 0, sizeof ifinfomsg);
+    ifinfomsg.ifi_family = AF_UNSPEC;
+    ifinfomsg.ifi_index =  do_get_ifindex(name);
+    nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg);
+
+    error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
+    ofpbuf_uninit(&request);
+    if (error) {
+        VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
+        return false;
+    }
+
+    if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
+                         getlink_policy, getlink_attrs,
+                         ARRAY_SIZE(getlink_policy))) {
+        VLOG_WARN("received bad rtnl message (getlink policy)");
+        goto error;
+    }
+
+    linkinfo.data = (void *)nl_attr_get(getlink_attrs[IFLA_LINKINFO]);
+    linkinfo.size = nl_attr_get_size(getlink_attrs[IFLA_LINKINFO]);
+    if (!nl_policy_parse(&linkinfo, 0, linkinfo_policy,
+                        linkinfo_attrs, ARRAY_SIZE(linkinfo_policy))) {
+        VLOG_WARN("received bad rtnl message (linkinfo policy)");
+        goto error;
+    }
+
+    device_kind = nl_attr_get_string(linkinfo_attrs[IFLA_INFO_KIND]);
+    ret = !strcmp(device_kind, "gretap");
+
+error:
+    ofpbuf_delete(reply);
+    return ret;
+#endif
+}
+
+static bool
+check_gre_device_ioctl(const char *name)
+{
+    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");
+
+    return !error && !strcmp(drvinfo.driver, "ip_gre")
+           && !strcmp(drvinfo.bus_info, "gretap");
+}
+
 static int
 setup_gre(const char *name, const struct shash *args, bool create)
 {
@@ -451,6 +536,32 @@ setup_gre(const char *name, const struct shash *args, bool create)
         error = setup_gre_ioctl(name, &config, create);
     }
 
+    if (create && error == EEXIST) {
+        bool gre_device;
+
+        if (gre_descriptors.use_ioctl) {
+            gre_device = check_gre_device_ioctl(name);
+        } else {
+            gre_device = check_gre_device_netlink(name);
+        }
+
+        if (!gre_device) {
+            goto error;
+        }
+
+        VLOG_WARN("replacing existing gre device %s", name);
+        error = destroy_gre(name);
+        if (error) {
+            goto error;
+        }
+
+        if (gre_descriptors.use_ioctl) {
+            error = setup_gre_ioctl(name, &config, create);
+        } else {
+            error = setup_gre_netlink(name, &config, create);
+        }
+    }
+
 error:
     return error;
 }
@@ -590,12 +701,11 @@ netdev_linux_reconfigure_gre(struct netdev_dev *netdev_dev_,
 /* The arguments are marked as unused to prevent warnings on platforms where
  * the Netlink interface isn't supported. */
 static int
-destroy_gre_netlink(struct netdev_dev_linux *netdev_dev UNUSED)
+destroy_gre_netlink(const char *name UNUSED)
 {
 #ifdef GRE_IOCTL_ONLY
     return EOPNOTSUPP;
 #else
-    const char *name = netdev_dev_get_name(&netdev_dev->netdev_dev);
     int error;
     struct ofpbuf request, *reply;
     struct ifinfomsg ifinfomsg;
@@ -618,7 +728,7 @@ destroy_gre_netlink(struct netdev_dev_linux *netdev_dev UNUSED)
     error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
     ofpbuf_uninit(&request);
     if (error) {
-        VLOG_WARN("couldn't transact netlink socket: %s\n", strerror(error));
+        VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
         goto error;
     }
     ofpbuf_delete(reply);
@@ -629,9 +739,8 @@ error:
 }
 
 static int
-destroy_gre_ioctl(struct netdev_dev_linux *netdev_dev)
+destroy_gre_ioctl(const char *name)
 {
-    const char *name = netdev_dev_get_name(&netdev_dev->netdev_dev);
     struct ip_tunnel_parm p;
     struct ifreq ifr;
 
@@ -659,6 +768,16 @@ destroy_tap(struct netdev_dev_linux *netdev_dev)
     }
 }
 
+static int
+destroy_gre(const char *name)
+{
+    if (gre_descriptors.use_ioctl) {
+        return destroy_gre_ioctl(name);
+    } else {
+        return destroy_gre_netlink(name);
+    }
+}
+
 /* Destroys the netdev device 'netdev_dev_'. */
 static void
 netdev_linux_destroy(struct netdev_dev *netdev_dev_)
@@ -675,11 +794,7 @@ netdev_linux_destroy(struct netdev_dev *netdev_dev_)
     } else if (!strcmp(type, "tap")) {
         destroy_tap(netdev_dev);
     } else if (!strcmp(type, "gre")) {
-        if (gre_descriptors.use_ioctl) {
-            destroy_gre_ioctl(netdev_dev);
-        } else {
-            destroy_gre_netlink(netdev_dev);
-        }
+        destroy_gre(netdev_dev_get_name(&netdev_dev->netdev_dev));
     }
 
     free(netdev_dev_);
@@ -1143,7 +1258,7 @@ netdev_linux_get_stats(const struct netdev *netdev_,
             struct ethtool_drvinfo drvinfo;
 
             memset(&drvinfo, 0, sizeof drvinfo);
-            error = netdev_linux_do_ethtool(netdev_,
+            error = netdev_linux_do_ethtool(netdev_get_name(netdev_),
                                             (struct ethtool_cmd *)&drvinfo,
                                             ETHTOOL_GDRVINFO,
                                             "ETHTOOL_GDRVINFO");
@@ -1219,7 +1334,7 @@ netdev_linux_get_features(struct netdev *netdev,
     int error;
 
     memset(&ecmd, 0, sizeof ecmd);
-    error = netdev_linux_do_ethtool(netdev, &ecmd,
+    error = netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
                                     ETHTOOL_GSET, "ETHTOOL_GSET");
     if (error) {
         return error;
@@ -1340,7 +1455,7 @@ netdev_linux_set_advertisements(struct netdev *netdev, uint32_t advertise)
     int error;
 
     memset(&ecmd, 0, sizeof ecmd);
-    error = netdev_linux_do_ethtool(netdev, &ecmd,
+    error = netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
                                     ETHTOOL_GSET, "ETHTOOL_GSET");
     if (error) {
         return error;
@@ -1383,7 +1498,7 @@ netdev_linux_set_advertisements(struct netdev *netdev, uint32_t advertise)
     if (advertise & OFPPF_PAUSE_ASYM) {
         ecmd.advertising |= ADVERTISED_Asym_Pause;
     }
-    return netdev_linux_do_ethtool(netdev, &ecmd,
+    return netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
                                    ETHTOOL_SSET, "ETHTOOL_SSET");
 }
 
@@ -2243,13 +2358,13 @@ set_etheraddr(const char *netdev_name, int hwaddr_family,
 }
 
 static int
-netdev_linux_do_ethtool(const struct netdev *netdev, struct ethtool_cmd *ecmd,
+netdev_linux_do_ethtool(const char *name, struct ethtool_cmd *ecmd,
                         int cmd, const char *cmd_name)
 {
     struct ifreq ifr;
 
     memset(&ifr, 0, sizeof ifr);
-    strncpy(ifr.ifr_name, netdev_get_name(netdev), sizeof ifr.ifr_name);
+    strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
     ifr.ifr_data = (caddr_t) ecmd;
 
     ecmd->cmd = cmd;
@@ -2259,8 +2374,7 @@ netdev_linux_do_ethtool(const struct netdev *netdev, struct ethtool_cmd *ecmd,
     } else {
         if (errno != EOPNOTSUPP) {
             VLOG_WARN_RL(&rl, "ethtool command %s on network device %s "
-                         "failed: %s", cmd_name, netdev_get_name(netdev),
-                         strerror(errno));
+                         "failed: %s", cmd_name, name, strerror(errno));
         } else {
             /* The device doesn't support this operation.  That's pretty
              * common, so there's no point in logging anything. */
index 2a29109..428ad95 100644 (file)
@@ -57,7 +57,7 @@ static struct list netdev_list = LIST_INITIALIZER(&netdev_list);
  * additional log messages. */
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
 
-static void restore_all_flags(void *aux);
+static void close_all_netdevs(void *aux UNUSED);
 static int restore_flags(struct netdev *netdev);
 
 /* Attempts to initialize the netdev module.  Returns 0 if successful,
@@ -73,7 +73,7 @@ netdev_initialize(void)
     if (status < 0) {
         int i, j;
 
-        fatal_signal_add_hook(restore_all_flags, NULL, true);
+        fatal_signal_add_hook(close_all_netdevs, NULL, true);
 
         status = 0;
         for (i = j = 0; i < n_netdev_classes; i++) {
@@ -1098,13 +1098,13 @@ restore_flags(struct netdev *netdev)
     return 0;
 }
 
-/* Retores all the flags on all network devices that we modified.  Called from
- * a signal handler, so it does not attempt to report error conditions. */
+/* Close all netdevs on shutdown so they can do any needed cleanup such as
+ * destroying devices, restoring flags, etc. */
 static void
-restore_all_flags(void *aux UNUSED)
+close_all_netdevs(void *aux UNUSED)
 {
-    struct netdev *netdev;
-    LIST_FOR_EACH (netdev, struct netdev, node, &netdev_list) {
-        restore_flags(netdev);
+    struct netdev *netdev, *next;
+    LIST_FOR_EACH_SAFE(netdev, next, struct netdev, node, &netdev_list) {
+        netdev_close(netdev);
     }
 }