]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
netlink: listen to netlink changes to trigger interface updates
authorVincent Bernat <bernat@luffy.cx>
Sun, 23 Dec 2012 10:11:38 +0000 (11:11 +0100)
committerVincent Bernat <bernat@luffy.cx>
Sun, 30 Dec 2012 11:28:02 +0000 (12:28 +0100)
This allows to detect "link down" or new interfaces in a timely
manner. There is still a global event loop for sending LLDPDU to each
port (no per-port sending loop).

NEWS
src/daemon/event.c
src/daemon/interfaces-linux.c
src/daemon/lldpd.c
src/daemon/lldpd.h
src/daemon/netlink.c

diff --git a/NEWS b/NEWS
index e5405f75c826d10a7755adcfed06548c1cefa83d..9df5b7f8b0ec1d005a615663e72329e48eb8d8a0 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,8 @@ lldpd (0.6.2)
     + Allow to filter debug logs using tokens. Add more debug logs.
     + lldpctl can now output JSON.
     + Use netlink to gather interface information on Linux.
+    + Detect interface changes with netlink as well on Linux and
+      trigger interface updates in a timely manner.
     + Don't use ioctl for bridges anymore on Linux. The configure
       option `--enable-oldies` allow to reenable their uses for
       systems not supporting sysfs.
index 55402e16e595c59d7ce25249f4b29434a98cd529..ecec1d37851d056a460281b92dd724a691e539bb 100644 (file)
@@ -575,3 +575,79 @@ levent_hardware_release(struct lldpd_hardware *hardware)
        }
        free(levent_hardware_fds(hardware));
 }
+
+static void
+levent_iface_trigger(evutil_socket_t fd, short what, void *arg)
+{
+       struct lldpd *cfg = arg;
+       log_info("event",
+           "triggering update of all interfaces");
+       lldpd_update_localports(cfg);
+}
+
+static void
+levent_iface_recv(evutil_socket_t fd, short what, void *arg)
+{
+       struct lldpd *cfg = arg;
+       char buffer[100];
+       int n;
+
+       /* Discard the message */
+       while (1) {
+               n = read(fd, buffer, sizeof(buffer));
+               if (n == -1 &&
+                   (errno == EWOULDBLOCK ||
+                       errno == EAGAIN)) break;
+               if (n == -1) {
+                       log_warn("event",
+                           "unable to receive interface change notification message");
+                       return;
+               }
+               if (n == 0) {
+                       log_warnx("event",
+                           "end of file reached while getting interface change notification message");
+                       return;
+               }
+       }
+
+       /* Schedule local port update. We don't run it right away because we may
+        * receive a batch of events like this. */
+       struct timeval one_sec = {1, 0};
+       log_debug("event",
+           "received notification change, schedule an update of all interfaces in one second");
+       if (cfg->g_iface_timer_event == NULL) {
+               if ((cfg->g_iface_timer_event = evtimer_new(cfg->g_base,
+                           levent_iface_trigger, cfg)) == NULL) {
+                       log_warnx("event",
+                           "unable to create a new event to trigger interface update");
+                       return;
+               }
+       }
+       if (evtimer_add(cfg->g_iface_timer_event, &one_sec) == -1) {
+               log_warnx("event",
+                   "unable to schedule interface updates");
+               return;
+       }
+}
+
+void
+levent_iface_subscribe(struct lldpd *cfg, int socket)
+{
+       log_debug("event", "subscribe to interface changes from socket %d",
+           socket);
+       evutil_make_socket_nonblocking(socket);
+       cfg->g_iface_event = event_new(cfg->g_base, socket,
+           EV_READ | EV_PERSIST, levent_iface_recv, cfg);
+       if (cfg->g_iface_event == NULL) {
+               log_warnx("event",
+                   "unable to allocate a new event for interface changes");
+               return;
+       }
+       if (event_add(cfg->g_iface_event, NULL) == -1) {
+               log_warnx("event",
+                   "unable to schedule new interface changes event");
+               event_free(cfg->g_iface_event);
+               cfg->g_iface_event = NULL;
+               return;
+       }
+}
index b96cd1379dec3aae3cb9f83aa1417a1c622d8d90..b0294d4538312180bc4cada896b444cc40e49a31 100644 (file)
@@ -848,6 +848,17 @@ interfaces_update(struct lldpd *cfg)
                iflinux_macphy(hardware);
        }
 
+       if (cfg->g_iface_event == NULL) {
+               int s;
+               log_debug("interfaces", "subscribe to netlink notifications");
+               s = netlink_subscribe_changes();
+               if (s == -1) {
+                       log_warnx("interfaces", "unable to subscribe to netlink notifications");
+                       goto end;
+               }
+               levent_iface_subscribe(cfg, s);
+       }
+
 end:
        interfaces_free_devices(interfaces);
        interfaces_free_addresses(addresses);
index 53bec842cd39769bda2a9f619cb94a71d405250e..41ec2b494ec83f20cb40cf38a7a0efac9d8dd465 100644 (file)
@@ -897,7 +897,7 @@ lldpd_update_localchassis(struct lldpd *cfg)
        }
 }
 
