diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 18:02:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 18:02:34 +0000 |
commit | fadeddfbb2aa38a980dd959b5ec1ffba7afd43cb (patch) | |
tree | a7bde6111c84ea64619656a38fba50909fa0bf60 /src/daemon/netlink.c | |
parent | Initial commit. (diff) | |
download | lldpd-fadeddfbb2aa38a980dd959b5ec1ffba7afd43cb.tar.xz lldpd-fadeddfbb2aa38a980dd959b5ec1ffba7afd43cb.zip |
Adding upstream version 1.0.18.upstream/1.0.18upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/daemon/netlink.c | 993 |
1 files changed, 993 insertions, 0 deletions
diff --git a/src/daemon/netlink.c b/src/daemon/netlink.c new file mode 100644 index 0000000..32fee76 --- /dev/null +++ b/src/daemon/netlink.c @@ -0,0 +1,993 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Grabbing interfaces information with netlink only. */ + +#include "lldpd.h" + +#include <errno.h> +#include <sys/socket.h> +#include <netdb.h> +#include <net/if_arp.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/if_bridge.h> + +#define NETLINK_BUFFER 4096 + +struct netlink_req { + struct nlmsghdr hdr; + struct ifinfomsg ifm; + /* attribute has to be NLMSG aligned */ + struct rtattr ext_req __attribute__((aligned(NLMSG_ALIGNTO))); + __u32 ext_filter_mask; +}; + +struct lldpd_netlink { + int nl_socket_queries; + int nl_socket_changes; + int nl_socket_recv_size; + /* Cache */ + struct interfaces_device_list *devices; + struct interfaces_address_list *addresses; +}; + +/** + * Set netlink socket buffer size. + * + * This returns the effective size on success. If the provided value is 0, this + * returns the current size instead. It returns -1 on system errors and -2 if + * the size was not changed appropriately (when reaching the max). + */ +static int +netlink_socket_set_buffer_size(int s, int optname, const char *optname_str, int bufsize) +{ + socklen_t size = sizeof(int); + int got = 0; + + if (bufsize > 0 && + setsockopt(s, SOL_SOCKET, optname, &bufsize, sizeof(bufsize)) < 0) { + log_warn("netlink", "unable to set %s to '%d'", optname_str, bufsize); + return -1; + } + + /* Now read them back from kernel. + * SO_SNDBUF & SO_RCVBUF are cap-ed at sysctl `net.core.rmem_max` & + * `net.core.wmem_max`. This it the easiest [probably sanest too] + * to validate that our socket buffers were set properly. + */ + if (getsockopt(s, SOL_SOCKET, optname, &got, &size) < 0) { + log_warn("netlink", "unable to get %s", optname_str); + return -1; + } + if (bufsize > 0 && got < bufsize) { + log_warnx("netlink", + "tried to set %s to '%d' " + "but got '%d'", + optname_str, bufsize, got); + return -2; + } + + return got; +} + +/** + * Connect to netlink. + * + * Open a Netlink socket and connect to it. + * + * @param groups Which groups we want to subscribe to + * @return 0 on success, -1 otherwise + */ +static int +netlink_connect(struct lldpd *cfg, unsigned groups) +{ + int s1 = -1, s2 = -1; + struct sockaddr_nl local = { .nl_family = AF_NETLINK, + .nl_pid = 0, + .nl_groups = groups }; + + /* Open Netlink socket for subscriptions */ + log_debug("netlink", "opening netlink sockets"); + s1 = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (s1 == -1) { + log_warn("netlink", "unable to open netlink socket for changes"); + goto error; + } + if (NETLINK_SEND_BUFSIZE && + netlink_socket_set_buffer_size(s1, SO_SNDBUF, "SO_SNDBUF", + NETLINK_SEND_BUFSIZE) == -1) { + log_warn("netlink", "unable to set send buffer size"); + goto error; + } + + int rc = netlink_socket_set_buffer_size(s1, SO_RCVBUF, "SO_RCVBUF", + NETLINK_RECEIVE_BUFSIZE); + switch (rc) { + case -1: + log_warn("netlink", "unable to set receiver buffer size"); + goto error; + case -2: + /* Cannot set size */ + cfg->g_netlink->nl_socket_recv_size = 0; + break; + default: + cfg->g_netlink->nl_socket_recv_size = rc; + break; + } + if (groups && + bind(s1, (struct sockaddr *)&local, sizeof(struct sockaddr_nl)) < 0) { + log_warn("netlink", "unable to bind netlink socket"); + goto error; + } + + /* Opening Netlink socket to for queries */ + s2 = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (s2 == -1) { + log_warn("netlink", "unable to open netlink socket for queries"); + goto error; + } + cfg->g_netlink->nl_socket_changes = s1; + cfg->g_netlink->nl_socket_queries = s2; + return 0; +error: + if (s1 != -1) close(s1); + if (s2 != -1) close(s2); + return -1; +} + +/** + * Send a netlink message. + * + * 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_send(int s, int type, int family, int seq) +{ + struct netlink_req req = { .hdr = { .nlmsg_len = + NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .nlmsg_type = type, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, + .nlmsg_seq = seq, + .nlmsg_pid = getpid() }, + .ifm = { .ifi_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) }; + + if (family == AF_BRIDGE) { + unsigned int len = RTA_LENGTH(sizeof(__u32)); + /* request bridge vlan attributes */ + req.ext_req.rta_type = IFLA_EXT_MASK; + req.ext_req.rta_len = len; + req.ext_filter_mask = RTEXT_FILTER_BRVLAN; + req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_ALIGN(len); + iov.iov_len = req.hdr.nlmsg_len; + } + + /* 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; +} + +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); + } +} + +/** + * 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; + uint16_t vlan_id; + + 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; + } else if (!strcmp(kind, "team")) { + log_debug("netlink", "interface %s is a team", + iff->name); + iff->type |= IFACE_BOND_T; + } + } + } + + 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]) { + vlan_id = *(uint16_t *)RTA_DATA( + vlan_link_info_data_attrs[IFLA_VLAN_ID]); + bitmap_set(iff->vlan_bmap, vlan_id); + log_debug("netlink", "VLAN ID for interface %s is %d", + iff->name, vlan_id); + } + } + + if (kind && !strcmp(kind, "bridge") && link_info_attrs[IFLA_INFO_DATA]) { + struct rtattr *bridge_link_info_data_attrs[IFLA_BR_MAX + 1] = {}; + netlink_parse_rtattr(bridge_link_info_data_attrs, IFLA_BR_MAX, + RTA_DATA(link_info_attrs[IFLA_INFO_DATA]), + RTA_PAYLOAD(link_info_attrs[IFLA_INFO_DATA])); + + if (bridge_link_info_data_attrs[IFLA_BR_VLAN_FILTERING] && + *(uint8_t *)RTA_DATA( + bridge_link_info_data_attrs[IFLA_BR_VLAN_FILTERING]) > 0) { + iff->type |= IFACE_BRIDGE_VLAN_T; + } + } + + free(kind); +} + +/** + * Parse a `afspec` attributes. + * + * @param iff where to put the result + * @param rta afspec attribute + * @param len length of attributes + */ +static void +netlink_parse_afspec(struct interfaces_device *iff, struct rtattr *rta, int len) +{ + while (RTA_OK(rta, len)) { + struct bridge_vlan_info *vinfo; + switch (rta->rta_type) { + case IFLA_BRIDGE_VLAN_INFO: + vinfo = RTA_DATA(rta); + log_debug("netlink", "found VLAN %d on interface %s", + vinfo->vid, iff->name ? iff->name : "(unknown)"); + + bitmap_set(iff->vlan_bmap, vinfo->vid); + if (vinfo->flags & + (BRIDGE_VLAN_INFO_PVID | BRIDGE_VLAN_INFO_UNTAGGED)) + iff->pvid = vinfo->vid; + break; + default: + log_debug("netlink", + "unknown afspec attribute type %d for iface %s", + rta->rta_type, iff->name ? iff->name : "(unknown)"); + break; + } + rta = RTA_NEXT(rta, len); + } + /* All enbridged interfaces will have VLAN 1 by default, ignore it */ + if (iff->vlan_bmap[0] == 2 && (bitmap_numbits(iff->vlan_bmap) == 1) && + iff->pvid == 1) { + log_debug("netlink", + "found only default VLAN 1 on interface %s, removing", + iff->name ? iff->name : "(unknown)"); + iff->vlan_bmap[0] = iff->pvid = 0; + } +} + +/** + * 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 + */ +static int +netlink_parse_link(struct nlmsghdr *msg, struct interfaces_device *iff) +{ + 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; + } + + 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 */ + if (iff->lower_idx == -1) { + iff->lower_idx = *(int *)RTA_DATA(attribute); + log_debug("netlink", "attribute IFLA_LINK for %s: %d", + iff->name ? iff->name : "(unknown)", + iff->lower_idx); + } else { + log_debug("netlink", + "attribute IFLA_LINK for %s: %d (ignored)", + iff->name ? iff->name : "(unknown)", + iff->lower_idx); + } + break; + case IFLA_LINK_NETNSID: + /* Is the lower interface into another namesapce? */ + iff->lower_idx = -2; + log_debug("netlink", + "attribute IFLA_LINK_NETNSID received for %s", + iff->name ? iff->name : "(unknown)"); + break; + case IFLA_MASTER: + /* Index of master interface */ + iff->upper_idx = *(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; + case IFLA_AF_SPEC: + if (ifi->ifi_family != AF_BRIDGE) break; + netlink_parse_afspec(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_debug("netlink", + "interface %d does not have a name or an address, skip", + iff->index); + return -1; + } + if (iff->upper_idx == -1) { + /* No upper interface, we cannot be enslaved. We need to clear + * the flag because the appropriate information may come later + * and we don't want to miss it. */ + iff->flags &= ~IFF_SLAVE; + } + if (iff->lower_idx == -2) iff->lower_idx = -1; + + if (ifi->ifi_family == AF_BRIDGE && msg->nlmsg_type == RTM_DELLINK && + iff->upper_idx != -1) { + log_debug("netlink", "removal of %s from bridge %d", iff->name, + iff->upper_idx); + msg->nlmsg_type = RTM_NEWLINK; + iff->upper_idx = -1; + } + + log_debug("netlink", "parsed link %d (%s, flags: %d)", iff->index, iff->name, + iff->flags); + return 0; +} + +/** + * Parse a `address` 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 + */ +static int +netlink_parse_address(struct nlmsghdr *msg, struct interfaces_address *ifa) +{ + 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; + 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; + } + } + if (ifa->address.ss_family == AF_UNSPEC) { + log_debug("netlink", "no IP for interface %d", ifa->index); + return -1; + } + return 0; +} + +/** + * Merge an old interface with a new one. + * + * Some properties may be absent in the new interface that should be copied over + * from the old one. + */ +static void +netlink_merge(struct interfaces_device *old, struct interfaces_device *new) +{ + if (new->alias == NULL) { + new->alias = old->alias; + old->alias = NULL; + } + if (new->address == NULL) { + new->address = old->address; + old->address = NULL; + } + if (new->mtu == 0) new->mtu = old->mtu; + if (new->type == 0) new->type = old->type; + + if (bitmap_isempty(new->vlan_bmap) && new->type == IFACE_VLAN_T) + memcpy((void *)new->vlan_bmap, (void *)old->vlan_bmap, + sizeof(uint32_t) * VLAN_BITMAP_LEN); + + /* It's not possible for lower link to change */ + new->lower_idx = old->lower_idx; +} + +/** + * Receive netlink answer from the kernel. + * + * @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 + */ +static int +netlink_recv(struct lldpd *cfg, int s, struct interfaces_device_list *ifs, + struct interfaces_address_list *ifas) +{ + int end = 0, ret = 0, flags, retry = 0; + struct iovec iov; + 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]; + + iov.iov_len = NETLINK_BUFFER; + iov.iov_base = malloc(iov.iov_len); + if (!iov.iov_base) { + log_warn("netlink", "not enough memory"); + return -1; + } + + while (!end) { + ssize_t len; + struct nlmsghdr *msg; + 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) }; + flags = MSG_PEEK | MSG_TRUNC; + retry: + len = recvmsg(s, &rtnl_reply, flags); + if (len == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (retry++ == 0) { + levent_recv_error(s, "netlink socket"); + goto retry; + } + log_warnx("netlink", + "should have received something, but didn't"); + ret = 0; + goto out; + } + int rsize = cfg->g_netlink->nl_socket_recv_size; + if (errno == ENOBUFS && rsize > 0 && + rsize < NETLINK_MAX_RECEIVE_BUFSIZE && + s == cfg->g_netlink->nl_socket_changes) { + /* Try to increase buffer size, only for the + * socket used to receive changes */ + rsize *= 2; + if (rsize > NETLINK_MAX_RECEIVE_BUFSIZE) { + rsize = NETLINK_MAX_RECEIVE_BUFSIZE; + } + int rc = netlink_socket_set_buffer_size(s, SO_RCVBUF, + "SO_RCVBUF", rsize); + if (rc < 0) + cfg->g_netlink->nl_socket_recv_size = 0; + else + cfg->g_netlink->nl_socket_recv_size = rsize; + if (rc > 0 || rc == -2) { + log_info("netlink", + "netlink receive buffer too small, retry with larger one (%d)", + rsize); + flags = 0; + goto retry; + } + } + log_warn("netlink", "unable to receive netlink answer"); + ret = -1; + goto out; + } + if (!len) { + ret = 0; + goto out; + } + + if (iov.iov_len < len || (rtnl_reply.msg_flags & MSG_TRUNC)) { + void *tmp; + + /* Provided buffer is not large enough, enlarge it + * to size of len (which should be total length of the message) + * and try again. */ + iov.iov_len = len; + tmp = realloc(iov.iov_base, iov.iov_len); + if (!tmp) { + log_warn("netlink", "not enough memory"); + ret = -1; + goto out; + } + log_debug("netlink", "enlarge message size to %zu bytes", len); + iov.iov_base = tmp; + flags = 0; + goto retry; + } + + if (flags != 0) { + /* Buffer is big enough, do the actual reading */ + flags = 0; + goto retry; + } + + for (msg = (struct nlmsghdr *)(void *)(iov.iov_base); + 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); + netlink_merge(ifdold, ifdnew); + 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) { + if (ifanew->address.ss_family == AF_INET6 && + ifanew->flags & IFA_F_TEMPORARY) { + interfaces_free_address(ifanew); + break; + } + TAILQ_FOREACH (ifaold, ifas, next) { + if ((ifaold->index == ifanew->index) && + !memcmp(&ifaold->address, + &ifanew->address, + sizeof(ifaold->address))) + break; + } + if (getnameinfo( + (struct sockaddr *)&ifanew->address, + sizeof(ifanew->address), addr, + sizeof(addr), NULL, 0, + NI_NUMERICHOST) != 0) { + strlcpy(addr, "(unknown)", + sizeof(addr)); + } + + 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_info("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); + } + } + } +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) { + log_debug("netlink", + "upper interface for %s is %s", + iface1->name, iface2->name); + iface1->upper = iface2; + break; + } + } + if (iface2 == NULL) iface1->upper = NULL; + } else { + iface1->upper = NULL; + } + if (iface1->lower_idx != -1 && + iface1->lower_idx != iface1->index) { + TAILQ_FOREACH (iface2, ifs, next) { + if (iface1->lower_idx == iface2->index) { + /* Workaround a bug introduced + * in Linux 4.1: a pair of veth + * will be lower interface of + * each other. Do not modify + * index as if one of them is + * updated, we will loose the + * information about the + * loop. */ + if (iface2->lower_idx == + iface1->index) { + iface1->lower = NULL; + log_debug("netlink", + "link loop detected between %s(%d) and %s(%d)", + iface1->name, iface1->index, + iface2->name, + iface2->index); + } else { + log_debug("netlink", + "lower interface for %s is %s", + iface1->name, iface2->name); + iface1->lower = iface2; + } + break; + } + } + } else { + iface1->lower = NULL; + } + } + } + +out: + free(iov.iov_base); + return ret; +} + +static int +netlink_group_mask(int group) +{ + return group ? (1 << (group - 1)) : 0; +} + +/** + * Subscribe to link changes. + * + * @return 0 on success, -1 otherwise + */ +static int +netlink_subscribe_changes(struct lldpd *cfg) +{ + 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(cfg, groups); +} + +/** + * Receive changes from netlink */ +static void +netlink_change_cb(struct lldpd *cfg) +{ + if (cfg->g_netlink == NULL) return; + netlink_recv(cfg, cfg->g_netlink->nl_socket_changes, cfg->g_netlink->devices, + cfg->g_netlink->addresses); +} + +/** + * 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) +{ +#ifdef ENABLE_DOT1 + struct interfaces_device *iff; +#endif + + 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; + } + + /* Connect to netlink (by requesting to get notified on updates) and + * request updated information right now */ + if (netlink_subscribe_changes(cfg) == -1) goto end; + + 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"); + goto end; + } + TAILQ_INIT(ifaddrs); + + 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(cfg->g_netlink->nl_socket_queries, RTM_GETADDR, AF_UNSPEC, + 1) == -1) + goto end; + netlink_recv(cfg, cfg->g_netlink->nl_socket_queries, NULL, ifaddrs); + if (netlink_send(cfg->g_netlink->nl_socket_queries, RTM_GETLINK, AF_PACKET, + 2) == -1) + goto end; + netlink_recv(cfg, cfg->g_netlink->nl_socket_queries, ifs, NULL); +#ifdef ENABLE_DOT1 + /* If we have a bridge, search for VLAN-aware bridges */ + TAILQ_FOREACH (iff, ifs, next) { + if (iff->type & IFACE_BRIDGE_T) { + log_debug("netlink", + "interface %s is a bridge, check for VLANs", iff->name); + if (netlink_send(cfg->g_netlink->nl_socket_queries, RTM_GETLINK, + AF_BRIDGE, 3) == -1) + goto end; + netlink_recv(cfg, cfg->g_netlink->nl_socket_queries, ifs, NULL); + break; + } + } +#endif + + /* Listen to any future change */ + cfg->g_iface_cb = netlink_change_cb; + if (levent_iface_subscribe(cfg, cfg->g_netlink->nl_socket_changes) == -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_changes != -1) + close(cfg->g_netlink->nl_socket_changes); + if (cfg->g_netlink->nl_socket_queries != -1) + close(cfg->g_netlink->nl_socket_queries); + 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; +} + +/** + * 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; +} |