From 0fa2254b441699776bc18cc66c1109a959e10c8e Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Fri, 1 Jan 2016 08:54:32 +0100 Subject: [PATCH] netlink: remove use of libnl3 Use netlink implementation from 0.7.19 instead but manage a cache ourselves. The changes are quite minimal compared to the implementation in 0.7.19. We handle deletion and updates. The use of linked list may be problematic performance-wise. When an interface goes down then up, no PDU is scheduled to be sent again. This bug was already present in the previous implementation and should be a regression of 36080c. --- .gitmodules | 3 - Makefile.am | 4 - NEWS | 5 + README.md | 4 - configure.ac | 7 - debian/control | 2 - debian/copyright | 105 ----- libnl | 1 - m4/libnl3.m4 | 48 --- redhat/lldpd.spec | 12 - src/daemon/Makefile.am | 12 - src/daemon/event.c | 3 +- src/daemon/interfaces-bsd.c | 2 +- src/daemon/interfaces-linux.c | 10 +- src/daemon/interfaces.c | 8 +- src/daemon/lldpd.h | 1 + src/daemon/netlink.c | 758 ++++++++++++++++++++++++---------- tests/Makefile.am | 4 - 18 files changed, 563 insertions(+), 426 deletions(-) delete mode 160000 libnl delete mode 100644 m4/libnl3.m4 diff --git a/.gitmodules b/.gitmodules index ec1560ac..2119f124 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [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/Makefile.am b/Makefile.am index 78978d05..ea936ec1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,10 +7,6 @@ EXTRA_DIST = $(DX_CONFIG) include get-version autogen.sh DIST_SUBDIRS = $(SUBDIRS) libevent DISTCLEANFILES = ChangeLog -if HOST_OS_LINUX -DIST_SUBDIRS += libnl -endif - dist_doc_DATA = README.md NEWS CONTRIBUTE.md doc_DATA = ChangeLog diff --git a/NEWS b/NEWS index 5fd6be2f..7069dba2 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,8 @@ +lldpd (0.9.0) + * Change: + + Don't rely on libnl3 for netlink. Reimplement a minimal, + API-compatible, version. + lldpd (0.8.0, never released) * Fix: + Fix a buffer overflow when receiving a too large management diff --git a/README.md b/README.md index 64477cc6..dde80a97 100644 --- a/README.md +++ b/README.md @@ -285,7 +285,3 @@ 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 3f7dd0ce..796cd042 100644 --- a/configure.ac +++ b/configure.ac @@ -181,7 +181,6 @@ PKG_CHECK_MODULES([CHECK], [check >= 0.9.4], [have_check=yes], [have_check=no]) # Third-party libraries lldp_CHECK_LIBEVENT -lldp_CHECK_LIBNL # Compatibility with pkg.m4 < 0.27 m4_ifdef([PKG_INSTALLDIR], [PKG_INSTALLDIR], @@ -350,11 +349,6 @@ 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 (>= 2.0.5), - libnl-3-dev (>= 3.2.7), libnl-route-3-dev (>= 3.2.7), - bison, flex, libreadline-dev, libbsd-dev, pkg-config, diff --git a/debian/copyright b/debian/copyright index cda00136..2eec55cd 100644 --- a/debian/copyright +++ b/debian/copyright @@ -18,111 +18,6 @@ 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 deleted file mode 160000 index 7b9671c6..00000000 --- a/libnl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7b9671c6d88fce7e5261e4caa1a9a17f7136eba3 diff --git a/m4/libnl3.m4 b/m4/libnl3.m4 deleted file mode 100644 index 486198e4..00000000 --- a/m4/libnl3.m4 +++ /dev/null @@ -1,48 +0,0 @@ -# -# lldp_CHECK_LIBNL -# - -AC_DEFUN([lldp_CHECK_LIBNL], [ - # Do we require embedded libnl? - if test x"$os" = x"Linux"; then - 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.0 >= 3.2.7 libnl-route-3.0 >= 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]) - fi - - AM_CONDITIONAL([LIBNL_EMBEDDED], [test x"$LIBNL_EMBEDDED" != x]) - AC_SUBST([LIBNL_LIBS]) - AC_SUBST([LIBNL_CFLAGS]) - AC_SUBST([LIBNL_LDFLAGS]) -]) diff --git a/redhat/lldpd.spec b/redhat/lldpd.spec index 07d11cc5..0bda7eff 100644 --- a/redhat/lldpd.spec +++ b/redhat/lldpd.spec @@ -39,13 +39,6 @@ %bcond_without system_libevent %endif -# On RHEL < 7, use embedded libnl. -%if 0%{?rhel_version} > 0 && 0%{?rhel_version} < 700 || 0%{?centos_version} > 0 && 0%{?centos_version} < 600 || 0%{?suse_version} > 0 && 0%{?suse_version} < 1200 -%bcond_with system_libnl -%else -%bcond_without system_libnl -%endif - %define lldpd_user _lldpd %define lldpd_group _lldpd %define lldpd_chroot /var/run/lldpd @@ -65,11 +58,6 @@ BuildRequires: pkgconfig %if %{with system_libevent} BuildRequires: libevent-devel %endif -%if %{with system_libnl} -BuildRequires: libnl3-devel -%endif -BuildRequires: flex -BuildRequires: bison BuildRequires: readline-devel %if %{with snmp} BuildRequires: net-snmp-devel diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am index c14d83aa..b4734d4a 100644 --- a/src/daemon/Makefile.am +++ b/src/daemon/Makefile.am @@ -45,9 +45,6 @@ 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 += \ @@ -151,15 +148,6 @@ $(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) CFLAGS="$(CFLAGS) -Wno-error=unused-variable") -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 bfcc7bb3..4846478f 100644 --- a/src/daemon/event.c +++ b/src/daemon/event.c @@ -732,7 +732,8 @@ levent_iface_subscribe(struct lldpd *cfg, int socket) { log_debug("event", "subscribe to interface changes from socket %d", socket); - levent_make_socket_nonblocking(socket); + if (cfg->g_iface_cb == NULL) + levent_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) { diff --git a/src/daemon/interfaces-bsd.c b/src/daemon/interfaces-bsd.c index 5b45c33a..9cee2e6a 100644 --- a/src/daemon/interfaces-bsd.c +++ b/src/daemon/interfaces-bsd.c @@ -351,7 +351,7 @@ ifbsd_blacklist(struct lldpd *cfg, if (iface->name[i] == '\0') { log_debug("interfaces", "skip %s: AirDrop interface", iface->name); - iface->flags = 0; + iface->ignore = 1; } } #endif diff --git a/src/daemon/interfaces-linux.c b/src/daemon/interfaces-linux.c index 1e5ba9a4..9803d30f 100644 --- a/src/daemon/interfaces-linux.c +++ b/src/daemon/interfaces-linux.c @@ -539,7 +539,7 @@ iflinux_handle_bond(struct lldpd *cfg, struct interfaces_device_list *interfaces struct bond_master *bmaster; TAILQ_FOREACH(iface, interfaces, next) { if (!(iface->type & IFACE_PHYSICAL_T)) continue; - if (!iface->flags) continue; + if (iface->ignore) continue; if (!iface->upper || !(iface->upper->type & IFACE_BOND_T)) continue; master = iface->upper; @@ -583,7 +583,7 @@ iflinux_handle_bond(struct lldpd *cfg, struct interfaces_device_list *interfaces } hardware->h_flags = iface->flags; - iface->flags = 0; + iface->ignore = 1; /* Get local address */ memcpy(&hardware->h_lladdr, iface->address, ETHER_ADDR_LEN); @@ -776,7 +776,7 @@ interfaces_update(struct lldpd *cfg) addresses = netlink_get_addresses(cfg); if (interfaces == NULL || addresses == NULL) { log_warnx("interfaces", "cannot update the list of local interfaces"); - goto end; + return; } /* Add missing bits to list of interfaces */ @@ -806,10 +806,6 @@ interfaces_update(struct lldpd *cfg) iflinux_macphy(hardware); interfaces_helper_promisc(cfg, hardware); } - -end: - interfaces_free_devices(interfaces); - interfaces_free_addresses(addresses); } void diff --git a/src/daemon/interfaces.c b/src/daemon/interfaces.c index 73f949a5..ec817213 100644 --- a/src/daemon/interfaces.c +++ b/src/daemon/interfaces.c @@ -170,7 +170,7 @@ interfaces_helper_whitelist(struct lldpd *cfg, switch (m) { case 0: log_debug("interfaces", "blacklist %s", iface->name); - iface->flags = 0; + iface->ignore = 1; continue; case 2: log_debug("interfaces", "whitelist %s (consider it as a physical interface)", @@ -273,7 +273,7 @@ interfaces_helper_vlan(struct lldpd *cfg, struct interfaces_device *iface; TAILQ_FOREACH(iface, interfaces, next) { - if (!iface->flags) + if (iface->ignore) continue; if (!(iface->type & IFACE_VLAN_T)) continue; @@ -563,7 +563,7 @@ interfaces_helper_physical(struct lldpd *cfg, TAILQ_FOREACH(iface, interfaces, next) { if (!(iface->type & IFACE_PHYSICAL_T)) continue; - if (!iface->flags) continue; + if (iface->ignore) continue; log_debug("interfaces", "%s is an acceptable ethernet device", iface->name); @@ -595,7 +595,7 @@ interfaces_helper_physical(struct lldpd *cfg, } hardware->h_flags = iface->flags; /* Should be non-zero */ - iface->flags = 0; /* Future handlers + iface->ignore = 1; /* Future handlers don't have to care about this interface. */ diff --git a/src/daemon/lldpd.h b/src/daemon/lldpd.h index 06ecf3bf..fd0d09e4 100644 --- a/src/daemon/lldpd.h +++ b/src/daemon/lldpd.h @@ -304,6 +304,7 @@ void interfaces_update(struct lldpd *); #define IFACE_WIRELESS_T (1 << 4) /* Wireless interface */ struct interfaces_device { TAILQ_ENTRY(interfaces_device) next; + int ignore; /* Ignore this interface */ int index; /* Index */ char *name; /* Name */ char *alias; /* Alias */ diff --git a/src/daemon/netlink.c b/src/daemon/netlink.c index 06a370da..720af383 100644 --- a/src/daemon/netlink.c +++ b/src/daemon/netlink.c @@ -19,305 +19,641 @@ #include "lldpd.h" -#include +#include #include -#include +#include #include +#include +#include + +#define NETLINK_BUFFER 4096 -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdocumentation" -#endif -#include -#include -#include -#include -#if defined(__clang__) -#pragma clang diagnostic pop -#endif +struct netlink_req { + struct nlmsghdr hdr; + struct rtgenmsg gen; +}; struct lldpd_netlink { - struct nl_cache_mngr *mngr; - struct nl_cache *addr; - struct nl_cache *link; + int nl_socket; + /* Cache */ + struct interfaces_device_list *devices; + struct interfaces_address_list *addresses; }; /** - * Callback when we get netlink updates. + * 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. */ -static void -netlink_change_cb(struct lldpd *cfg) +static int +netlink_connect(int protocol, unsigned groups) { - 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)); + 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; } /** - * Initialize netlink subsystem. + * Send a netlink message. * - * This can be called several times but will have effect only the first time. + * The type of the message can be chosen as well the route family. The + * mesage will always be NLM_F_REQUEST | NLM_F_DUMP. * + * @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_initialize(struct lldpd *cfg) +netlink_send(int s, int type, int family, int seq) { - int err; - if (cfg->g_netlink) return 0; - - 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; - } - - 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; + 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 = seq, + .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; } - 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; - } + return 0; +} - cfg->g_iface_cb = netlink_change_cb; - if (levent_iface_subscribe(cfg, nl_cache_mngr_get_fd(cfg->g_netlink->mngr)) == -1) { - goto end; +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); } - - return 0; -end: - netlink_cleanup(cfg); - return -1; } /** - * Cleanup netlink subsystem. + * Parse a `linkinfo` attributes. + * + * @param iff where to put the result + * @param rta linkinfo attribute + * @param len length of attributes */ -void -netlink_cleanup(struct lldpd *cfg) +static void +netlink_parse_linkinfo(struct interfaces_device *iff, struct rtattr *rta, int len) { - if (cfg->g_netlink == NULL) return; - if (cfg->g_netlink->mngr != NULL) nl_cache_mngr_free(cfg->g_netlink->mngr); + 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; + } + } + } - free(cfg->g_netlink); - cfg->g_netlink = NULL; + 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 (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); + } + } + + free(kind); } /** * Parse a `link` netlink message. * - * @param link link object from cache - * @return parsed interface + * @param msg message to be parsed + * @param iff where to put the result + * return 0 if the interface is worth it, -1 otherwise */ -static struct interfaces_device * -netlink_parse_link(struct rtnl_link *link) +static int +netlink_parse_link(struct nlmsghdr *msg, + struct interfaces_device *iff) { - const char *name = rtnl_link_get_name(link); - if (name == NULL) { - log_debug("netlink", "skip unnamed interface"); - return NULL; + struct ifinfomsg *ifi; + struct rtattr *attribute; + int len; + ifi = NLMSG_DATA(msg); + len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg)); + + if (ifi->ifi_type != ARPHRD_ETHER) { + log_debug("netlink", "skip non Ethernet interface at index %d", + ifi->ifi_index); + return -1; } - unsigned int flags = rtnl_link_get_flags(link); - 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)); + 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->address) { - log_info("netlink", "interface %d does not have a MAC address, skip", + if (!iff->name || !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 -1; } - return iff; + log_debug("netlink", "parsed link %d (%s, flags: %d)", + iff->index, iff->name, iff->flags); + return 0; } /** * Parse a `address` netlink message. * - * @param addr address object from cache - * @return parsed address + * @param msg message to be parsed + * @param ifa where to put the result + * return 0 if the address is worth it, -1 otherwise */ -static struct interfaces_address * -netlink_parse_address(struct rtnl_addr *addr) +static int +netlink_parse_address(struct nlmsghdr *msg, + struct interfaces_address *ifa) { - int family = rtnl_addr_get_family(addr); - switch (family) { + 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)", - rtnl_addr_get_ifindex(addr), family); - return NULL; + ifa->index, ifi->ifa_family); + return -1; } - struct interfaces_address *ifa = calloc(1, sizeof(struct interfaces_address)); - if (ifa == NULL) { - log_warn("netlink", "no memory for a new address"); - return NULL; + 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; + memset(&ip, 0, sizeof(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; + memset(&ip6, 0, sizeof(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; + } } - 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) { + if (ifa->address.ss_family == AF_UNSPEC) { log_debug("netlink", "no IP for interface %d", ifa->index); - interfaces_free_address(ifa); - return NULL; + return -1; } - return ifa; + return 0; } /** - * Receive the list of interfaces. + * Receive netlink answer from the kernel. * - * @return a list of interfaces. + * @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 */ -struct interfaces_device_list* -netlink_get_interfaces(struct lldpd *cfg) +static int +netlink_recv(int s, + struct interfaces_device_list *ifs, + struct interfaces_address_list *ifas) { - 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); + char reply[NETLINK_BUFFER] __attribute__ ((aligned)); + int end = 0; + int link_update = 0; + + struct interfaces_device *ifdold; + struct interfaces_device *ifdnew; + struct interfaces_address *ifaold; + struct interfaces_address *ifanew; + char addr[INET6_ADDRSTRLEN + 1]; + + while (!end) { + ssize_t 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) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + log_debug("netlink", "should have received something, but didn't"); + return 0; + } + 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)) { + if (!(msg->nlmsg_flags & NLM_F_MULTI)) + end = 1; + switch (msg->nlmsg_type) { + case NLMSG_DONE: + log_debug("netlink", "received done message"); + end = 1; + break; + case RTM_NEWLINK: + case RTM_DELLINK: + if (!ifs) break; + log_debug("netlink", "received link information"); + ifdnew = calloc(1, sizeof(struct interfaces_device)); + if (ifdnew == NULL) { + log_warn("netlink", "not enough memory for another interface, give up what we have"); + goto end; + } + if (netlink_parse_link(msg, ifdnew) == 0) { + /* We need to find if we already have this interface */ + TAILQ_FOREACH(ifdold, ifs, next) { + if (ifdold->index == ifdnew->index) break; + } + if (msg->nlmsg_type == RTM_NEWLINK) { + if (ifdold == NULL) { + log_debug("netlink", "interface %s is new", + ifdnew->name); + TAILQ_INSERT_TAIL(ifs, ifdnew, next); + } else { + log_debug("netlink", "interface %s/%s is updated", + ifdold->name, ifdnew->name); + TAILQ_INSERT_AFTER(ifs, ifdold, ifdnew, next); + TAILQ_REMOVE(ifs, ifdold, next); + interfaces_free_device(ifdold); + } + } else { + if (ifdold == NULL) { + log_warnx("netlink", + "removal request for %s, but no knowledge of it", + ifdnew->name); + } else { + log_debug("netlink", "interface %s is to be removed", + ifdold->name); + TAILQ_REMOVE(ifs, ifdold, next); + interfaces_free_device(ifdold); + } + interfaces_free_device(ifdnew); + } + link_update = 1; + } else { + interfaces_free_device(ifdnew); + } + break; + case RTM_NEWADDR: + case RTM_DELADDR: + if (!ifas) break; + log_debug("netlink", "received address information"); + ifanew = calloc(1, sizeof(struct interfaces_address)); + if (ifanew == NULL) { + log_warn("netlink", "not enough memory for another address, give what we have"); + goto end; + } + if (netlink_parse_address(msg, ifanew) == 0) { + TAILQ_FOREACH(ifaold, ifas, next) { + if ((ifaold->index == ifanew->index) && + !memcmp(&ifaold->address, &ifanew->address, + sizeof(ifaold->address))) continue; + } + if (getnameinfo((struct sockaddr *)&ifanew->address, + sizeof(ifanew->address), + addr, sizeof(addr), + NULL, 0, NI_NUMERICHOST) != 0) { + strlcpy(addr, "(unknown)", sizeof(addr)); + } - 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); + if (msg->nlmsg_type == RTM_NEWADDR) { + if (ifaold == NULL) { + log_debug("netlink", "new address %s%%%d", + addr, ifanew->index); + TAILQ_INSERT_TAIL(ifas, ifanew, next); + } else { + log_debug("netlink", "updated address %s%%%d", + addr, ifaold->index); + TAILQ_INSERT_AFTER(ifas, ifaold, ifanew, next); + TAILQ_REMOVE(ifas, ifaold, next); + interfaces_free_address(ifaold); + } + } else { + if (ifaold == NULL) { + log_warnx("netlink", + "removal request for address of %s%%%d, but no knowledge of it", + addr, ifanew->index); + } else { + log_debug("netlink", "address %s%%%d is to be removed", + addr, ifaold->index); + TAILQ_REMOVE(ifas, ifaold, next); + interfaces_free_address(ifaold); + } + interfaces_free_address(ifanew); + } + } else { + interfaces_free_address(ifanew); + } + break; + default: + log_debug("netlink", + "received unhandled message type %d (len: %d)", + msg->nlmsg_type, msg->nlmsg_len); + } + } } - - 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; +end: + if (link_update) { + /* Fill out lower/upper */ + struct interfaces_device *iface1, *iface2; + 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; + } } + } else { + iface1->upper = NULL; } - if (iface1->lower_idx != 0 && iface1->lower_idx != iface1->index) - TAILQ_FOREACH(iface2, ifs, next) { - if (iface1->lower_idx == iface2->index) { - if (iface2->lower_idx == iface1->index) { - log_debug("netlink", "%s and %s are peered together", - iface1->name, iface2->name); - /* Workaround a bug introduced in Linux 4.1 */ - iface2->lower_idx = iface2->index; - iface1->lower_idx = iface1->index; - } else { - log_debug("netlink", "%s is lower iface for %s", - iface2->name, iface1->name); - iface1->lower = iface2; + if (iface1->lower_idx != -1 && iface1->lower_idx != iface1->index) { + TAILQ_FOREACH(iface2, ifs, next) { + if (iface1->lower_idx == iface2->index) { + if (iface2->lower_idx == iface1->index) { + /* Workaround a bug introduced in Linux 4.1 */ + iface2->lower_idx = iface2->index; + iface1->lower_idx = iface1->index; + } else iface1->lower = iface2; + break; } - break; } + } else { + iface1->lower = NULL; } + } } + return 0; +} - return ifs; +static int +netlink_group_mask(int group) +{ + return group ? (1 << (group - 1)) : 0; } /** - * Receive the list of addresses. + * Subscribe to link changes. * - * @return a list of addresses. + * @return The socket we should listen to for changes. */ -struct interfaces_address_list* -netlink_get_addresses(struct lldpd *cfg) +int +netlink_subscribe_changes() { - if (netlink_initialize(cfg) == -1) return NULL; + 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); + + return netlink_connect(NETLINK_ROUTE, groups); +} + +/** + * Receive changes from netlink */ +static void +netlink_change_cb(struct lldpd *cfg) +{ + if (cfg->g_netlink == NULL) + return; + netlink_recv(cfg->g_netlink->nl_socket, + cfg->g_netlink->devices, + cfg->g_netlink->addresses); +} - struct interfaces_address_list *ifaddrs; +/** + * Initialize netlink subsystem. + * + * This can be called several times but will have effect only the first time. + * + * @return 0 on success, -1 otherwise + */ +static int +netlink_initialize(struct lldpd *cfg) +{ + if (cfg->g_netlink) return 0; - log_debug("netlink", "get the list of available addresses"); - ifaddrs = malloc(sizeof(struct interfaces_address_list)); + 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; + } + + /* Connect to netlink (by requesting to get notified on updates) and + * request updated information right now */ + int s = cfg->g_netlink->nl_socket = netlink_subscribe_changes(); + + struct interfaces_address_list *ifaddrs = cfg->g_netlink->addresses = + malloc(sizeof(struct interfaces_address_list)); if (ifaddrs == NULL) { log_warn("netlink", "not enough memory for address list"); - return NULL; + goto end; } 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); + struct interfaces_device_list *ifs = cfg->g_netlink->devices = + malloc(sizeof(struct interfaces_device_list)); + if (ifs == NULL) { + log_warn("netlink", "not enough memory for interface list"); + goto end; + } + TAILQ_INIT(ifs); + + if (netlink_send(s, RTM_GETADDR, AF_UNSPEC, 1) == -1) + goto end; + netlink_recv(s, NULL, ifaddrs); + if (netlink_send(s, RTM_GETLINK, AF_PACKET, 2) == -1) + goto end; + netlink_recv(s, ifs, NULL); + + /* Listen to any future change */ + cfg->g_iface_cb = netlink_change_cb; + if (levent_iface_subscribe(cfg, s) == -1) { + goto end; + } + + return 0; +end: + netlink_cleanup(cfg); + return -1; +} + +/** + * Cleanup netlink subsystem. + */ +void +netlink_cleanup(struct lldpd *cfg) +{ + if (cfg->g_netlink == NULL) return; + if (cfg->g_netlink->nl_socket != -1) + close(cfg->g_netlink->nl_socket); + interfaces_free_devices(cfg->g_netlink->devices); + interfaces_free_addresses(cfg->g_netlink->addresses); + + free(cfg->g_netlink); + cfg->g_netlink = NULL; +} + +/** + * Receive the list of interfaces. + * + * @return a list of interfaces. + */ +struct interfaces_device_list* +netlink_get_interfaces(struct lldpd *cfg) +{ + if (netlink_initialize(cfg) == -1) return NULL; + struct interfaces_device *ifd; + TAILQ_FOREACH(ifd, cfg->g_netlink->devices, next) { + ifd->ignore = 0; } + return cfg->g_netlink->devices; +} - return ifaddrs; +/** + * Receive the list of addresses. + * + * @return a list of addresses. + */ +struct interfaces_address_list* +netlink_get_addresses(struct lldpd *cfg) +{ + if (netlink_initialize(cfg) == -1) return NULL; + return cfg->g_netlink->addresses; } diff --git a/tests/Makefile.am b/tests/Makefile.am index 2278b4ea..b06d7f81 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -8,10 +8,6 @@ TESTS = check_marshal check_pattern check_lldp check_cdp check_sonmp check_edp c AM_CFLAGS += @CHECK_CFLAGS@ LDADD = $(top_builddir)/src/daemon/liblldpd.la @CHECK_LIBS@ @LIBEVENT_LDFLAGS@ -if HOST_OS_LINUX -LDADD += @LIBNL_LDFLAGS@ -endif - check_marshal_SOURCES = check_marshal.c \ $(top_srcdir)/src/marshal.h \ check-compat.h -- 2.39.5