netdev: Implement netdev_monitor, for monitoring network device status changes.
authorBen Pfaff <blp@nicira.com>
Wed, 7 Jan 2009 17:29:50 +0000 (09:29 -0800)
committerBen Pfaff <blp@nicira.com>
Fri, 23 Jan 2009 18:52:06 +0000 (10:52 -0800)
lib/netdev.c
lib/netdev.h
lib/svec.c
lib/svec.h

index f9b27aceef151378eabed71a1e604022a7bba72b..8cf778f35a61ed23d4219b950e5d6dba9bd3abd8 100644 (file)
 #include <fcntl.h>
 #include <arpa/inet.h>
 #include <inttypes.h>
-#include <linux/if.h>
 #include <linux/if_tun.h>
 #include <linux/types.h>
 #include <linux/ethtool.h>
+#include <linux/rtnetlink.h>
 #include <linux/sockios.h>
 #include <linux/version.h>
 #include <sys/types.h>
@@ -50,6 +50,7 @@
 #include <sys/socket.h>
 #include <netpacket/packet.h>
 #include <net/ethernet.h>
+#include <net/if.h>
 #include <net/if_arp.h>
 #include <net/if_packet.h>
 #include <net/route.h>
@@ -60,6 +61,7 @@
 
 #include "fatal-signal.h"
 #include "list.h"
+#include "netlink.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
 #include "packets.h"
 #include "socket-util.h"
 #include "svec.h"
 
+/* linux/if.h defines IFF_LOWER_UP, net/if.h doesn't.
+ * net/if.h defines if_nameindex(), linux/if.h doesn't.
+ * We can't include both headers, so define IFF_LOWER_UP ourselves. */
+#ifndef IFF_LOWER_UP
+#define IFF_LOWER_UP 0x10000
+#endif
+
 #define THIS_MODULE VLM_netdev
 #include "vlog.h"
 
@@ -963,6 +972,171 @@ netdev_nodev_get_flags(const char *netdev_name, enum netdev_flags *flagsp)
     return 0;
 }
 \f