-static void
+void
 lldpd_update_localports(struct lldpd *cfg)
 {
        struct lldpd_hardware *hardware;
@@ -926,8 +926,10 @@ lldpd_loop(struct lldpd *cfg)
        */
        log_debug("loop", "start new loop");
        LOCAL_CHASSIS(cfg)->c_cap_enabled = 0;
-       log_debug("loop", "update information for local ports");
-       lldpd_update_localports(cfg);
+       if (cfg->g_iface_event == NULL) {
+               log_debug("loop", "update information for local ports");
+               lldpd_update_localports(cfg);
+       }
        log_debug("loop", "update information for local chassis");
        lldpd_update_localchassis(cfg);
        log_debug("loop", "send appropriate PDU on all interfaces");
index ea96dcf1cfb53f8b2103e0c93bb344338a124c45..eda04afd7e499bdb0dcc56d113fdd5eaf8c37e96 100644 (file)
@@ -108,6 +108,8 @@ struct lldpd {
        /* Unix socket handling */
        int                      g_ctl;
        struct event            *g_ctl_event;
+       struct event            *g_iface_event; /* Triggered when there is an interface change */
+       struct event            *g_iface_timer_event; /* Triggered one second after last interface change */
 
        char                    *g_lsb_release;
 
@@ -125,6 +127,8 @@ struct lldpd_mgmt *lldpd_alloc_mgmt(int family, void *addr, size_t addrsize, u_i
 void    lldpd_recv(struct lldpd *, struct lldpd_hardware *, int);
 void    lldpd_loop(struct lldpd *);
 int     lldpd_main(int, char **);
+void    lldpd_update_localports(struct lldpd *);
+
 
 /* event.c */
 void    levent_loop(struct lldpd *);
@@ -133,6 +137,8 @@ void         levent_hardware_add_fd(struct lldpd_hardware *, int);
 void    levent_hardware_release(struct lldpd_hardware *);
 void    levent_ctl_notify(char *, int, struct lldpd_port *);
 void    levent_send_now(struct lldpd *);
+void    levent_iface_subscribe(struct lldpd *, int);
+
 
 /* lldp.c */
 int     lldp_send(PROTO_SEND_SIG);
@@ -331,6 +337,7 @@ void interfaces_setup_multicast(struct lldpd *, const char *, int);
 /* netlink.c */
 struct interfaces_device_list  *netlink_get_interfaces(void);
 struct interfaces_address_list *netlink_get_addresses(void);
+int netlink_subscribe_changes(void);
 #endif
 
 #endif /* _LLDPD_H */
index 7558ebce6e2b209374fc7d48f640fb403a239279..12006f64f2b42d9e9b23c051d18e5ab3346da25e 100644 (file)
@@ -40,16 +40,17 @@ struct netlink_req {
  * Open a Netlink socket and connect to it.
  *
  * @param protocol Which protocol to use (eg NETLINK_ROUTE).
+ * @param groups   Which groups we want to subscribe to
  * @return The opened socket or -1 on error.
  */
 static int
-netlink_connect(int protocol)
+netlink_connect(int protocol, unsigned groups)
 {
     int s;
     struct sockaddr_nl local = {
         .nl_family = AF_NETLINK,
         .nl_pid = getpid(),
-        .nl_groups = 0
+        .nl_groups = groups
     };
 
     /* Open Netlink socket */
@@ -59,7 +60,7 @@ netlink_connect(int protocol)
         log_warn("netlink", "unable to open netlink socket");
         return -1;
     }
-    if (bind(s, (struct sockaddr *)&local, sizeof(struct sockaddr_nl)) < 0) {
+    if (groups && bind(s, (struct sockaddr *)&local, sizeof(struct sockaddr_nl)) < 0) {
         log_warn("netlink", "unable to bind netlink socket");
         close(s);
         return -1;
@@ -350,7 +351,7 @@ netlink_get_interfaces()
     struct interfaces_device_list *ifs;
     struct interfaces_device *iface1, *iface2;
 
-    if ((s = netlink_connect(NETLINK_ROUTE)) == -1)
+    if ((s = netlink_connect(NETLINK_ROUTE, 0)) == -1)
         return NULL;
     if (netlink_send(s, RTM_GETLINK, AF_PACKET) == -1) {
         close(s);
@@ -399,7 +400,7 @@ netlink_get_addresses()
     int s;
     struct interfaces_address_list *ifaddrs;
 
-    if ((s = netlink_connect(NETLINK_ROUTE)) == -1)
+    if ((s = netlink_connect(NETLINK_ROUTE, 0)) == -1)
         return NULL;
     if (netlink_send(s, RTM_GETADDR, AF_UNSPEC) == -1) {
         close(s);
@@ -418,3 +419,15 @@ netlink_get_addresses()
     close(s);
     return ifaddrs;
 }
+
+/**
+ * Subscribe to link changes.
+ *
+ * @return The socket we should listen to for changes.
+ */
+int
+netlink_subscribe_changes()
+{
+    log_debug("netlink", "listening on interface changes");
+    return netlink_connect(NETLINK_ROUTE, RTMGRP_LINK);
+}