From 0484f180fafdf488e8bc13f8a0ee616421f12e57 Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Sun, 23 Dec 2012 11:11:38 +0100 Subject: [PATCH] netlink: listen to netlink changes to trigger interface updates 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 | 2 + src/daemon/event.c | 76 +++++++++++++++++++++++++++++++++++ src/daemon/interfaces-linux.c | 11 +++++ src/daemon/lldpd.c | 8 ++-- src/daemon/lldpd.h | 7 ++++ src/daemon/netlink.c | 23 ++++++++--- 6 files changed, 119 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index e5405f75..9df5b7f8 100644 --- 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. diff --git a/src/daemon/event.c b/src/daemon/event.c index 55402e16..ecec1d37 100644 --- a/src/daemon/event.c +++ b/src/daemon/event.c @@ -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; + } +} diff --git a/src/daemon/interfaces-linux.c b/src/daemon/interfaces-linux.c index b96cd137..b0294d45 100644 --- a/src/daemon/interfaces-linux.c +++ b/src/daemon/interfaces-linux.c @@ -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); diff --git a/src/daemon/lldpd.c b/src/daemon/lldpd.c index 53bec842..41ec2b49 100644 --- a/src/daemon/lldpd.c +++ b/src/daemon/lldpd.c @@ -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"); diff --git a/src/daemon/lldpd.h b/src/daemon/lldpd.h index ea96dcf1..eda04afd 100644 --- a/src/daemon/lldpd.h +++ b/src/daemon/lldpd.h @@ -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 */ diff --git a/src/daemon/netlink.c b/src/daemon/netlink.c index 7558ebce..12006f64 100644 --- a/src/daemon/netlink.c +++ b/src/daemon/netlink.c @@ -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); +} -- 2.39.5