+struct netdev_monitor {
+    struct nl_sock *sock;
+    struct svec netdevs;
+    struct svec changed;
+};
+
+/* Policy for RTNLGRP_LINK messages.
+ *
+ * There are *many* more fields in these messages, but currently we only care
+ * about interface names. */
+static const struct nl_policy rtnlgrp_link_policy[] = {
+    [IFLA_IFNAME] = { .type = NL_A_STRING, .optional = false },
+};
+
+static const char *lookup_netdev(const struct netdev_monitor *, const char *);
+static const char *pop_changed(struct netdev_monitor *);
+static const char *all_netdevs_changed(struct netdev_monitor *);
+
+/* Creates a new network device monitor that initially monitors no
+ * devices.  On success, sets '*monp' to the new network monitor and returns
+ * 0; on failure, sets '*monp' to a null pointer and returns a positive errno
+ * value. */
+int
+netdev_monitor_create(struct netdev_monitor **monp)
+{
+    struct netdev_monitor *mon;
+    struct nl_sock *sock;
+    int error;
+
+    *monp = NULL;
+    error = nl_sock_create(NETLINK_ROUTE, RTNLGRP_LINK, 0, 0, &sock);
+    if (error) {
+        /* XXX Fall back to polling?  Non-root is not allowed to subscribe to
+         * multicast groups but can still poll network device state. */
+        VLOG_WARN("could not create rtnetlink socket: %s", strerror(error));
+        return error;
+    }
+
+    mon = *monp = xmalloc(sizeof *mon);
+    mon->sock = sock;
+    svec_init(&mon->netdevs);
+    svec_init(&mon->changed);
+    return 0;
+}
+
+void
+netdev_monitor_destroy(struct netdev_monitor *mon)
+{
+    if (mon) {
+        nl_sock_destroy(mon->sock);
+        svec_destroy(&mon->netdevs);
+        svec_destroy(&mon->changed);
+        free(mon);
+    }
+}
+
+/* Sets the set of network devices monitored by 'mon' to the 'n_netdevs'
+ * network devices named in 'netdevs'.  The caller retains ownership of
+ * 'netdevs'. */
+void
+netdev_monitor_set_devices(struct netdev_monitor *mon,
+                           char **netdevs, size_t n_netdevs)
+{
+    size_t i;
+
+    svec_clear(&mon->netdevs);
+    for (i = 0; i < n_netdevs; i++) {
+        svec_add(&mon->netdevs, netdevs[i]);
+    }
+    svec_sort(&mon->netdevs);
+}
+
+/* If the state of any network device has changed, returns its name.  The
+ * caller must not modify or free the name.
+ *
+ * This function can return "false positives".  The caller is responsible for
+ * verifying that the network device's state actually changed, if necessary.
+ *
+ * If no network device's state has changed, returns a null pointer. */
+const char *
+netdev_monitor_poll(struct netdev_monitor *mon)
+{
+    static struct vlog_rate_limit slow_rl = VLOG_RATE_LIMIT_INIT(1, 5);
+    const char *changed_name;
+
+    changed_name = pop_changed(mon);
+    if (changed_name) {
+        return changed_name;
+    }
+
+    for (;;) {
+        struct ofpbuf *buf;
+        int retval;
+
+        retval = nl_sock_recv(mon->sock, &buf, false);
+        if (retval == EAGAIN) {
+            return NULL;
+        } else if (retval == ENOBUFS) {
+            VLOG_WARN_RL(&slow_rl, "network monitor socket overflowed");
+            return all_netdevs_changed(mon);
+        } else if (retval) {
+            VLOG_WARN_RL(&slow_rl, "error on network monitor socket: %s",
+                         strerror(retval));
+            return NULL;
+        } else {
+            struct nlattr *attrs[ARRAY_SIZE(rtnlgrp_link_policy)];
+            const char *name;
+
+            if (!nl_policy_parse(buf, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
+                                 rtnlgrp_link_policy,
+                                 attrs, ARRAY_SIZE(rtnlgrp_link_policy))) {
+                VLOG_WARN_RL(&slow_rl, "received bad rtnl message");
+                return all_netdevs_changed(mon);
+            }
+            name = lookup_netdev(mon, nl_attr_get_string(attrs[IFLA_IFNAME]));
+            ofpbuf_delete(buf);
+            if (name) {
+                /* Return the looked-up string instead of the attribute string,
+                 * because we freed the buffer that contains the attribute. */
+                return name;
+            }
+        }
+    }
+}
+
+void
+netdev_monitor_run(struct netdev_monitor *mon UNUSED)
+{
+    /* Nothing to do in this implementation. */
+}
+
+void
+netdev_monitor_wait(struct netdev_monitor *mon)
+{
+    nl_sock_wait(mon->sock, POLLIN);
+}
+
+static const char *
+lookup_netdev(const struct netdev_monitor *mon, const char *name)
+{
+    size_t idx = svec_find(&mon->netdevs, name);
+    return idx ? mon->netdevs.names[idx] : NULL;
+}
+
+static const char *
+pop_changed(struct netdev_monitor *mon)
+{
+    while (mon->changed.n) {
+        const char *name = lookup_netdev(mon, svec_back(&mon->changed));
+        svec_pop_back(&mon->changed);
+        if (name) {
+            return name;
+        }
+    }
+    return NULL;
+}
+
+static const char *
+all_netdevs_changed(struct netdev_monitor *mon)
+{
+    svec_clear(&mon->changed);
+    svec_append(&mon->changed, &mon->netdevs);
+    return pop_changed(mon);
+}
+\f
 static void restore_all_flags(void *aux);
 
 /* Set up a signal hook to restore network device flags on program
index 28d84321497f929d32636234d0ac5eb3bbd7d119..ae77397a99196c2a748ba1228afac9bd412b2b07 100644 (file)
  * derivatives without specific, written prior permission.
  */
 
