Introduce general-purpose ways to wait for dpif and netdev changes.
authorBen Pfaff <blp@nicira.com>
Wed, 24 Jun 2009 17:24:09 +0000 (10:24 -0700)
committerBen Pfaff <blp@nicira.com>
Mon, 6 Jul 2009 16:07:24 +0000 (09:07 -0700)
The dpif and netdev code has had various ways to check for changes to
dpifs and netdevs over the course of Open vSwitch development.  All of
these have been thus far fairly specific to the Linux implementation.  This
commit is the start of a more general API for watching for such changes.
The dpif-related parts seem fairly mature and so they are documented,
the netdev parts will probably need to change somewhat and so they are
not documented yet.

lib/automake.mk
lib/dpif-linux.c
lib/dpif-provider.h
lib/dpif.c
lib/dpif.h
lib/netdev-linux.c [new file with mode: 0644]
lib/netdev-linux.h [new file with mode: 0644]
lib/netdev.c
lib/netdev.h
lib/vlog-modules.def
secchan/ofproto.c

index 54b3c60..d2980d3 100644 (file)
@@ -56,6 +56,8 @@ lib_libopenvswitch_a_SOURCES = \
        lib/list.h \
        lib/mac-learning.c \
        lib/mac-learning.h \
+       lib/netdev-linux.c \
+       lib/netdev-linux.h \
        lib/netdev.c \
        lib/netdev.h \
        lib/odp-util.c \
@@ -174,6 +176,7 @@ COVERAGE_FILES = \
        lib/hmap.c \
        lib/mac-learning.c \
        lib/netdev.c \
+       lib/netdev-linux.c \
        lib/netlink.c \
        lib/odp-util.c \
        lib/poll-loop.c \
index 4090e57..417349d 100644 (file)
 #include <inttypes.h>
 #include <net/if.h>
 #include <linux/ethtool.h>
+#include <linux/rtnetlink.h>
 #include <linux/sockios.h>
 #include <stdlib.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 
 #include "dpif-provider.h"
+#include "netdev-linux.h"
 #include "ofpbuf.h"
 #include "poll-loop.h"
+#include "svec.h"
 #include "util.h"
 
 #include "vlog.h"
 struct dpif_linux {
     struct dpif dpif;
     int fd;
+
+    /* Change notification. */
+    int local_ifindex;          /* Ifindex of local port. */
+    struct svec changed_ports;  /* Ports that have changed. */
+    struct linux_netdev_notifier port_notifier;
 };
 
 static struct vlog_rate_limit error_rl = VLOG_RATE_LIMIT_INIT(9999, 5);
 
 static int do_ioctl(const struct dpif *, int cmd, const void *arg);
 static int lookup_minor(const char *name, int *minor);
+static int finish_open(struct dpif *, const char *local_ifname);
 static int create_minor(const char *name, int minor, struct dpif **dpifp);
 static int open_minor(int minor, struct dpif **dpifp);
 static int make_openvswitch_device(int minor, char **fnp);
+static void dpif_linux_port_changed(const struct linux_netdev_change *,
+                                    void *dpif);
 
 static struct dpif_linux *
 dpif_linux_cast(const struct dpif *dpif)
@@ -58,6 +69,18 @@ dpif_linux_cast(const struct dpif *dpif)
     return CONTAINER_OF(dpif, struct dpif_linux, dpif);
 }
 
+static void
+dpif_linux_run(void)
+{
+    linux_netdev_notifier_run();
+}
+
+static void
+dpif_linux_wait(void)
+{
+    linux_netdev_notifier_wait();
+}
+
 static int
 dpif_linux_open(const char *name UNUSED, char *suffix, bool create,
                 struct dpif **dpifp)
