/* -*- mode: c; c-file-style: "openbsd" -*- */ /* * Copyright (c) 2012 Vincent Bernat * * 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 #include #include #include #include #include #include #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; }