-/* Generic interface to network devices.
- *
- * Currently, there is a single implementation of this interface that supports
- * Linux.  The interface should be generic enough to be implementable on other
- * operating systems as well. */
-
 #ifndef NETDEV_H
 #define NETDEV_H 1
 
 #include <stdbool.h>
+#include <stddef.h>
 #include <stdint.h>
 
+/* Generic interface to network devices.
+ *
+ * Currently, there is a single implementation of this interface that supports
+ * Linux.  The interface should be generic enough to be implementable on other
+ * operating systems as well. */
+
 struct ofpbuf;
 struct in_addr;
 struct in6_addr;
@@ -96,4 +97,18 @@ int netdev_arp_lookup(const struct netdev *, uint32_t ip, uint8_t mac[6]);
 void netdev_enumerate(struct svec *);
 int netdev_nodev_get_flags(const char *netdev_name, enum netdev_flags *);
 
+/* Generic interface for monitoring network devices.
+ *
+ * A network device monitor keeps track of the state of network devices and
+ * reports when that state changes.  At a minimum, it must report when a
+ * device's link status changes.
+ */
+struct netdev_monitor;
+int netdev_monitor_create(struct netdev_monitor **);
+void netdev_monitor_destroy(struct netdev_monitor *);
+void netdev_monitor_set_devices(struct netdev_monitor *, char **, size_t);
+const char *netdev_monitor_poll(struct netdev_monitor *);
+void netdev_monitor_run(struct netdev_monitor *);
+void netdev_monitor_wait(struct netdev_monitor *);
+
 #endif /* netdev.h */
index 7f7e75943c890f7d151ad11d7688b6e909f217e8..e2ac25d31bf8474f6e1ed2c4067884faf064e351 100644 (file)
@@ -202,9 +202,18 @@ svec_diff(const struct svec *a, const struct svec *b,
 bool
 svec_contains(const struct svec *svec, const char *name)
 {
+    return svec_find(svec, name) != SIZE_MAX;
+}
+
+size_t
+svec_find(const struct svec *svec, const char *name)
+{
+    char **p;
+
     assert(svec_is_sorted(svec));
-    return bsearch(&name, svec->names, svec->n, sizeof *svec->names,
-                   compare_strings) != NULL;
+    p = bsearch(&name, svec->names, svec->n, sizeof *svec->names,
+                compare_strings);
+    return p ? p - svec->names : SIZE_MAX;
 }
 
 bool
@@ -337,3 +346,17 @@ svec_join(const struct svec *svec, const char *delimiter)
     }
     return ds_cstr(&ds);
 }
+
+const char *
+svec_back(const struct svec *svec)
+{
+    assert(svec->n);
+    return svec->names[svec->n - 1];
+}
+
+void
+svec_pop_back(struct svec *svec)
+{
+    assert(svec->n);
+    free(svec->names[--svec->n]);
+}
index 68d361c4c6b24078dfa79de3f8fcf11b954ea523..0cd219229de60f8ef0c98aaa82c66dfca675ab7f 100644 (file)
@@ -58,6 +58,7 @@ void svec_unique(struct svec *);
 void svec_diff(const struct svec *a, const struct svec *b,
                struct svec *a_only, struct svec *both, struct svec *b_only);
 bool svec_contains(const struct svec *, const char *);
+size_t svec_find(const struct svec *, const char *);
 bool svec_is_sorted(const struct svec *);
 bool svec_is_unique(const struct svec *);
 const char *svec_get_duplicate(const struct svec *);
@@ -66,5 +67,7 @@ void svec_print(const struct svec *svec, const char *title);
 void svec_parse_words(struct svec *svec, const char *words);
 bool svec_equal(const struct svec *, const struct svec *);
 char *svec_join(const struct svec *, const char *delimiter);
+const char *svec_back(const struct svec *);
+void svec_pop_back(struct svec *);
 
 #endif /* svec.h */