@@ -82,7 +105,7 @@ dpif_linux_open(const char *name UNUSED, char *suffix, bool create,
         }
     } else {
         struct dpif_linux *dpif;
-        int listen_mask;
+        struct odp_port port;
         int error;
 
         if (minor < 0) {
@@ -98,19 +121,22 @@ dpif_linux_open(const char *name UNUSED, char *suffix, bool create,
         }
         dpif = dpif_linux_cast(*dpifp);
 
-        /* We can open the device, but that doesn't mean that it's been
-         * created.  If it hasn't been, then any command other than
-         * ODP_DP_CREATE will return ENODEV.  Try something innocuous. */
-        listen_mask = 0;            /* Make Valgrind happy. */
-        error = do_ioctl(*dpifp, ODP_GET_LISTEN_MASK, &listen_mask);
-        if (error) {
+        /* We need the local port's ifindex for the poll function.  Start by
+         * getting the local port's name. */
+        memset(&port, 0, sizeof port);
+        port.port = ODPP_LOCAL;
+        if (ioctl(dpif->fd, ODP_PORT_QUERY, &port)) {
+            error = errno;
             if (error != ENODEV) {
                 VLOG_WARN("%s: probe returned unexpected error: %s",
                           dpif_name(*dpifp), strerror(error));
             }
             dpif_close(*dpifp);
+            return error;
         }
-        return error;
+
+        /* Then use that to finish up opening. */
+        return finish_open(&dpif->dpif, port.devname);
     }
 }
 
@@ -118,6 +144,8 @@ static void
 dpif_linux_close(struct dpif *dpif_)
 {
     struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+    linux_netdev_notifier_unregister(&dpif->port_notifier);
+    svec_destroy(&dpif->changed_ports);
     close(dpif->fd);
     free(dpif);
 }
@@ -214,6 +242,36 @@ dpif_linux_port_list(const struct dpif *dpif_, struct odp_port *ports, int n)
     return error ? -error : pv.n_ports;
 }
 
+static int
+dpif_linux_port_poll(const struct dpif *dpif_, char **devnamep)
+{
+    struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+    int error;
+
+    error = linux_netdev_notifier_get_error(&dpif->port_notifier);
+    if (!error) {
+        if (!dpif->changed_ports.n) {
+            return EAGAIN;
+        }
+        *devnamep = dpif->changed_ports.names[--dpif->changed_ports.n];
+    } else {
+        svec_clear(&dpif->changed_ports);
+    }
+    return error;
+}
+
+static void
+dpif_linux_port_poll_wait(const struct dpif *dpif_)
+{
+    struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+    if (dpif->changed_ports.n
+        || linux_netdev_notifier_peek_error(&dpif->port_notifier)) {
+        poll_immediate_wake();
+    } else {
+        linux_netdev_notifier_wait();
+    }
+}
+
 static int
 dpif_linux_port_group_get(const struct dpif *dpif_, int group,
                           uint16_t ports[], int n)
