diff --git a/lib/automake.mk b/lib/automake.mk index 54b3c60e7..d2980d38f 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -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 \ diff --git a/lib/dpif-linux.c b/lib/dpif-linux.c index 4090e572b..417349d8d 100644 --- a/lib/dpif-linux.c +++ b/lib/dpif-linux.c @@ -24,14 +24,17 @@ #include #include #include +#include #include #include #include #include #include "dpif-provider.h" +#include "netdev-linux.h" #include "ofpbuf.h" #include "poll-loop.h" +#include "svec.h" #include "util.h" #include "vlog.h" @@ -41,15 +44,23 @@ 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; + 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); + name = xasprintf("dp%d", minor); + dpif_init(&dpif->dpif, &dpif_linux_class, name, minor, minor); + free(name); - dpif = xmalloc(sizeof *dpif); - dpif_init(&dpif->dpif, &dpif_linux_class, name, minor, minor); - dpif->fd = fd; - *dpifp = &dpif->dpif; - - 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); + } + } +} diff --git a/lib/dpif-provider.h b/lib/dpif-provider.h index 020d06708..35f6c744c 100644 --- a/lib/dpif-provider.h +++ b/lib/dpif-provider.h @@ -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. */ diff --git a/lib/dpif.c b/lib/dpif.c index 8c33736b0..d2ef845da 100644 --- a/lib/dpif.c +++ b/lib/dpif.c @@ -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]); } } - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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); -} diff --git a/lib/dpif.h b/lib/dpif.h index 213bd2bcd..aa4c1a4b7 100644 --- a/lib/dpif.h +++ b/lib/dpif.h @@ -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); - -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 index 000000000..c753e28a2 --- /dev/null +++ b/lib/netdev-linux.c @@ -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 + +#include "netdev-linux.h" + +#include +#include +#include +#include + +#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, ¬ifier->node); + notifier->error = 0; + notifier->cb = cb; + notifier->aux = aux; + return 0; +} + +void +linux_netdev_notifier_unregister(struct linux_netdev_notifier *notifier) +{ + list_remove(¬ifier->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 index 000000000..93ddfcb63 --- /dev/null +++ b/lib/netdev-linux.h @@ -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 */ diff --git a/lib/netdev.c b/lib/netdev.c index 3d93e37e2..796583ece 100644 --- a/lib/netdev.c +++ b/lib/netdev.c @@ -46,11 +46,13 @@ #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; } +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); + } +} + static void restore_all_flags(void *aux); /* Set up a signal hook to restore network device flags on program diff --git a/lib/netdev.h b/lib/netdev.h index d128f3f32..de312d62d 100644 --- a/lib/netdev.h +++ b/lib/netdev.h @@ -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 */ diff --git a/lib/vlog-modules.def b/lib/vlog-modules.def index 2653781b9..5fbb59041 100644 --- a/lib/vlog-modules.def +++ b/lib/vlog-modules.def @@ -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) diff --git a/secchan/ofproto.c b/secchan/ofproto.c index 285684617..44d1a8508 100644 --- a/secchan/ofproto.c +++ b/secchan/ofproto.c @@ -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));