/* -*- 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;
			} else if (!strcmp(kind, "wlan")) {
				log_debug("netlink", "interface %s is wireless",
				    iff->name);
				iff->type |= IFACE_WIRELESS_T | IFACE_PHYSICAL_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(1, sizeof(struct lldpd_netlink))) == 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;
}