@@ -355,8 +413,8 @@ dpif_linux_recv_wait(struct dpif *dpif_)
 const struct dpif_class dpif_linux_class = {
     "",                         /* This is the default class. */
     "linux",
-    NULL,                       /* run */
-    NULL,                       /* wait */
+    dpif_linux_run,
+    dpif_linux_wait,
     dpif_linux_open,
     dpif_linux_close,
     dpif_linux_delete,
@@ -368,6 +426,8 @@ const struct dpif_class dpif_linux_class = {
     dpif_linux_port_query_by_number,
     dpif_linux_port_query_by_name,
     dpif_linux_port_list,
+    dpif_linux_port_poll,
+    dpif_linux_port_poll_wait,
     dpif_linux_port_group_get,
     dpif_linux_port_group_set,
     dpif_linux_flow_get,
@@ -557,13 +617,30 @@ error:
     return default_major;
 }
 
+static int
+finish_open(struct dpif *dpif_, const char *local_ifname)
+{
+    struct dpif_linux *dpif = dpif_linux_cast(dpif_);
+    dpif->local_ifindex = if_nametoindex(local_ifname);
+    if (!dpif->local_ifindex) {
+        int error = errno;
+        dpif_close(dpif_);
+        VLOG_WARN("could not get ifindex of %s device: %s",
+                  local_ifname, strerror(errno));
+        return error;
+    }
+    return 0;
+}
+
 static int
 create_minor(const char *name, int minor, struct dpif **dpifp)
 {
     int error = open_minor(minor, dpifp);
     if (!error) {
         error = do_ioctl(*dpifp, ODP_DP_CREATE, name);
-        if (error) {
+        if (!error) {
+            error = finish_open(*dpifp, name);
+        } else {
             dpif_close(*dpifp);
         }
     }
@@ -584,17 +661,23 @@ open_minor(int minor, struct dpif **dpifp)
 
     fd = open(fn, O_RDONLY | O_NONBLOCK);
     if (fd >= 0) {
-        struct dpif_linux *dpif;
-        char *name;
-
-        name = xasprintf("dp%d", minor);
-
-        dpif = xmalloc(sizeof *dpif);
-        dpif_init(&dpif->dpif, &dpif_linux_class, name, minor, minor);
-        dpif->fd = fd;
-        *dpifp = &dpif->dpif;
-
-        free(name);
+        struct dpif_linux *dpif = xmalloc(sizeof *dpif);
+        error = linux_netdev_notifier_register(&dpif->port_notifier,
+                                               dpif_linux_port_changed, dpif);
+        if (!error) {
+            char *name;
+
+            name = xasprintf("dp%d", minor);
+            dpif_init(&dpif->dpif, &dpif_linux_class, name, minor, minor);
+            free(name);
+
+            dpif->fd = fd;
+            dpif->local_ifindex = 0;
+            svec_init(&dpif->changed_ports);
+            *dpifp = &dpif->dpif;
+        } else {
+            free(dpif);
+        }
     } else {
         error = errno;
         VLOG_WARN("%s: open failed (%s)", fn, strerror(error));
@@ -603,3 +686,21 @@ open_minor(int minor, struct dpif **dpifp)
 
     return error;
 }
+
+static void
+dpif_linux_port_changed(const struct linux_netdev_change *change, void *dpif_)
+{
+    struct dpif_linux *dpif = dpif_;
+
+    if (change->master_ifindex == dpif->local_ifindex
+        && (change->nlmsg_type == RTM_NEWLINK
+            || change->nlmsg_type == RTM_DELLINK))
+    {
+        /* Our datapath changed, either adding a new port or deleting an
+         * existing one. */
+        if (!svec_contains(&dpif->changed_ports, change->ifname)) {
+            svec_add(&dpif->changed_ports, change->ifname);
+            svec_sort(&dpif->changed_ports);
+        }
+    }
+}
index 020d067..35f6c74 100644 (file)
@@ -126,6 +126,30 @@ struct dpif_class {
      * value. */
     int (*port_list)(const struct dpif *dpif, struct odp_port *ports, int n);
 
+    /* Polls for changes in the set of ports in 'dpif'.  If the set of ports in
+     * 'dpif' has changed, then this function should do one of the
+     * following:
+     *
+     * - Preferably: store the name of the device that was added to or deleted
+     *   from 'dpif' in '*devnamep' and return 0.  The caller is responsible
+     *   for freeing '*devnamep' (with free()) when it no longer needs it.
+     *
+     * - Alternatively: return ENOBUFS, without indicating the device that was
+     *   added or deleted.
+     *
+     * Occasional 'false positives', in which the function returns 0 while
+     * indicating a device that was not actually added or deleted or returns
+     * ENOBUFS without any change, are acceptable.
+     *
+     * If the set of ports in 'dpif' has not changed, returns EAGAIN.  May also
+     * return other positive errno values to indicate that something has gone
+     * wrong. */
+    int (*port_poll)(const struct dpif *dpif, char **devnamep);
+
+    /* Arranges for the poll loop to wake up when 'port_poll' will return a
+     * value other than EAGAIN. */
+    void (*port_poll_wait)(const struct dpif *dpif);
+
     /* Stores in 'ports' the port numbers of up to 'n' ports that belong to
      * 'group' in 'dpif'.  Returns the number of ports in 'group' (not the
      * number stored), if successful, otherwise a negative errno value. */
index 8c33736..d2ef845 100644 (file)
@@ -383,6 +383,40 @@ exit:
     return error;
 }
 
+/* Polls for changes in the set of ports in 'dpif'.  If the set of ports in
+ * 'dpif' has changed, this function does one of the following:
+ *
+ * - Stores the name of the device that was added to or deleted from 'dpif' in
+ *   '*devnamep' and returns 0.  The caller is responsible for freeing
+ *   '*devnamep' (with free()) when it no longer needs it.
+ *
+ * - Returns ENOBUFS and sets '*devnamep' to NULL.
+ *
+ * This function may also return 'false positives', where it returns 0 and
+ * '*devnamep' names a device that was not actually added or deleted or it
+ * returns ENOBUFS without any change.
+ *
+ * Returns EAGAIN if the set of ports in 'dpif' has not changed.  May also
+ * return other positive errno values to indicate that something has gone
+ * wrong. */
+int
+dpif_port_poll(const struct dpif *dpif, char **devnamep)
+{
+    int error = dpif->class->port_poll(dpif, devnamep);
+    if (error) {
+        *devnamep = NULL;
+    }
+    return error;
+}
+
+/* Arranges for the poll loop to wake up when port_poll(dpif) will return a
+ * value other than EAGAIN. */
+void
+dpif_port_poll_wait(const struct dpif *dpif)
+{
+    dpif->class->port_poll_wait(dpif);
+}
+
 /* Retrieves a list of the port numbers in port group 'group' in 'dpif'.
  *
  * On success, returns 0 and points '*ports' to a newly allocated array of
@@ -932,145 +966,3 @@ check_rw_odp_flow(struct odp_flow *flow)
         memset(&flow->actions[0], 0xcc, sizeof flow->actions[0]);
     }
 }
-\f
-#include <net/if.h>
-#include <linux/rtnetlink.h>
-#include <linux/ethtool.h>
-#include <linux/sockios.h>
-#include <netinet/in.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/sysmacros.h>
-#include <unistd.h>
-
-struct dpifmon {
-    struct dpif *dpif;
-    struct nl_sock *sock;
-    int local_ifindex;
-};
-
-int
-dpifmon_create(const char *datapath_name, struct dpifmon **monp)
-{
-    struct dpifmon *mon;
-    char local_name[IFNAMSIZ];
-    int error;
-
-    mon = *monp = xmalloc(sizeof *mon);
-
-    error = dpif_open(datapath_name, &mon->dpif);
-    if (error) {
-        goto error;
-    }
-    error = dpif_port_get_name(mon->dpif, ODPP_LOCAL,
-                               local_name, sizeof local_name);
-    if (error) {
-        goto error_close_dpif;
-    }
-
-    mon->local_ifindex = if_nametoindex(local_name);
-    if (!mon->local_ifindex) {
-        error = errno;
-        VLOG_WARN("could not get ifindex of %s device: %s",
-                  local_name, strerror(errno));
-        goto error_close_dpif;
-    }
-
-    error = nl_sock_create(NETLINK_ROUTE, RTNLGRP_LINK, 0, 0, &mon->sock);
-    if (error) {
-        VLOG_WARN("could not create rtnetlink socket: %s", strerror(error));
-        goto error_close_dpif;
-    }
-
-    return 0;
-
-error_close_dpif:
-    dpif_close(mon->dpif);
-error:
-    free(mon);
-    *monp = NULL;
-    return error;
-}
-
-void
-dpifmon_destroy(struct dpifmon *mon)
-{
-    if (mon) {
-        dpif_close(mon->dpif);
-        nl_sock_destroy(mon->sock);
-    }
-}
-
-int
-dpifmon_poll(struct dpifmon *mon, char **devnamep)
-{
-    static struct vlog_rate_limit slow_rl = VLOG_RATE_LIMIT_INIT(1, 5);
-    static const struct nl_policy rtnlgrp_link_policy[] = {
-        [IFLA_IFNAME] = { .type = NL_A_STRING },
-        [IFLA_MASTER] = { .type = NL_A_U32, .optional = true },
-    };
-    struct nlattr *attrs[ARRAY_SIZE(rtnlgrp_link_policy)];
-    struct ofpbuf *buf;
-    int error;
-
-    *devnamep = NULL;
-again:
-    error = nl_sock_recv(mon->sock, &buf, false);
-    switch (error) {
-    case 0:
-        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");
-            error = ENOBUFS;
-        } else {
-            const char *devname = nl_attr_get_string(attrs[IFLA_IFNAME]);
-            bool for_us;
-
-            if (attrs[IFLA_MASTER]) {
-                uint32_t master_ifindex = nl_attr_get_u32(attrs[IFLA_MASTER]);
-                for_us = master_ifindex == mon->local_ifindex;
-            } else {
-                /* It's for us if that device is one of our ports. */
-                struct odp_port port;
-                for_us = !dpif_port_query_by_name(mon->dpif, devname, &port);
-            }
-
-            if (!for_us) {
-                /* Not for us, try again. */
-                ofpbuf_delete(buf);
-                COVERAGE_INC(dpifmon_poll_false_wakeup);
-                goto again;
-            }
-            COVERAGE_INC(dpifmon_poll_changed);
-            *devnamep = xstrdup(devname);
-        }
-        ofpbuf_delete(buf);
-        break;
-
-    case EAGAIN:
-        /* Nothing to do. */
-        break;
-
-    case ENOBUFS:
-        VLOG_WARN_RL(&slow_rl, "dpifmon socket overflowed");
-        break;
-
-    default:
-        VLOG_WARN_RL(&slow_rl, "error on dpifmon socket: %s", strerror(error));
-        break;
-    }
-    return error;
-}
-
-void
-dpifmon_run(struct dpifmon *mon UNUSED)
-{
-    /* Nothing to do in this implementation. */
-}
-
-void
-dpifmon_wait(struct dpifmon *mon)
-{
-    nl_sock_wait(mon->sock, POLLIN);
-}
index 213bd2b..aa4c1a4 100644 (file)
@@ -56,6 +56,9 @@ int dpif_port_get_name(struct dpif *, uint16_t port_no,
                        char *name, size_t name_size);
 int dpif_port_list(const struct dpif *, struct odp_port **, size_t *n_ports);
 
