From: Vincent Bernat Date: Sun, 16 Aug 2015 23:19:52 +0000 (+0200) Subject: netlink: use libnl3 instead of custom netlink code X-Git-Tag: 0.8.0~101 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=13181ede3ff5189e53eaf10db8021c5b7f09db4c;p=thirdparty%2Flldpd.git netlink: use libnl3 instead of custom netlink code The main goal of this change is to implement a caching system for netlink and avoiding the full scan done when a change was triggered. Implementing a netlink cache is not as funny as it seems and therefore, it seems just better to use libnl3. Licensing issues are explained in README. People concerned with that should just use dynamic linking. An embedded copy of libnl3 is also provided, just like libevent. --- diff --git a/.gitmodules b/.gitmodules index 2119f124..ec1560ac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libevent"] path = libevent url = https://github.com/libevent/libevent.git +[submodule "libnl"] + path = libnl + url = https://github.com/thom311/libnl.git diff --git a/NEWS b/NEWS index 52e8ca0d..7a997aae 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,8 @@ +lldpd (0.8.0) + * Change: + + For Linux, switch to libnl3. Be aware of the licensing issues in + case of static linking. + lldpd (0.7.16) * Change: + For Linux, 2.6.32 is now the minimal required kernel. When using diff --git a/README.md b/README.md index b4e4d117..fae20942 100644 --- a/README.md +++ b/README.md @@ -263,3 +263,7 @@ lldpd is distributed under the ISC license: Also, `lldpcli` will be linked to GNU Readline (which is GPL licensed) if available. To avoid this, use `--without-readline` as a configure option. + +libnl is LGPL licensed. To avoid any licensing issue, be sure to use +dynamic linking (`--with-embedded-libnl=no`). If you use static +linking, ensure that you understand the license implication of LGPL. diff --git a/configure.ac b/configure.ac index e1ddb011..0de82744 100644 --- a/configure.ac +++ b/configure.ac @@ -164,8 +164,9 @@ AC_CACHE_SAVE ## Unit tests wich check PKG_CHECK_MODULES([CHECK], [check >= 0.9.4], [have_check=yes], [have_check=no]) -# Libevent +# Third-party libraries lldp_CHECK_LIBEVENT +lldp_CHECK_LIBNL # Compatibility with pkg.m4 < 0.27 m4_ifdef([PKG_INSTALLDIR], [PKG_INSTALLDIR], @@ -319,6 +320,11 @@ if test x"$LIBEVENT_EMBEDDED" = x; then else libevent=embedded fi +if test x"$LIBNL_EMBEDDED" = x; then + libnl="system (if needed)" +else + libnl=embedded +fi cat <= 5), libxml2-dev, libjansson-dev | libjson-c-dev | libjson0-dev (>= 0.10), libevent-dev, + libnl-3-dev, libreadline-dev, libbsd-dev, pkg-config diff --git a/debian/copyright b/debian/copyright index 2eec55cd..cda00136 100644 --- a/debian/copyright +++ b/debian/copyright @@ -18,6 +18,111 @@ License: ISC ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +Files: libnl/* +Copyright: Copyright (c) Thomas Graf + Baruch Even + Petr Gotthard + Siemens AG Oesterreich + Philip Craig + Patrick McHardy + Secure Computing Corporation +License: LGPL-2.1 + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation version 2.1 of the License. + . + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + . + On Debian GNU/Linux systems, the complete text of the GNU Lesser General + Public License can be found in /usr/share/common-licenses/LGPL-2.1 +Comment: + The content of this directory is shipped with lldpd but not used for + compilation. The system libnl3 is used in place of this embedded + copy. + +Files: libnl/src/idiag-socket-details.c + libnl/src/lib/utils.c + libnl/src/nl-addr-add.c + libnl/src/nl-addr-delete.c + libnl/src/nl-addr-list.c + libnl/src/nl-cls-add.c + libnl/src/nl-addr-add.c +Copyright: (c) 2013 Sassano Systems LLC + (c) 2003-2009 Thomas Graf +License: GPL-2 +Comment: + Those files are userland tools part of libnl-3. They are never + compiled. + +Files: libnl/include/linux-private/linux/* +Copyright: 1991-2012 Linus Torvalds and many others +License: GPL-2 +Comment: + It is believed that header files are an interface for user space and + therefore cannot be covered by copyright. + +License: GPL-2 + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + . + On Debian systems, the complete text of the GNU General Public + License version 2 can be found in `/usr/share/common-licenses/GPL-2'. + +Files: libnl/include/netlink/xfrm/selector.h + libnl/include/netlink/xfrm/sa.h + libnl/include/netlink/xfrm/ae.h + libnl/include/netlink/xfrm/sp.h + libnl/include/netlink/xfrm/template.h + libnl/include/netlink/xfrm/lifetime.h + libnl/lib/xfrm/sa.c + libnl/lib/xfrm/template.c + libnl/lib/xfrm/ae.c + libnl/lib/xfrm/sp.c + libnl/lib/xfrm/selector.c + libnl/lib/xfrm/lifetime.c +Copyright: Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ +License: BSD-3-clause + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + . + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + . + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + . + Neither the name of Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Files: libevent/* Copyright: Copyright 2000-2007 Niels Provos Copyright 2007-2012 Niels Provos and Nick Mathewson diff --git a/libnl b/libnl new file mode 160000 index 00000000..8f5c65be --- /dev/null +++ b/libnl @@ -0,0 +1 @@ +Subproject commit 8f5c65beee10598eb3264494963773e7b44a85e5 diff --git a/m4/libnl3.m4 b/m4/libnl3.m4 new file mode 100644 index 00000000..efe6e07b --- /dev/null +++ b/m4/libnl3.m4 @@ -0,0 +1,46 @@ +# +# lldp_CHECK_LIBNL +# + +AC_DEFUN([lldp_CHECK_LIBNL], [ + # Do we require embedded libnl? + AC_ARG_WITH([embedded-libnl], + AS_HELP_STRING( + [--with-embedded-libnl], + [Use embedded libnl @<:@default=auto@:>@] + ), [], [with_embedded_libnl=auto]) + if test x"$with_embedded_libnl" = x"yes"; then + LIBNL_EMBEDDED=1 + else + # Check with pkg-config (3.2.7 is needed for nl_cache_mngr_alloc) + PKG_CHECK_MODULES([LIBNL], [libnl >= 3.2.7 libnl-route >= 3.2.7], [], [ + # No appropriate version, let's use the shipped copy if possible + if test x"$with_embedded_libnl" = x"auto"; then + AC_MSG_NOTICE([using shipped libnl]) + LIBNL_EMBEDDED=1 + else + AC_MSG_ERROR([*** libnl not found]) + fi + ]) + fi + + if test x"$LIBNL_EMBEDDED" != x; then + unset LIBNL_LIBS + LIBNL_CFLAGS="-I\$(top_srcdir)/libnl/include -I\$(top_builddir)/libnl/include" + LIBNL_LDFLAGS="\$(top_builddir)/libnl/lib/libnl-3.la \$(top_builddir)/libnl/lib/libnl-route-3.la" + fi + + # Call ./configure in libnl. Need it for make dist... + libnl_configure_args="$libnl_configure_args --disable-pthreads" + libnl_configure_args="$libnl_configure_args --disable-cli" + libnl_configure_args="$libnl_configure_args --disable-debug" + libnl_configure_args="$libnl_configure_args --disable-shared" + libnl_configure_args="$libnl_configure_args --with-pic" + libnl_configure_args="$libnl_configure_args --enable-static" + lldp_CONFIG_SUBDIRS([libnl], [$libnl_configure_args]) + + AM_CONDITIONAL([LIBNL_EMBEDDED], [test x"$LIBNL_EMBEDDED" != x]) + AC_SUBST([LIBNL_LIBS]) + AC_SUBST([LIBNL_CFLAGS]) + AC_SUBST([LIBNL_LDFLAGS]) +]) diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am index 71de01af..4fb515f5 100644 --- a/src/daemon/Makefile.am +++ b/src/daemon/Makefile.am @@ -33,6 +33,11 @@ liblldpd_la_LIBADD = \ $(top_builddir)/src/libcommon-daemon-client.la \ $(top_builddir)/src/libcommon-daemon-lib.la @LIBEVENT_LIBS@ +## lldpd +lldpd_SOURCES = main.c +lldpd_LDFLAGS = $(AM_LDFLAGS) $(LLDP_BIN_LDFLAGS) +lldpd_LDADD = liblldpd.la @LIBEVENT_LDFLAGS@ + if HOST_OS_LINUX liblldpd_la_SOURCES += \ forward-linux.c \ @@ -40,6 +45,9 @@ liblldpd_la_SOURCES += \ netlink.c \ dmi-linux.c \ priv-linux.c +liblldpd_la_LIBADD += @LIBNL_LIBS@ +liblldpd_la_CFLAGS += @LIBNL_CFLAGS@ +lldpd_LDADD += @LIBNL_LDFLAGS@ endif if HOST_OS_DRAGONFLY liblldpd_la_SOURCES += \ @@ -94,11 +102,6 @@ liblldpd_la_SOURCES += \ priv-bsd.c endif -## lldpd -lldpd_SOURCES = main.c -lldpd_LDFLAGS = $(AM_LDFLAGS) $(LLDP_BIN_LDFLAGS) -lldpd_LDADD = liblldpd.la @LIBEVENT_LDFLAGS@ - # Add SNMP support if needed if USE_SNMP liblldpd_la_SOURCES += agent.c agent_priv.c agent.h @@ -148,6 +151,15 @@ $(top_builddir)/libevent/libevent.la: $(top_srcdir)/libevent/*.c $(top_srcdir)/l (cd $(top_builddir)/libevent && $(MAKE)) endif +## libnl +if HOST_OS_LINUX +if LIBNL_EMBEDDED +netlink.c: $(top_builddir)/libnl/lib/libnl-3.la $(top_builddir)/libnl/lib/libnl-route-3.la +$(top_builddir)/libnl/lib/libnl-3.la $(top_builddir)/libnl/lib/libnl-route-3.la: $(top_srcdir)/libnl/lib/*.c $(top_srcdir)/libnl/lib/route/*.c + (cd $(top_builddir)/libnl && $(MAKE)) +endif +endif + ## systemd service file if HAVE_SYSTEMDSYSTEMUNITDIR systemdsystemunit_DATA = lldpd.service diff --git a/src/daemon/event.c b/src/daemon/event.c index 3ee8f769..50f7977f 100644 --- a/src/daemon/event.c +++ b/src/daemon/event.c @@ -657,22 +657,26 @@ levent_iface_recv(evutil_socket_t fd, short what, void *arg) char buffer[EVENT_BUFFER]; 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; + if (cfg->g_iface_cb == NULL) { + /* 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; + } } + } else { + cfg->g_iface_cb(cfg); } /* Schedule local port update. We don't run it right away because we may diff --git a/src/daemon/interfaces-bsd.c b/src/daemon/interfaces-bsd.c index 7db99cf7..4084d53b 100644 --- a/src/daemon/interfaces-bsd.c +++ b/src/daemon/interfaces-bsd.c @@ -685,3 +685,8 @@ end: interfaces_free_addresses(addresses); if (ifaddrs) freeifaddrs(ifaddrs); } + +void +interfaces_cleanup(struct lldpd *) +{ +} diff --git a/src/daemon/interfaces-linux.c b/src/daemon/interfaces-linux.c index c9342d48..30e4b8e2 100644 --- a/src/daemon/interfaces-linux.c +++ b/src/daemon/interfaces-linux.c @@ -782,8 +782,8 @@ interfaces_update(struct lldpd *cfg) struct lldpd_hardware *hardware; struct interfaces_device_list *interfaces; struct interfaces_address_list *addresses; - interfaces = netlink_get_interfaces(); - addresses = netlink_get_addresses(); + interfaces = netlink_get_interfaces(cfg); + addresses = netlink_get_addresses(cfg); if (interfaces == NULL || addresses == NULL) { log_warnx("interfaces", "cannot update the list of local interfaces"); goto end; @@ -817,21 +817,13 @@ interfaces_update(struct lldpd *cfg) interfaces_helper_promisc(cfg, 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; - } - if (levent_iface_subscribe(cfg, s) == -1) - close(s); - /* coverity[leaked_handle] - s has been saved by levent_iface_subscribe */ - } - end: interfaces_free_devices(interfaces); interfaces_free_addresses(addresses); } + +void +interfaces_cleanup(struct lldpd *cfg) +{ + netlink_cleanup(cfg); +} diff --git a/src/daemon/interfaces-solaris.c b/src/daemon/interfaces-solaris.c index 9a4f0049..f6f67639 100644 --- a/src/daemon/interfaces-solaris.c +++ b/src/daemon/interfaces-solaris.c @@ -184,3 +184,8 @@ end: interfaces_free_devices(interfaces); interfaces_free_addresses(addresses); } + +void +interfaces_cleanup(struct lldpd *) +{ +} diff --git a/src/daemon/lldpd.c b/src/daemon/lldpd.c index 876fcc90..f32cd05b 100644 --- a/src/daemon/lldpd.c +++ b/src/daemon/lldpd.c @@ -1154,6 +1154,9 @@ lldpd_exit(struct lldpd *cfg) lldpd_remote_cleanup(hardware, NULL, 1); lldpd_hardware_cleanup(cfg, hardware); } + interfaces_cleanup(cfg); + + free(cfg->g_config.c_platform); } /** diff --git a/src/daemon/lldpd.h b/src/daemon/lldpd.h index fded75f4..cf6f2fa9 100644 --- a/src/daemon/lldpd.h +++ b/src/daemon/lldpd.h @@ -97,37 +97,7 @@ struct protocol { #define SMART_HIDDEN(port) (port->p_hidden_in) -struct lldpd { - int g_sock; - struct event_base *g_base; -#ifdef USE_SNMP -#endif - - struct lldpd_config g_config; - - struct protocol *g_protocols; - int g_lastrid; - struct event *g_main_loop; - struct event *g_cleanup_timer; -#ifdef USE_SNMP - int g_snmp; - struct event *g_snmp_timeout; - void *g_snmp_fds; - const char *g_snmp_agentx; -#endif /* USE_SNMP */ - - /* Unix socket handling */ - const char *g_ctlname; - int g_ctl; - 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; - -#define LOCAL_CHASSIS(cfg) ((struct lldpd_chassis *)(TAILQ_FIRST(&cfg->g_chassis))) - TAILQ_HEAD(, lldpd_chassis) g_chassis; - TAILQ_HEAD(, lldpd_hardware) g_hardware; -}; +struct lldpd; /* lldpd.c */ struct lldpd_hardware *lldpd_get_hardware(struct lldpd *, @@ -398,12 +368,14 @@ int interfaces_send_helper(struct lldpd *, void interfaces_setup_multicast(struct lldpd *, const char *, int); int interfaces_routing_enabled(struct lldpd *); +void interfaces_cleanup(struct lldpd *); #ifdef HOST_OS_LINUX /* netlink.c */ -struct interfaces_device_list *netlink_get_interfaces(void); -struct interfaces_address_list *netlink_get_addresses(void); -int netlink_subscribe_changes(void); +struct interfaces_device_list *netlink_get_interfaces(struct lldpd *); +struct interfaces_address_list *netlink_get_addresses(struct lldpd *); +void netlink_cleanup(struct lldpd *); +struct lldpd_netlink; #endif #ifndef HOST_OS_LINUX @@ -413,4 +385,41 @@ int ifbpf_phys_init(struct lldpd *, struct lldpd_hardware *); /* pattern.c */ int pattern_match(char *, char *, int); +struct lldpd { + int g_sock; + struct event_base *g_base; +#ifdef USE_SNMP +#endif + + struct lldpd_config g_config; + + struct protocol *g_protocols; + int g_lastrid; + struct event *g_main_loop; + struct event *g_cleanup_timer; +#ifdef USE_SNMP + int g_snmp; + struct event *g_snmp_timeout; + void *g_snmp_fds; + const char *g_snmp_agentx; +#endif /* USE_SNMP */ + + /* Unix socket handling */ + const char *g_ctlname; + int g_ctl; + 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 */ + void(*g_iface_cb)(struct lldpd *); /* Called when there is an interface change */ + + char *g_lsb_release; + +#ifdef HOST_OS_LINUX + struct lldpd_netlink *g_netlink; +#endif + +#define LOCAL_CHASSIS(cfg) ((struct lldpd_chassis *)(TAILQ_FIRST(&cfg->g_chassis))) + TAILQ_HEAD(, lldpd_chassis) g_chassis; + TAILQ_HEAD(, lldpd_hardware) g_hardware; +}; + #endif /* _LLDPD_H */ diff --git a/src/daemon/netlink.c b/src/daemon/netlink.c index 0e3dba8a..694e4e34 100644 --- a/src/daemon/netlink.c +++ b/src/daemon/netlink.c @@ -19,387 +19,209 @@ #include "lldpd.h" -#include -#include -#include +#include #include -#include #include -#include - -#define NETLINK_BUFFER 4096 - -struct netlink_req { - struct nlmsghdr hdr; - struct rtgenmsg gen; +#include +#include +#include +#include +#include + +struct lldpd_netlink { + struct nl_cache_mngr *mngr; + struct nl_cache *addr; + struct nl_cache *link; }; /** - * Connect to netlink. - * - * 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. + * Callback when we get netlink updates. */ -static int -netlink_connect(int protocol, unsigned groups) +static void +netlink_change_cb(struct lldpd *cfg) { - int s; - struct sockaddr_nl local = { - .nl_family = AF_NETLINK, - .nl_pid = getpid(), - .nl_groups = groups - }; - - /* Open Netlink socket */ - log_debug("netlink", "opening netlink socket"); - s = socket(AF_NETLINK, SOCK_RAW, protocol); - if (s == -1) { - log_warn("netlink", "unable to open netlink socket"); - return -1; - } - if (groups && bind(s, (struct sockaddr *)&local, sizeof(struct sockaddr_nl)) < 0) { - log_warn("netlink", "unable to bind netlink socket"); - close(s); - return -1; - } - return s; + int err; + log_debug("netlink", "netlink update received"); + if ((err = nl_cache_mngr_data_ready(cfg->g_netlink->mngr)) < 0) { + log_warn("netlink", "unable to parse incoming netlink messages: %s", + nl_geterror(err)); + } } /** - * Send a netlink message. + * Initialize netlink subsystem. * - * The type of the message can be chosen as well the route family. The - * mesage will always be NLM_F_REQUEST | NLM_F_DUMP. + * This can be called several times but will have effect only the first time. * - * @param s the netlink socket - * @param type the request type (eg RTM_GETLINK) - * @param family the rt family (eg AF_PACKET) * @return 0 on success, -1 otherwise */ static int -netlink_send(int s, int type, int family) +netlink_initialize(struct lldpd *cfg) { - struct netlink_req req = { - .hdr = { - .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)), - .nlmsg_type = type, - .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, - .nlmsg_seq = 1, - .nlmsg_pid = getpid() }, - .gen = { .rtgen_family = family } - }; - struct iovec iov = { - .iov_base = &req, - .iov_len = req.hdr.nlmsg_len - }; - struct sockaddr_nl peer = { .nl_family = AF_NETLINK }; - struct msghdr rtnl_msg = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_name = &peer, - .msg_namelen = sizeof(struct sockaddr_nl) - }; - - /* Send netlink message. This is synchronous but we are guaranteed - * to not block. */ - log_debug("netlink", "sending netlink message"); - if (sendmsg(s, (struct msghdr *)&rtnl_msg, 0) == -1) { - log_warn("netlink", "unable to send netlink message"); - return -1; - } - - return 0; -} + int err; + if (cfg->g_netlink) return 0; -static void -netlink_parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) -{ - while (RTA_OK(rta, len)) { - if ((rta->rta_type <= max) && (!tb[rta->rta_type])) - tb[rta->rta_type] = rta; - rta = RTA_NEXT(rta,len); + log_debug("netlink", "initialize netlink subsystem"); + if ((cfg->g_netlink = calloc(sizeof(struct lldpd_netlink), 1)) == NULL) { + log_warn("netlink", "unable to allocate memory for netlink subsystem"); + goto end; } -} -/** - * Parse a `linkinfo` attributes. - * - * @param iff where to put the result - * @param rta linkinfo attribute - * @param len length of attributes - */ -static void -netlink_parse_linkinfo(struct interfaces_device *iff, struct rtattr *rta, int len) -{ - struct rtattr *link_info_attrs[IFLA_INFO_MAX+1] = {}; - char *kind = NULL; - - netlink_parse_rtattr(link_info_attrs, IFLA_INFO_MAX, rta, len); - - if (link_info_attrs[IFLA_INFO_KIND]) { - kind = strdup(RTA_DATA(link_info_attrs[IFLA_INFO_KIND])); - if (kind) { - if (!strcmp(kind, "vlan")) { - log_debug("netlink", "interface %s is a VLAN", - iff->name); - iff->type |= IFACE_VLAN_T; - } else if (!strcmp(kind, "bridge")) { - log_debug("netlink", "interface %s is a bridge", - iff->name); - iff->type |= IFACE_BRIDGE_T; - } else if (!strcmp(kind, "bond")) { - log_debug("netlink", "interface %s is a bond", - iff->name); - iff->type |= IFACE_BOND_T; - } - } + if ((err = nl_cache_mngr_alloc(NULL, NETLINK_ROUTE, NL_AUTO_PROVIDE, + &cfg->g_netlink->mngr)) < 0) { + log_warn("netlink", "unable to allocate cache manager: %s", + nl_geterror(err)); + goto end; } - if (kind && !strcmp(kind, "vlan") && link_info_attrs[IFLA_INFO_DATA]) { - struct rtattr *vlan_link_info_data_attrs[IFLA_VLAN_MAX+1] = {}; - netlink_parse_rtattr(vlan_link_info_data_attrs, IFLA_VLAN_MAX, - RTA_DATA(link_info_attrs[IFLA_INFO_DATA]), - RTA_PAYLOAD(link_info_attrs[IFLA_INFO_DATA])); + if ((err = nl_cache_mngr_add(cfg->g_netlink->mngr, + "route/link", NULL, NULL, &cfg->g_netlink->link)) < 0) { + log_warn("netlink", "unable to allocate route/link cache"); + goto end; + } + if ((err = nl_cache_mngr_add(cfg->g_netlink->mngr, + "route/addr", NULL, NULL, &cfg->g_netlink->addr)) < 0) { + log_warn("netlink", "unable to allocate route/addr cache"); + goto end; + } - if (vlan_link_info_data_attrs[IFLA_VLAN_ID]) { - iff->vlanid = *(uint16_t *)RTA_DATA(vlan_link_info_data_attrs[IFLA_VLAN_ID]); - log_debug("netlink", "VLAN ID for interface %s is %d", - iff->name, iff->vlanid); - } + cfg->g_iface_cb = netlink_change_cb; + if (levent_iface_subscribe(cfg, nl_cache_mngr_get_fd(cfg->g_netlink->mngr)) == -1) { + goto end; } - free(kind); + return 0; +end: + netlink_cleanup(cfg); + return -1; } /** - * Parse a `link` netlink message. - * - * @param msg message to be parsed - * @param iff where to put the result - * return 0 if the interface is worth it, -1 otherwise + * Cleanup netlink subsystem. */ -static int -netlink_parse_link(struct nlmsghdr *msg, - struct interfaces_device *iff) +void +netlink_cleanup(struct lldpd *cfg) { - struct ifinfomsg *ifi; - struct rtattr *attribute; - int len; - ifi = NLMSG_DATA(msg); - len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg)); - - if (!((ifi->ifi_flags & IFF_UP) && (ifi->ifi_flags & IFF_RUNNING))) { - log_debug("netlink", "skip down interface at index %d", - ifi->ifi_index); - return -1; - } - if (ifi->ifi_type != ARPHRD_ETHER) { - log_debug("netlink", "skip non Ethernet interface at index %d", - ifi->ifi_index); - return -1; - } - - iff->index = ifi->ifi_index; - iff->flags = ifi->ifi_flags; - iff->lower_idx = -1; - iff->upper_idx = -1; - - for (attribute = IFLA_RTA(ifi); - RTA_OK(attribute, len); - attribute = RTA_NEXT(attribute, len)) { - switch(attribute->rta_type) { - case IFLA_IFNAME: - /* Interface name */ - iff->name = strdup(RTA_DATA(attribute)); - break; - case IFLA_IFALIAS: - /* Interface alias */ - iff->alias = strdup(RTA_DATA(attribute)); - break; - case IFLA_ADDRESS: - /* Interface MAC address */ - iff->address = malloc(RTA_PAYLOAD(attribute)); - if (iff->address) - memcpy(iff->address, RTA_DATA(attribute), RTA_PAYLOAD(attribute)); - break; - case IFLA_LINK: - /* Index of "lower" interface */ - iff->lower_idx = *(int*)RTA_DATA(attribute); - break; - case IFLA_MASTER: - /* Index of master interface */ - iff->upper_idx = *(int*)RTA_DATA(attribute); - break; - case IFLA_TXQLEN: - /* Transmit queue length */ - iff->txqueue = *(int*)RTA_DATA(attribute); - break; - case IFLA_MTU: - /* Maximum Transmission Unit */ - iff->mtu = *(int*)RTA_DATA(attribute); - break; - case IFLA_LINKINFO: - netlink_parse_linkinfo(iff, RTA_DATA(attribute), RTA_PAYLOAD(attribute)); - break; - default: - log_debug("netlink", "unhandled link attribute type %d for iface %s", - attribute->rta_type, iff->name ? iff->name : "(unknown)"); - break; - } - } - if (!iff->name || !iff->address) { - log_info("netlink", "interface %d does not have a name or an address, skip", - iff->index); - return -1; - } - return 0; + if (cfg->g_netlink == NULL) return; + if (cfg->g_netlink->mngr != NULL) nl_cache_mngr_free(cfg->g_netlink->mngr); + + free(cfg->g_netlink); + cfg->g_netlink = NULL; } /** - * Parse a `address` netlink message. + * Parse a `link` netlink message. * - * @param msg message to be parsed - * @param ifa where to put the result - * return 0 if the address is worth it, -1 otherwise + * @param link link object from cache + * @return parsed interface */ -static int -netlink_parse_address(struct nlmsghdr *msg, - struct interfaces_address *ifa) +static struct interfaces_device * +netlink_parse_link(struct rtnl_link *link) { - struct ifaddrmsg *ifi; - struct rtattr *attribute; - int len; - ifi = NLMSG_DATA(msg); - len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifaddrmsg)); - - ifa->index = ifi->ifa_index; - ifa->flags = ifi->ifa_flags; - switch (ifi->ifa_family) { - case AF_INET: - case AF_INET6: break; - default: - log_debug("netlink", "got a non IP address on if %d (family: %d)", - ifa->index, ifi->ifa_family); - return -1; - } - - for (attribute = IFA_RTA(ifi); - RTA_OK(attribute, len); - attribute = RTA_NEXT(attribute, len)) { - switch(attribute->rta_type) { - case IFA_ADDRESS: - /* Address */ - if (ifi->ifa_family == AF_INET) { - struct sockaddr_in ip = { .sin_family = AF_INET }; - memcpy(&ip.sin_addr, RTA_DATA(attribute), - sizeof(struct in_addr)); - memcpy(&ifa->address, &ip, sizeof(struct sockaddr_in)); - } else { - struct sockaddr_in6 ip6 = { .sin6_family = AF_INET6 }; - memcpy(&ip6.sin6_addr, RTA_DATA(attribute), - sizeof(struct in6_addr)); - memcpy(&ifa->address, &ip6, sizeof(struct sockaddr_in6)); - } - break; - default: - log_debug("netlink", "unhandled address attribute type %d for iface %d", - attribute->rta_type, ifa->index); - break; - } - } - if (ifa->address.ss_family == AF_UNSPEC) { - log_debug("netlink", "no IP for interface %d", - ifa->index); - return -1; - } - return 0; + const char *name = rtnl_link_get_name(link); + if (name == NULL) { + log_debug("netlink", "skip unnamed interface"); + return NULL; + } + + unsigned int flags = rtnl_link_get_flags(link); + if (!((flags & IFF_UP) && (flags & IFF_RUNNING))) { + log_debug("netlink", "skip down interface %s", name); + return NULL; + } + if (rtnl_link_get_arptype(link) != ARPHRD_ETHER) { + log_debug("netlink", "skip non Ethernet interface %s", name); + return NULL; + } + + struct interfaces_device *iff = calloc(1, sizeof(struct interfaces_device)); + if (iff == NULL) { + log_warn("netlink", "no memory for a new interface"); + return NULL; + } + iff->index = rtnl_link_get_ifindex(link);; + iff->flags = flags; + iff->lower_idx = rtnl_link_get_link(link); + iff->upper_idx = rtnl_link_get_master(link); + iff->name = strdup(name); + if (rtnl_link_get_ifalias(link) != NULL) + iff->alias = strdup(rtnl_link_get_ifalias(link)); + + struct nl_addr *mac = rtnl_link_get_addr(link); + if (mac) { + iff->address = malloc(nl_addr_get_len(mac)); + if (iff->address) + memcpy(iff->address, + nl_addr_get_binary_addr(mac), + nl_addr_get_len(mac)); + } + if (!iff->address) { + log_info("netlink", "interface %d does not have a name or an address, skip", + iff->index); + interfaces_free_device(iff); + return NULL; + } + iff->txqueue = rtnl_link_get_txqlen(link); + iff->mtu = rtnl_link_get_mtu(link); + + const char *kind = rtnl_link_get_type(link); + if (kind) { + if (!strcmp(kind, "vlan")) { + iff->type |= IFACE_VLAN_T; + iff->vlanid = rtnl_link_vlan_get_id(link); + log_debug("netlink", "interface %s is a VLAN (id=%d)", + name, iff->vlanid); + } else if (!strcmp(kind, "bridge")) { + iff->type |= IFACE_BRIDGE_T; + log_debug("netlink", "interface %s is a bridge", + name); + } else if (!strcmp(kind, "bond")) { + iff->type |= IFACE_BOND_T; + log_debug("netlink", "interface %s is a bond", + name); + } + } + + return iff; } /** - * Receive netlink answer from the kernel. + * Parse a `address` netlink message. * - * @param s the netlink socket - * @param ifs list to store interface list or NULL if we don't - * @param ifas list to store address list or NULL if we don't - * @return 0 on success, -1 on error + * @param addr address object from cache + * @return parsed address */ -static int -netlink_recv(int s, - struct interfaces_device_list *ifs, - struct interfaces_address_list *ifas) +static struct interfaces_address * +netlink_parse_address(struct rtnl_addr *addr) { - char reply[NETLINK_BUFFER] __attribute__ ((aligned)); - int end = 0; - - struct interfaces_device *iff; - struct interfaces_address *ifa; - - while (!end) { - int len; - struct nlmsghdr *msg; - struct iovec iov = { - .iov_base = reply, - .iov_len = NETLINK_BUFFER - }; - struct sockaddr_nl peer = { .nl_family = AF_NETLINK }; - struct msghdr rtnl_reply = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_name = &peer, - .msg_namelen = sizeof(struct sockaddr_nl) - }; - - len = recvmsg(s, &rtnl_reply, 0); - if (len == -1) { - log_warnx("netlink", "unable to receive netlink answer"); - return -1; - } - if (!len) return 0; - for (msg = (struct nlmsghdr*)(void*)reply; - NLMSG_OK(msg, len); - msg = NLMSG_NEXT(msg, len)) { - switch (msg->nlmsg_type) { - case NLMSG_DONE: - log_debug("netlink", "received end of dump message"); - end = 1; - break; - case RTM_NEWLINK: - if (!ifs) break; - log_debug("netlink", "received link information"); - iff = calloc(1, sizeof(struct interfaces_device)); - if (iff == NULL) { - log_warn("netlink", "not enough memory for another interface, give what we have"); - return 0; - } - if (netlink_parse_link(msg, iff) == 0) - TAILQ_INSERT_TAIL(ifs, iff, next); - else - interfaces_free_device(iff); - break; - case RTM_NEWADDR: - if (!ifas) break; - log_debug("netlink", "received address information"); - ifa = calloc(1, sizeof(struct interfaces_address)); - if (ifa == NULL) { - log_warn("netlink", "not enough memory for another address, give what we have"); - return 0; - } - if (netlink_parse_address(msg, ifa) == 0) - TAILQ_INSERT_TAIL(ifas, ifa, next); - else - interfaces_free_address(ifa); - break; - default: - log_debug("netlink", - "received unhandled message type %d (len: %d)", - msg->nlmsg_type, msg->nlmsg_len); - } - } - } - return 0; + int family = rtnl_addr_get_family(addr); + switch (family) { + case AF_INET: + case AF_INET6: break; + default: + log_debug("netlink", "got a non IP address on if %d (family: %d)", + rtnl_addr_get_ifindex(addr), family); + return NULL; + } + + struct interfaces_address *ifa = calloc(1, sizeof(struct interfaces_address)); + if (ifa == NULL) { + log_warn("netlink", "no memory for a new address"); + return NULL; + } + ifa->index = rtnl_addr_get_ifindex(addr); + ifa->flags = rtnl_addr_get_flags(addr); + + socklen_t len = sizeof(ifa->address); + int err = nl_addr_fill_sockaddr(rtnl_addr_get_local(addr), + (struct sockaddr *)&ifa->address, &len); + if (err < 0 || ifa->address.ss_family == AF_UNSPEC) { + log_debug("netlink", "no IP for interface %d", + ifa->index); + interfaces_free_address(ifa); + return NULL; + } + return ifa; } /** @@ -408,49 +230,52 @@ netlink_recv(int s, * @return a list of interfaces. */ struct interfaces_device_list* -netlink_get_interfaces() +netlink_get_interfaces(struct lldpd *cfg) { - int s; - struct interfaces_device_list *ifs; - struct interfaces_device *iface1, *iface2; - - if ((s = netlink_connect(NETLINK_ROUTE, 0)) == -1) - return NULL; - if (netlink_send(s, RTM_GETLINK, AF_PACKET) == -1) { - close(s); - return NULL; - } - - log_debug("netlink", "get the list of available interfaces"); - ifs = malloc(sizeof(struct interfaces_device_list)); - if (ifs == NULL) { - log_warn("netlink", "not enough memory for interface list"); - close(s); - return NULL; - } - TAILQ_INIT(ifs); - netlink_recv(s, ifs, NULL); - - /* Fill out lower/upper */ - TAILQ_FOREACH(iface1, ifs, next) { - if (iface1->upper_idx != -1 && iface1->upper_idx != iface1->index) - TAILQ_FOREACH(iface2, ifs, next) { - if (iface1->upper_idx == iface2->index) { - iface1->upper = iface2; - break; - } - } - if (iface1->lower_idx != -1 && iface1->lower_idx != iface1->index) - TAILQ_FOREACH(iface2, ifs, next) { - if (iface1->lower_idx == iface2->index) { - iface1->lower = iface2; - break; - } - } - } - - close(s); - return ifs; + if (netlink_initialize(cfg) == -1) return NULL; + + struct interfaces_device_list *ifs; + + log_debug("netlink", "get the list of available interfaces"); + ifs = malloc(sizeof(struct interfaces_device_list)); + if (ifs == NULL) { + log_warn("netlink", "not enough memory for interface list"); + return NULL; + } + TAILQ_INIT(ifs); + + for (struct nl_object *link = nl_cache_get_first(cfg->g_netlink->link); + link != NULL; + link = nl_cache_get_next(link)) { + nl_object_get(link); + struct interfaces_device *iff = netlink_parse_link((struct rtnl_link *)link); + if (iff) TAILQ_INSERT_TAIL(ifs, iff, next); + nl_object_put(link); + } + + struct interfaces_device *iface1, *iface2; + TAILQ_FOREACH(iface1, ifs, next) { + if (iface1->upper_idx != 0 && iface1->upper_idx != iface1->index) + TAILQ_FOREACH(iface2, ifs, next) { + if (iface1->upper_idx == iface2->index) { + log_debug("netlink", "%s is upper iface for %s", + iface2->name, iface1->name); + iface1->upper = iface2; + break; + } + } + if (iface1->lower_idx != 0 && iface1->lower_idx != iface1->index) + TAILQ_FOREACH(iface2, ifs, next) { + if (iface1->lower_idx == iface2->index) { + log_debug("netlink", "%s is lower iface for %s", + iface2->name, iface1->name); + iface1->lower = iface2; + break; + } + } + } + + return ifs; } /** @@ -459,53 +284,28 @@ netlink_get_interfaces() * @return a list of addresses. */ struct interfaces_address_list* -netlink_get_addresses() +netlink_get_addresses(struct lldpd *cfg) { - int s; - struct interfaces_address_list *ifaddrs; - - if ((s = netlink_connect(NETLINK_ROUTE, 0)) == -1) - return NULL; - if (netlink_send(s, RTM_GETADDR, AF_UNSPEC) == -1) { - close(s); - return NULL; - } - - log_debug("netlink", "get the list of available addresses"); - ifaddrs = malloc(sizeof(struct interfaces_address_list)); - if (ifaddrs == NULL) { - log_warn("netlink", "not enough memory for address list"); - close(s); - return NULL; - } - TAILQ_INIT(ifaddrs); - netlink_recv(s, NULL, ifaddrs); - - close(s); - return ifaddrs; -} + if (netlink_initialize(cfg) == -1) return NULL; -static int -netlink_group_mask(int group) -{ - return group ? (1 << (group - 1)) : 0; -} + struct interfaces_address_list *ifaddrs; -/** - * Subscribe to link changes. - * - * @return The socket we should listen to for changes. - */ -int -netlink_subscribe_changes() -{ - unsigned int groups; - - log_debug("netlink", "listening on interface changes"); - - groups = netlink_group_mask(RTNLGRP_LINK) | - netlink_group_mask(RTNLGRP_IPV4_IFADDR) | - netlink_group_mask(RTNLGRP_IPV6_IFADDR); + log_debug("netlink", "get the list of available addresses"); + ifaddrs = malloc(sizeof(struct interfaces_address_list)); + if (ifaddrs == NULL) { + log_warn("netlink", "not enough memory for address list"); + return NULL; + } + TAILQ_INIT(ifaddrs); + + for (struct nl_object *addr = nl_cache_get_first(cfg->g_netlink->addr); + addr != NULL; + addr = nl_cache_get_next(addr)) { + nl_object_get(addr); + struct interfaces_address *ifa = netlink_parse_address((struct rtnl_addr *)addr); + if (ifa) TAILQ_INSERT_TAIL(ifaddrs, ifa, next); + nl_object_put(addr); + } - return netlink_connect(NETLINK_ROUTE, groups); + return ifaddrs; } diff --git a/src/daemon/priv.c b/src/daemon/priv.c index 7b3c5eca..5345f230 100644 --- a/src/daemon/priv.c +++ b/src/daemon/priv.c @@ -117,7 +117,7 @@ int priv_iface_init(int index, char *iface) { int rc; - char dev[IFNAMSIZ]; + char dev[IFNAMSIZ] = {}; enum priv_cmd cmd = PRIV_IFACE_INIT; must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd)); must_write(PRIV_UNPRIVILEGED, &index, sizeof(int));