+int dpif_port_poll(const struct dpif *, char **devnamep);
+void dpif_port_poll_wait(const struct dpif *);
+
 int dpif_port_group_get(const struct dpif *, uint16_t group,
                         uint16_t **ports, size_t *n_ports);
 int dpif_port_group_set(struct dpif *, uint16_t group,
@@ -83,15 +86,5 @@ void dpif_recv_wait(struct dpif *);
 
 void dpif_get_netflow_ids(const struct dpif *,
                           uint8_t *engine_type, uint8_t *engine_id);
-\f
-struct dpifmon;
-
-int dpifmon_create(const char *datapath_name, struct dpifmon **);
-void dpifmon_destroy(struct dpifmon *);
-
-int dpifmon_poll(struct dpifmon *, char **devnamep);
-
-void dpifmon_run(struct dpifmon *);
-void dpifmon_wait(struct dpifmon *);
 
 #endif /* dpif.h */
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
new file mode 100644 (file)
index 0000000..c753e28
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "netdev-linux.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <linux/rtnetlink.h>
+#include <poll.h>
+
+#include "coverage.h"
+#include "netlink.h"
+#include "ofpbuf.h"
+
+#define THIS_MODULE VLM_netdev_linux
+#include "vlog.h"
+
+/* rtnetlink socket. */
+static struct nl_sock *rtnl_sock;
+
+/* All registered notifiers. */
+static struct list all_notifiers = LIST_INITIALIZER(&all_notifiers);
+
+static void linux_netdev_report_change(const struct nlmsghdr *,
+                                       const struct ifinfomsg *,
+                                       struct nlattr *attrs[]);
+static void linux_netdev_report_notify_error(int error);
+
+int
+linux_netdev_notifier_register(struct linux_netdev_notifier *notifier,
+                               linux_netdev_notify_func *cb, void *aux)
+{
+    if (!rtnl_sock) {
+        int error = nl_sock_create(NETLINK_ROUTE, RTNLGRP_LINK, 0, 0,
+                                   &rtnl_sock);
+        if (error) {
+            VLOG_WARN("could not create rtnetlink socket: %s",
+                      strerror(error));
+            return error;
+        }
+    } else {
+        /* Catch up on notification work so that the new notifier won't
+         * receive any stale notifications. */
+        linux_netdev_notifier_run();
+    }
+
+    list_push_back(&all_notifiers, &notifier->node);
+    notifier->error = 0;
+    notifier->cb = cb;
+    notifier->aux = aux;
+    return 0;
+}
+
+void
+linux_netdev_notifier_unregister(struct linux_netdev_notifier *notifier)
+{
+    list_remove(&notifier->node);
+    if (list_is_empty(&all_notifiers)) {
+        nl_sock_destroy(rtnl_sock);
+        rtnl_sock = NULL;
+    }
+}
+
+int
+linux_netdev_notifier_get_error(struct linux_netdev_notifier *notifier)
+{
+    int error = notifier->error;
+    notifier->error = 0;
+    return error;
+}
+
+int
+linux_netdev_notifier_peek_error(const struct linux_netdev_notifier *notifier)
+{
+    return notifier->error;
+}
+
+static const struct nl_policy rtnlgrp_link_policy[] = {
+    [IFLA_IFNAME] = { .type = NL_A_STRING },
+    [IFLA_MASTER] = { .type = NL_A_U32, .optional = true },
+};
+
+void
+linux_netdev_notifier_run(void)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+    if (!rtnl_sock) {
+        return;
+    }
+
+    for (;;) {
+        struct nlattr *attrs[ARRAY_SIZE(rtnlgrp_link_policy)];
+        struct ofpbuf *buf;
+        int error;
+
+        error = nl_sock_recv(rtnl_sock, &buf, false);
+        if (!error) {
+            if (nl_policy_parse(buf, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
+                                rtnlgrp_link_policy,
+                                attrs, ARRAY_SIZE(rtnlgrp_link_policy))) {
+                struct ifinfomsg *ifinfo;
+
+                ifinfo = (void *) ((char *) buf->data + NLMSG_HDRLEN);
+                linux_netdev_report_change(buf->data, ifinfo, attrs);
+            } else {
+                VLOG_WARN_RL(&rl, "received bad rtnl message");
+                linux_netdev_report_notify_error(ENOBUFS);
+            }
+            ofpbuf_delete(buf);
+        } else if (error == EAGAIN) {
+            return;
+        } else {
+            if (error == ENOBUFS) {
+                VLOG_WARN_RL(&rl, "rtnetlink receive buffer overflowed");
+            } else {
+                VLOG_WARN_RL(&rl, "error reading rtnetlink socket: %s",
+                             strerror(error));
+            }
+            linux_netdev_report_notify_error(error);
+        }
+    }
+}
+
+void
+linux_netdev_notifier_wait(void)
+{
+    if (rtnl_sock) {
+        nl_sock_wait(rtnl_sock, POLLIN);
+    }
+}
+
+static void
+linux_netdev_report_change(const struct nlmsghdr *nlmsg,
+                           const struct ifinfomsg *ifinfo,
+                           struct nlattr *attrs[])
+{
+    struct linux_netdev_notifier *notifier;
+    struct linux_netdev_change change;
+
+    COVERAGE_INC(linux_netdev_changed);
+
+    change.nlmsg_type = nlmsg->nlmsg_type;
+    change.ifi_index = ifinfo->ifi_index;
+    change.ifname = nl_attr_get_string(attrs[IFLA_IFNAME]);
+    change.master_ifindex = (attrs[IFLA_MASTER]
+                             ? nl_attr_get_u32(attrs[IFLA_MASTER]) : 0);
+
+    LIST_FOR_EACH (notifier, struct linux_netdev_notifier, node,
+                   &all_notifiers) {
+        if (!notifier->error) {
+            notifier->cb(&change, notifier->aux);
+        }
+    }
+}
+
+static void
+linux_netdev_report_notify_error(int error)
+{
+    struct linux_netdev_notifier *notifier;
+
+    LIST_FOR_EACH (notifier, struct linux_netdev_notifier, node,
+                   &all_notifiers) {
+        if (error != ENOBUFS || !notifier->error) {
+            notifier->error = error;
+        }
+    }
+}
diff --git a/lib/netdev-linux.h b/lib/netdev-linux.h
new file mode 100644 (file)
index 0000000..93ddfcb
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NETDEV_LINUX_H
+#define NETDEV_LINUX_H 1
+
+/* These functions are specific to the Linux implementation of dpif and netdev.
+ * They should only be used directly by Linux-specific code. */
+
+#include "list.h"
+
+struct linux_netdev_change {
+    /* Copied from struct nlmsghdr. */
+    int nlmsg_type;             /* e.g. RTM_NEWLINK, RTM_DELLINK. */
+
+    /* Copied from struct ifinfomsg. */
+    int ifi_index;              /* Index of network device. */
+
+    /* Extracted from Netlink attributes. */
+    const char *ifname;         /* Name of network device. */
+    int master_ifindex;         /* Ifindex of datapath master (0 if none). */
+};
+
+typedef void linux_netdev_notify_func(const struct linux_netdev_change *,
+                                      void *aux);
+
+struct linux_netdev_notifier {
+    struct list node;
+    int error;
+    linux_netdev_notify_func *cb;
+    void *aux;
+};
+
+int linux_netdev_notifier_register(struct linux_netdev_notifier *,
+                                   linux_netdev_notify_func *, void *aux);
+void linux_netdev_notifier_unregister(struct linux_netdev_notifier *);
+int linux_netdev_notifier_get_error(struct linux_netdev_notifier *);
+int linux_netdev_notifier_peek_error(const struct linux_netdev_notifier *);
+void linux_netdev_notifier_run(void);
+void linux_netdev_notifier_wait(void);
+
+#endif /* netdev-linux.h */
index 3d93e37..796583e 100644 (file)
 #include "dynamic-string.h"
 #include "fatal-signal.h"
 #include "list.h"
+#include "netdev-linux.h"
 #include "netlink.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
 #include "packets.h"
 #include "poll-loop.h"
+#include "shash.h"
 #include "socket-util.h"
 #include "svec.h"
 
@@ -1368,6 +1370,106 @@ done:
     return error;
 }
 \f
+struct netdev_monitor {
+    struct linux_netdev_notifier notifier;
+    struct shash polled_netdevs;
+    struct shash changed_netdevs;
+};
+
+static void netdev_monitor_change(const struct linux_netdev_change *change,
+                                  void *monitor);
+
+int
+netdev_monitor_create(struct netdev_monitor **monitorp)
+{
+    struct netdev_monitor *monitor;
+    int error;
+
+    monitor = xmalloc(sizeof *monitor);
+    error = linux_netdev_notifier_register(&monitor->notifier,
+                                           netdev_monitor_change, monitor);
+    if (error) {
+        free(monitor);
+        return error;
+    }
+    shash_init(&monitor->polled_netdevs);
+    shash_init(&monitor->changed_netdevs);
+    *monitorp = monitor;
+    return 0;
+}
+
+void
+netdev_monitor_destroy(struct netdev_monitor *monitor)
+{
+    if (monitor) {
+        linux_netdev_notifier_unregister(&monitor->notifier);
+        shash_destroy(&monitor->polled_netdevs);
+        free(monitor);
+    }
+}
+
+void
+netdev_monitor_add(struct netdev_monitor *monitor, struct netdev *netdev)
+{
+    if (!shash_find(&monitor->polled_netdevs, netdev_get_name(netdev))) {
+        shash_add(&monitor->polled_netdevs, netdev_get_name(netdev), NULL);
+    }
+}
+
+void
+netdev_monitor_remove(struct netdev_monitor *monitor, struct netdev *netdev)
+{
+    struct shash_node *node;
+
+    node = shash_find(&monitor->polled_netdevs, netdev_get_name(netdev));
+    if (node) {
+        shash_delete(&monitor->polled_netdevs, node);
+        node = shash_find(&monitor->changed_netdevs, netdev_get_name(netdev));
+        if (node) {
+            shash_delete(&monitor->changed_netdevs, node);
+        }
+    }
+}
+
+int
+netdev_monitor_poll(struct netdev_monitor *monitor, char **devnamep)
+{
+    int error = linux_netdev_notifier_get_error(&monitor->notifier);
+    *devnamep = NULL;
+    if (!error) {
+        struct shash_node *node = shash_first(&monitor->changed_netdevs);
+        if (!node) {
+            return EAGAIN;
+        }
+        *devnamep = xstrdup(node->name);
+        shash_delete(&monitor->changed_netdevs, node);
+    } else {
+        shash_clear(&monitor->changed_netdevs);
+    }
+    return error;
+}
+
+void
+netdev_monitor_poll_wait(const struct netdev_monitor *monitor)
+{
+    if (!shash_is_empty(&monitor->changed_netdevs)
+        || linux_netdev_notifier_peek_error(&monitor->notifier)) {
+        poll_immediate_wake();
+    } else {
+        linux_netdev_notifier_wait();
+    }
+}
+
+static void
+netdev_monitor_change(const struct linux_netdev_change *change, void *monitor_)
+{
+    struct netdev_monitor *monitor = monitor_;
+    if (shash_find(&monitor->polled_netdevs, change->ifname)
+        && !shash_find(&monitor->changed_netdevs, change->ifname)) {
+        shash_add(&monitor->changed_netdevs, change->ifname, NULL);
+    }
+}
+\f
 static void restore_all_flags(void *aux);
 
 /* Set up a signal hook to restore network device flags on program
index d128f3f..de312d6 100644 (file)
@@ -114,4 +114,12 @@ int netdev_nodev_get_carrier(const char *netdev_name, bool *carrier);
 
 int netdev_get_vlan_vid(const char *netdev_name, int *vlan_vid);
 
+struct netdev_monitor;
+int netdev_monitor_create(struct netdev_monitor **);
+void netdev_monitor_destroy(struct netdev_monitor *);
+void netdev_monitor_add(struct netdev_monitor *, struct netdev *);
+void netdev_monitor_remove(struct netdev_monitor *, struct netdev *);
+int netdev_monitor_poll(struct netdev_monitor *, char **devnamep);
+void netdev_monitor_poll_wait(const struct netdev_monitor *);
+
 #endif /* netdev.h */
index 2653781..5fbb590 100644 (file)
@@ -43,6 +43,7 @@ VLOG_MODULE(learning_switch)
 VLOG_MODULE(mac_learning)
 VLOG_MODULE(mgmt)
 VLOG_MODULE(netdev)
+VLOG_MODULE(netdev_linux)
 VLOG_MODULE(netflow)
 VLOG_MODULE(netlink)
 VLOG_MODULE(ofctl)
index 2856846..44d1a85 100644 (file)
@@ -193,7 +193,7 @@ struct ofproto {
 
     /* Datapath. */
     struct dpif *dpif;
-    struct dpifmon *dpifmon;
+    struct netdev_monitor *netdev_monitor;
     struct port_array ports;    /* Index is ODP port nr; ofport->opp.port_no is
                                  * OFP port nr. */
     struct shash port_by_name;
@@ -259,7 +259,7 @@ int
 ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
                struct ofproto **ofprotop)
 {
-    struct dpifmon *dpifmon;
+    struct netdev_monitor *netdev_monitor;
     struct odp_stats stats;
     struct ofproto *p;
     struct dpif *dpif;
@@ -290,8 +290,8 @@ ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
     dpif_flow_flush(dpif);
     dpif_recv_purge(dpif);
 
-    /* Start monitoring datapath ports for status changes. */
-    error = dpifmon_create(datapath, &dpifmon);
+    /* Arrange to monitor datapath ports for status changes. */
+    error = netdev_monitor_create(&netdev_monitor);
     if (error) {
         VLOG_ERR("failed to starting monitoring datapath %s: %s",
                  datapath, strerror(error));
@@ -311,7 +311,7 @@ ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
 
     /* Initialize datapath. */
     p->dpif = dpif;
-    p->dpifmon = dpifmon;
+    p->netdev_monitor = netdev_monitor;
     port_array_init(&p->ports);
     shash_init(&p->port_by_name);
     p->max_ports = stats.max_ports;
@@ -706,7 +706,7 @@ ofproto_destroy(struct ofproto *p)
     }
 
     dpif_close(p->dpif);
-    dpifmon_destroy(p->dpifmon);
+    netdev_monitor_destroy(p->netdev_monitor);
     PORT_ARRAY_FOR_EACH (ofport, &p->ports, port_no) {
         ofport_free(ofport);
     }
@@ -748,6 +748,17 @@ ofproto_run(struct ofproto *p)
     return error;
 }
 
+static void
+process_port_change(struct ofproto *ofproto, int error, char *devname)
+{
+    if (error == ENOBUFS) {
+        reinit_ports(ofproto);
+    } else if (!error) {
+        update_port(ofproto, devname);
+        free(devname);
+    }
+}
+
 int
 ofproto_run1(struct ofproto *p)
 {
@@ -777,13 +788,12 @@ ofproto_run1(struct ofproto *p)
         handle_odp_msg(p, buf);
     }
 
-    while ((error = dpifmon_poll(p->dpifmon, &devname)) != EAGAIN) {
-        if (error == ENOBUFS) {
-            reinit_ports(p);
-        } else if (!error) {
-            update_port(p, devname);
-            free(devname);
-        }
+    while ((error = dpif_port_poll(p->dpif, &devname)) != EAGAIN) {
+        process_port_change(p, error, devname);
+    }
+    while ((error = netdev_monitor_poll(p->netdev_monitor,
+                                        &devname)) != EAGAIN) {
+        process_port_change(p, error, devname);
     }
 
     if (p->in_band) {
@@ -896,7 +906,8 @@ ofproto_wait(struct ofproto *p)
     size_t i;
 
     dpif_recv_wait(p->dpif);
-    dpifmon_wait(p->dpifmon);
+    dpif_port_poll_wait(p->dpif);
+    netdev_monitor_poll_wait(p->netdev_monitor);
     LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) {
         ofconn_wait(ofconn);
     }
@@ -1178,6 +1189,7 @@ send_port_status(struct ofproto *p, const struct ofport *ofport,
 static void
 ofport_install(struct ofproto *p, struct ofport *ofport)
 {
+    netdev_monitor_add(p->netdev_monitor, ofport->netdev);
     port_array_set(&p->ports, ofp_port_to_odp_port(ofport->opp.port_no),
                    ofport);
     shash_add(&p->port_by_name, (char *) ofport->opp.name, ofport);
@@ -1186,6 +1198,7 @@ ofport_install(struct ofproto *p, struct ofport *ofport)
 static void
 ofport_remove(struct ofproto *p, struct ofport *ofport)
 {
+    netdev_monitor_remove(p->netdev_monitor, ofport->netdev);
     port_array_set(&p->ports, ofp_port_to_odp_port(ofport->opp.port_no), NULL);
     shash_delete(&p->port_by_name,
                  shash_find(&p->port_by_name, (char *) ofport->opp.name));