view src/if-linux.c @ 5486:8e2b8ce8c972 draft

BSD: struct if_data->ifi_link_state is the single source of truth Vastly improve and simplify link detection on BSD. dhcpcd either examines the whole system via getifaddrs(3) or reacts to events via route(4). In both cases we have struct if_data which has ifi_link_state. Armed with this knowledge, we no longer need SIOCGIFDATA or SIOCGIFMEDIA. To solve the issue of newly attached interfaces having LINK_STATE_UNKNOWN or some interfaces not even changing it, we only change the local knowledge of interface flags when reports them by getifaddrs(3) or route(4) when we change them. For example, if we set IFF_UP and it succeeds we don't set this internally until reported by the kernel as above. This keeps flags and link state in sync with each other. The hope is that the kernel can set the real link state before it reports IFF_UP. As such, we no longer require the poll option or need to enter a tight loop for old interfaces.
author Roy Marples <roy@marples.name>
date Mon, 28 Sep 2020 17:09:38 +0100
parents 8dcd62252f2d
children 8f117781bb06
line wrap: on
line source

/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Linux interface driver for dhcpcd
 * Copyright (c) 2006-2020 Roy Marples <roy@marples.name>
 * All rights reserved

 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 */

#include <asm/types.h> /* Needed for 2.4 kernels */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/stat.h>

#include <linux/icmpv6.h>
#include <linux/if_addr.h>
#include <linux/if_link.h>
#include <linux/if_packet.h>
#include <linux/if_tun.h>
#include <linux/if_vlan.h>
#include <linux/filter.h>
#include <linux/netlink.h>
#include <linux/sockios.h>
#include <linux/rtnetlink.h>

#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <net/route.h>

/* musl has its own definition of struct ethhdr, so only include
 * netinet/if_ether.h on systems with GLIBC.  For the ARPHRD constants,
 * we must include linux/if_arp.h instead. */
#if defined(__GLIBC__)
#include <netinet/if_ether.h>
#else
#include <linux/if_arp.h>
#endif

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "config.h"
#include "bpf.h"
#include "common.h"
#include "dev.h"
#include "dhcp.h"
#include "if.h"
#include "ipv4.h"
#include "ipv4ll.h"
#include "ipv6.h"
#include "ipv6nd.h"
#include "logerr.h"
#include "privsep.h"
#include "route.h"
#include "sa.h"

#ifdef HAVE_NL80211_H
#include <linux/genetlink.h>
#include <linux/nl80211.h>
#else
int if_getssid_wext(const char *ifname, uint8_t *ssid);
#endif

/* Support older kernels */
#ifndef IFLA_WIRELESS
#define IFLA_WIRELESS (IFLA_MASTER + 1)
#endif

/* For some reason, glibc doesn't include newer flags from linux/if.h
 * However, we cannot include linux/if.h directly as it conflicts
 * with the glibc version. D'oh! */
#ifndef IFF_LOWER_UP
#define IFF_LOWER_UP	0x10000		/* driver signals L1 up		*/
#endif

/* Buggy CentOS and RedHat */
#ifndef SOL_NETLINK
#define	SOL_NETLINK	270
#endif

/*
 * Someone should fix kernel headers for clang alignment warnings.
 * But this is unlikely.
 * https://www.spinics.net/lists/netdev/msg646934.html
 */

#undef NLA_ALIGNTO
#undef NLA_ALIGN
#undef NLA_HDRLEN
#define NLA_ALIGNTO		4U
#define NLA_ALIGN(len)		(((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
#define NLA_HDRLEN		((int) NLA_ALIGN(sizeof(struct nlattr)))

#undef IFA_RTA
#define IFA_RTA(r)  ((struct rtattr *)(void *)(((char *)(r)) \
	+ NLMSG_ALIGN(sizeof(struct ifaddrmsg))))
#undef IFLA_RTA
#define IFLA_RTA(r)  ((struct rtattr *)(void *)(((char *)(r)) \
	+ NLMSG_ALIGN(sizeof(struct ifinfomsg))))
#undef NLMSG_NEXT
#define NLMSG_NEXT(nlh, len)	 ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
	(struct nlmsghdr *)(void *)(((char *)(nlh)) \
	+ NLMSG_ALIGN((nlh)->nlmsg_len)))
#undef RTM_RTA
#define RTM_RTA(r) (void *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))
#undef RTA_NEXT
#define RTA_NEXT(rta, attrlen)	((attrlen) -= RTA_ALIGN((rta)->rta_len), \
	(struct rtattr *)(void *)(((char *)(rta)) \
	+ RTA_ALIGN((rta)->rta_len)))

struct priv {
	int route_fd;
	int generic_fd;
	uint32_t route_pid;
};

/* We need this to send a broadcast for InfiniBand.
 * Our old code used sendto, but our new code writes to a raw BPF socket.
 * What header structure does IPoIB use? */
#if 0
/* Broadcast address for IPoIB */
static const uint8_t ipv4_bcast_addr[] = {
	0x00, 0xff, 0xff, 0xff,
	0xff, 0x12, 0x40, 0x1b, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff
};
#endif

static int if_addressexists(struct interface *, struct in_addr *);

#define PROC_INET6	"/proc/net/if_inet6"
#define PROC_PROMOTE	"/proc/sys/net/ipv4/conf/%s/promote_secondaries"
#define SYS_BRIDGE	"/sys/class/net/%s/bridge/bridge_id"
#define SYS_LAYER2	"/sys/class/net/%s/device/layer2"
#define SYS_TUNTAP	"/sys/class/net/%s/tun_flags"

static const char *mproc =
#if defined(__alpha__)
	"system type"
#elif defined(__arm__) || defined(__aarch64__)
	"Hardware"
#elif defined(__avr32__)
	"cpu family"
#elif defined(__bfin__)
	"BOARD Name"
#elif defined(__cris__)
	"cpu model"
#elif defined(__frv__)
	"System"
#elif defined(__i386__) || defined(__x86_64__)
	"vendor_id"
#elif defined(__ia64__)
	"vendor"
#elif defined(__hppa__)
	"model"
#elif defined(__m68k__)
	"MMU"
#elif defined(__mips__)
	"system type"
#elif defined(__powerpc__) || defined(__powerpc64__)
	"machine"
#elif defined(__s390__) || defined(__s390x__)
	"Manufacturer"
#elif defined(__sh__)
	"machine"
#elif defined(sparc) || defined(__sparc__)
	"cpu"
#elif defined(__vax__)
	"cpu"
#else
	NULL
#endif
	;

int
if_machinearch(char *str, size_t len)
{
	FILE *fp;
	char buf[256];

	if (mproc == NULL) {
		errno = EINVAL;
		return -1;
	}

	fp = fopen("/proc/cpuinfo", "r");
	if (fp == NULL)
		return -1;

	while (fscanf(fp, "%255s : ", buf) != EOF) {
		if (strncmp(buf, mproc, strlen(mproc)) == 0 &&
		    fscanf(fp, "%255s", buf) == 1)
		{
		        fclose(fp);
			return snprintf(str, len, "%s", buf);
		}
	}
	fclose(fp);
	errno = ESRCH;
	return -1;
}

static int
check_proc_int(struct dhcpcd_ctx *ctx, const char *path)
{
	char buf[64];
	int error, i;

	if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1)
		return -1;
	i = (int)strtoi(buf, NULL, 0, INT_MIN, INT_MAX, &error);
	if (error != 0 && error != ENOTSUP) {
		errno = error;
		return -1;
	}
	return i;
}

static int
check_proc_uint(struct dhcpcd_ctx *ctx, const char *path, unsigned int *u)
{
	char buf[64];
	int error;

	if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1)
		return -1;
	*u = (unsigned int)strtou(buf, NULL, 0, 0, UINT_MAX, &error);
	if (error != 0 && error != ENOTSUP) {
		errno = error;
		return error;
	}
	return 0;
}

static ssize_t
if_writepathuint(struct dhcpcd_ctx *ctx, const char *path, unsigned int val)
{
	char buf[64];
	int len;

	len = snprintf(buf, sizeof(buf), "%u\n", val);
	if (len == -1)
		return -1;
	return dhcp_writefile(ctx, path, 0664, buf, (size_t)len);
}

int
if_init(struct interface *ifp)
{
	char path[sizeof(PROC_PROMOTE) + IF_NAMESIZE];
	int n;

	/* We enable promote_secondaries so that we can do this
	 * add 192.168.1.2/24
	 * add 192.168.1.3/24
	 * del 192.168.1.2/24
	 * and the subnet mask moves onto 192.168.1.3/24
	 * This matches the behaviour of BSD which makes coding dhcpcd
	 * a little easier as there's just one behaviour. */
	snprintf(path, sizeof(path), PROC_PROMOTE, ifp->name);
	n = check_proc_int(ifp->ctx, path);
	if (n == -1)
		return errno == ENOENT ? 0 : -1;
	if (n == 1)
		return 0;
	return if_writepathuint(ifp->ctx, path, 1) == -1 ? -1 : 0;
}

int
if_conf(struct interface *ifp)
{
	char path[sizeof(SYS_LAYER2) + IF_NAMESIZE];
	int n;

	/* Some qeth setups require the use of the broadcast flag. */
	snprintf(path, sizeof(path), SYS_LAYER2, ifp->name);
	n = check_proc_int(ifp->ctx, path);
	if (n == -1)
		return errno == ENOENT ? 0 : -1;
	if (n == 0)
		ifp->options->options |= DHCPCD_BROADCAST;
	return 0;
}

static bool
if_bridge(struct dhcpcd_ctx *ctx, const char *ifname)
{
	char path[sizeof(SYS_BRIDGE) + IF_NAMESIZE], buf[64];

	snprintf(path, sizeof(path), SYS_BRIDGE, ifname);
	if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1)
		return false;
	return true;
}

static bool
if_tap(struct dhcpcd_ctx *ctx, const char *ifname)
{
	char path[sizeof(SYS_TUNTAP) + IF_NAMESIZE];
	unsigned int u;

	snprintf(path, sizeof(path), SYS_TUNTAP, ifname);
	if (check_proc_uint(ctx, path, &u) == -1)
		return false;
	return u & IFF_TAP;
}

bool
if_ignore(struct dhcpcd_ctx *ctx, const char *ifname)
{

	if (if_tap(ctx, ifname) || if_bridge(ctx, ifname))
		return true;
	return false;
}

/* XXX work out Virtal Interface Masters */
int
if_vimaster(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname)
{

	return 0;
}

unsigned short
if_vlanid(const struct interface *ifp)
{
	struct vlan_ioctl_args v;

	memset(&v, 0, sizeof(v));
	strlcpy(v.device1, ifp->name, sizeof(v.device1));
	v.cmd = GET_VLAN_VID_CMD;
	if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFVLAN, &v) != 0)
		return 0; /* 0 means no VLANID */
	return (unsigned short)v.u.VID;
}

int
if_linksocket(struct sockaddr_nl *nl, int protocol, int flags)
{
	int fd;

	fd = xsocket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC | flags, protocol);
	if (fd == -1)
		return -1;
	nl->nl_family = AF_NETLINK;
	if (bind(fd, (struct sockaddr *)nl, sizeof(*nl)) == -1) {
		close(fd);
		return -1;
	}
	return fd;
}

char *
if_getnetworknamespace(char *buf, size_t len)
{
	struct stat sb_self, sb_netns;
	DIR *dir;
	struct dirent *de;
	char file[PATH_MAX], *bufp = NULL;

	if (stat("/proc/self/ns/net", &sb_self) == -1)
		return NULL;

	dir = opendir("/var/run/netns");
	if (dir == NULL)
		return NULL;

	while ((de = readdir(dir)) != NULL) {
		snprintf(file, sizeof(file), "/var/run/netns/%s", de->d_name);
		if (stat(file, &sb_netns) == -1)
			continue;
		if (sb_self.st_dev != sb_netns.st_dev &&
		    sb_self.st_ino != sb_netns.st_ino)
			continue;
		strlcpy(buf, de->d_name, len);
		bufp = buf;
		break;
	}
	closedir(dir);
	return bufp;
}

int
os_init(void)
{
	char netns[PATH_MAX], *p;

	p = if_getnetworknamespace(netns, sizeof(netns));
	if (p != NULL)
		loginfox("network namespace: %s", p);

	return 0;
}

int
if_opensockets_os(struct dhcpcd_ctx *ctx)
{
	struct priv *priv;
	struct sockaddr_nl snl;
	socklen_t len;
#ifdef NETLINK_BROADCAST_ERROR
	int on = 1;
#endif

	/* Open the link socket first so it gets pid() for the socket.
	 * Then open our persistent route socket so we get a unique
	 * pid that doesn't clash with a process id for after we fork. */
	memset(&snl, 0, sizeof(snl));
	snl.nl_groups = RTMGRP_LINK;

#ifdef INET
	snl.nl_groups |= RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR;
#endif
#ifdef INET6
	snl.nl_groups |= RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_NEIGH;
#endif

	ctx->link_fd = if_linksocket(&snl, NETLINK_ROUTE, SOCK_NONBLOCK);
	if (ctx->link_fd == -1)
		return -1;
#ifdef NETLINK_BROADCAST_ERROR
	if (setsockopt(ctx->link_fd, SOL_NETLINK, NETLINK_BROADCAST_ERROR,
	    &on, sizeof(on)) == -1)
		logerr("%s: NETLINK_BROADCAST_ERROR", __func__);
#endif

	if ((priv = calloc(1, sizeof(*priv))) == NULL)
		return -1;

	ctx->priv = priv;
	memset(&snl, 0, sizeof(snl));
	priv->route_fd = if_linksocket(&snl, NETLINK_ROUTE, 0);
	if (priv->route_fd == -1)
		return -1;
	len = sizeof(snl);
	if (getsockname(priv->route_fd, (struct sockaddr *)&snl, &len) == -1)
		return -1;
	priv->route_pid = snl.nl_pid;

	memset(&snl, 0, sizeof(snl));
	priv->generic_fd = if_linksocket(&snl, NETLINK_GENERIC, 0);
	if (priv->generic_fd == -1)
		return -1;

	return 0;
}

void
if_closesockets_os(struct dhcpcd_ctx *ctx)
{
	struct priv *priv;

	if (ctx->priv != NULL) {
		priv = (struct priv *)ctx->priv;
		close(priv->route_fd);
		close(priv->generic_fd);
	}
}

int
if_setmac(struct interface *ifp, void *mac, uint8_t maclen)
{
	struct ifreq ifr = {
		.ifr_hwaddr.sa_family = ifp->hwtype,
	};

	if (ifp->hwlen != maclen || maclen > sizeof(ifr.ifr_hwaddr.sa_data)) {
		errno = EINVAL;
		return -1;
	}

	strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
	memcpy(ifr.ifr_hwaddr.sa_data, mac, maclen);
	return if_ioctl(ifp->ctx, SIOCSIFHWADDR, &ifr, sizeof(ifr));
}

int
if_carrier(struct interface *ifp, __unused const void *ifadata)
{

	return ifp->flags & IFF_RUNNING ? LINK_UP : LINK_DOWN;
}

int
if_getnetlink(struct dhcpcd_ctx *ctx, struct iovec *iov, int fd, int flags,
    int (*cb)(struct dhcpcd_ctx *, void *, struct nlmsghdr *), void *cbarg)
{
	struct sockaddr_nl nladdr = { .nl_pid = 0 };
	struct msghdr msg = {
	    .msg_name = &nladdr, .msg_namelen = sizeof(nladdr),
	    .msg_iov = iov, .msg_iovlen = 1,
	};
	ssize_t len;
	struct nlmsghdr *nlm;
	int r = 0;
	unsigned int again;
	bool terminated;

recv_again:
	len = recvmsg(fd, &msg, flags);
	if (len == -1 || len == 0)
		return (int)len;

	/* Check sender */
	if (msg.msg_namelen != sizeof(nladdr)) {
		errno = EINVAL;
		return -1;
	}

	/* Ignore message if it is not from kernel */
	if (nladdr.nl_pid != 0)
		return 0;

	again = 0;
	terminated = false;
	for (nlm = iov->iov_base;
	     nlm && NLMSG_OK(nlm, (size_t)len);
	     nlm = NLMSG_NEXT(nlm, len))
	{
		again = (nlm->nlmsg_flags & NLM_F_MULTI);
		if (nlm->nlmsg_type == NLMSG_NOOP)
			continue;

		if (nlm->nlmsg_type == NLMSG_ERROR) {
			struct nlmsgerr *err;

			if (nlm->nlmsg_len - sizeof(*nlm) < sizeof(*err)) {
				errno = EBADMSG;
				return -1;
			}
			err = (struct nlmsgerr *)NLMSG_DATA(nlm);
			if (err->error != 0) {
				errno = -err->error;
				return -1;
			}
			again = 0;
			terminated = true;
			break;
		}
		if (nlm->nlmsg_type == NLMSG_DONE) {
			again = 0;
			terminated = true;
			break;
		}
		if (cb == NULL)
			continue;
		if (nlm->nlmsg_seq != (uint32_t)ctx->seq && fd != ctx->link_fd)
			logwarnx("%s: received sequence %u, expecting %d",
			    __func__, nlm->nlmsg_seq, ctx->seq);
		else
			r = cb(ctx, cbarg, nlm);
	}

	if ((again || !terminated) && (ctx != NULL && ctx->link_fd != fd))
		goto recv_again;

	return r;
}

static int
if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, struct nlmsghdr *nlm)
{
	size_t len;
	struct rtmsg *rtm;
	struct rtattr *rta;
	unsigned int ifindex;
	struct sockaddr *sa;

	len = nlm->nlmsg_len - sizeof(*nlm);
	if (len < sizeof(*rtm)) {
		errno = EBADMSG;
		return -1;
	}
	rtm = (struct rtmsg *)NLMSG_DATA(nlm);
	if (rtm->rtm_table != RT_TABLE_MAIN)
		return -1;

	memset(rt, 0, sizeof(*rt));
	if (rtm->rtm_type == RTN_UNREACHABLE)
		rt->rt_flags |= RTF_REJECT;

	rta = RTM_RTA(rtm);
	len = RTM_PAYLOAD(nlm);
	for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
		sa = NULL;
		switch (rta->rta_type) {
		case RTA_DST:
			sa = &rt->rt_dest;
			break;
		case RTA_GATEWAY:
			sa = &rt->rt_gateway;
			break;
		case RTA_PREFSRC:
			sa = &rt->rt_ifa;
			break;
		case RTA_OIF:
			ifindex = *(unsigned int *)RTA_DATA(rta);
			rt->rt_ifp = if_findindex(ctx->ifaces, ifindex);
			break;
		case RTA_PRIORITY:
			rt->rt_metric = *(unsigned int *)RTA_DATA(rta);
			break;
		case RTA_METRICS:
		{
			struct rtattr *r2;
			size_t l2;

			l2 = rta->rta_len;
			r2 = (struct rtattr *)RTA_DATA(rta);
			for (; RTA_OK(r2, l2); r2 = RTA_NEXT(r2, l2)) {
				switch (r2->rta_type) {
				case RTAX_MTU:
					rt->rt_mtu = *(unsigned int *)RTA_DATA(r2);
					break;
				}
			}
			break;
		}
		}

		if (sa != NULL) {
			socklen_t salen;

			sa->sa_family = rtm->rtm_family;
			salen = sa_addrlen(sa);
			/* sa is a union where sockaddr_in6 is the biggest. */
			/* coverity[overrun-buffer-arg] */
			memcpy((char *)sa + sa_addroffset(sa), RTA_DATA(rta),
			    MIN(salen, RTA_PAYLOAD(rta)));
		}
	}

	/* If no RTA_DST set the unspecified address for the family. */
	if (rt->rt_dest.sa_family == AF_UNSPEC)
		rt->rt_dest.sa_family = rtm->rtm_family;

	rt->rt_netmask.sa_family = rtm->rtm_family;
	sa_fromprefix(&rt->rt_netmask, rtm->rtm_dst_len);
	if (sa_is_allones(&rt->rt_netmask))
		rt->rt_flags |= RTF_HOST;

	#if 0
	if (rt->rtp_ifp == NULL && rt->src.s_addr != INADDR_ANY) {
		struct ipv4_addr *ap;

		/* For some reason the default route comes back with the
		 * loopback interface in RTA_OIF? Lets find it by
		 * preferred source address */
		if ((ap = ipv4_findaddr(ctx, &rt->src)))
			rt->iface = ap->iface;
	}
	#endif

	if (rt->rt_ifp == NULL) {
		errno = ESRCH;
		return -1;
	}
	return 0;
}

static int
link_route(struct dhcpcd_ctx *ctx, __unused struct interface *ifp,
    struct nlmsghdr *nlm)
{
	size_t len;
	int cmd;
	struct priv *priv;
	struct rt rt;

	switch (nlm->nlmsg_type) {
	case RTM_NEWROUTE:
		cmd = RTM_ADD;
		break;
	case RTM_DELROUTE:
		cmd = RTM_DELETE;
		break;
	default:
		return 0;
	}

	len = nlm->nlmsg_len - sizeof(*nlm);
	if (len < sizeof(struct rtmsg)) {
		errno = EBADMSG;
		return -1;
	}

	/* Ignore messages we sent. */
#ifdef PRIVSEP
	if (ctx->ps_root_pid != 0 &&
	    nlm->nlmsg_pid == (uint32_t)ctx->ps_root_pid)
		return 0;
#endif
	priv = (struct priv *)ctx->priv;
	if (nlm->nlmsg_pid == priv->route_pid)
		return 0;

	if (if_copyrt(ctx, &rt, nlm) == 0)
		rt_recvrt(cmd, &rt, (pid_t)nlm->nlmsg_pid);

	return 0;
}

static int
link_addr(struct dhcpcd_ctx *ctx, struct interface *ifp, struct nlmsghdr *nlm)
{
	size_t len;
	struct rtattr *rta;
	struct ifaddrmsg *ifa;
	struct priv *priv;
#ifdef INET
	struct in_addr addr, net, brd;
#endif
#ifdef INET6
	struct in6_addr addr6;
	int flags;
#endif

	if (nlm->nlmsg_type != RTM_DELADDR && nlm->nlmsg_type != RTM_NEWADDR)
		return 0;

	len = nlm->nlmsg_len - sizeof(*nlm);
	if (len < sizeof(*ifa)) {
		errno = EBADMSG;
		return -1;
	}

	/* Ignore address deletions from ourself.
	 * We need to process address flag changes though. */
	if (nlm->nlmsg_type == RTM_DELADDR) {
#ifdef PRIVSEP
		if (ctx->ps_root_pid != 0 &&
		    nlm->nlmsg_pid == (uint32_t)ctx->ps_root_pid)
			return 0;
#endif
		priv = (struct priv*)ctx->priv;
		if (nlm->nlmsg_pid == priv->route_pid)
			return 0;
	}

	ifa = NLMSG_DATA(nlm);
	if ((ifp = if_findindex(ctx->ifaces, ifa->ifa_index)) == NULL) {
		/* We don't know about the interface the address is for
		 * so it's not really an error */
		return 1;
	}
	rta = IFA_RTA(ifa);
	len = NLMSG_PAYLOAD(nlm, sizeof(*ifa));
	switch (ifa->ifa_family) {
#ifdef INET
	case AF_INET:
		addr.s_addr = brd.s_addr = INADDR_ANY;
		inet_cidrtoaddr(ifa->ifa_prefixlen, &net);
		for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
			switch (rta->rta_type) {
			case IFA_ADDRESS:
				if (ifp->flags & IFF_POINTOPOINT) {
					memcpy(&brd.s_addr, RTA_DATA(rta),
					    sizeof(brd.s_addr));
				}
				break;
			case IFA_BROADCAST:
				memcpy(&brd.s_addr, RTA_DATA(rta),
				    sizeof(brd.s_addr));
				break;
			case IFA_LOCAL:
				memcpy(&addr.s_addr, RTA_DATA(rta),
				    sizeof(addr.s_addr));
				break;
			}
		}

		/* Validate RTM_DELADDR really means address deleted
		 * and anything else really means address exists. */
		if (if_addressexists(ifp, &addr) == 1) {
			if (nlm->nlmsg_type == RTM_DELADDR)
				break;
		} else {
			if (nlm->nlmsg_type != RTM_DELADDR)
				break;
		}

		ipv4_handleifa(ctx, nlm->nlmsg_type, NULL, ifp->name,
		    &addr, &net, &brd, ifa->ifa_flags, (pid_t)nlm->nlmsg_pid);
		break;
#endif
#ifdef INET6
	case AF_INET6:
		memset(&addr6, 0, sizeof(addr6));
		for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
			switch (rta->rta_type) {
			case IFA_ADDRESS:
				memcpy(&addr6.s6_addr, RTA_DATA(rta),
				       sizeof(addr6.s6_addr));
				break;
			}
		}

		/* Validate RTM_DELADDR really means address deleted
		 * and anything else really means address exists. */
		flags = if_addrflags6(ifp, &addr6, NULL);
		if (nlm->nlmsg_type == RTM_DELADDR) {
			if (flags != -1)
				break;
		} else {
			if (flags == -1)
				break;
		}

		ipv6_handleifa(ctx, nlm->nlmsg_type, NULL, ifp->name,
		    &addr6, ifa->ifa_prefixlen, ifa->ifa_flags,
		    (pid_t)nlm->nlmsg_pid);
		break;
#endif
	}
	return 0;
}

static uint8_t
l2addr_len(unsigned short if_type)
{

	switch (if_type) {
	case ARPHRD_ETHER: /* FALLTHROUGH */
	case ARPHRD_IEEE802: /*FALLTHROUGH */
	case ARPHRD_IEEE80211:
		return 6;
	case ARPHRD_IEEE1394:
		return 8;
	case ARPHRD_INFINIBAND:
		return 20;
	}

	/* Impossible */
	return 0;
}

#ifdef INET6
static int
link_neigh(struct dhcpcd_ctx *ctx, __unused struct interface *ifp,
    struct nlmsghdr *nlm)
{
	struct ndmsg *r;
	struct rtattr *rta;
	size_t len;

	if (nlm->nlmsg_type != RTM_NEWNEIGH && nlm->nlmsg_type != RTM_DELNEIGH)
		return 0;
	if (nlm->nlmsg_len < sizeof(*r))
		return -1;

	r = NLMSG_DATA(nlm);
	rta = RTM_RTA(r);
	len = RTM_PAYLOAD(nlm);
        if (r->ndm_family == AF_INET6) {
		bool unreachable;
		struct in6_addr addr6;

		unreachable = (nlm->nlmsg_type == RTM_NEWNEIGH &&
		    r->ndm_state & NUD_FAILED);
		memset(&addr6, 0, sizeof(addr6));
		for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
			switch (rta->rta_type) {
			case NDA_DST:
				memcpy(&addr6.s6_addr, RTA_DATA(rta),
				       sizeof(addr6.s6_addr));
				break;
			}
		}
		ipv6nd_neighbour(ctx, &addr6, !unreachable);
	}

	return 0;
}
#endif

static int
link_netlink(struct dhcpcd_ctx *ctx, void *arg, struct nlmsghdr *nlm)
{
	struct interface *ifp = arg;
	int r;
	size_t len;
	struct rtattr *rta, *hwaddr;
	struct ifinfomsg *ifi;
	char ifn[IF_NAMESIZE + 1];

	r = link_route(ctx, ifp, nlm);
	if (r != 0)
		return r;
	r = link_addr(ctx, ifp, nlm);
	if (r != 0)
		return r;
#ifdef INET6
	r = link_neigh(ctx, ifp, nlm);
	if (r != 0)
		return r;
#endif

	if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK)
		return 0;
	len = nlm->nlmsg_len - sizeof(*nlm);
	if ((size_t)len < sizeof(*ifi)) {
		errno = EBADMSG;
		return -1;
	}
	ifi = NLMSG_DATA(nlm);
	if (ifi->ifi_flags & IFF_LOOPBACK)
		return 0;
	rta = (void *)((char *)ifi + NLMSG_ALIGN(sizeof(*ifi)));
	len = NLMSG_PAYLOAD(nlm, sizeof(*ifi));
	*ifn = '\0';
	hwaddr = NULL;

	for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
		switch (rta->rta_type) {
		case IFLA_WIRELESS:
			/* Ignore wireless messages */
			if (nlm->nlmsg_type == RTM_NEWLINK &&
			    ifi->ifi_change == 0)
				return 0;
			break;
		case IFLA_IFNAME:
			strlcpy(ifn, (char *)RTA_DATA(rta), sizeof(ifn));
			break;
		case IFLA_ADDRESS:
			hwaddr = rta;
			break;
		}
	}

	if (nlm->nlmsg_type == RTM_DELLINK) {
#ifdef PLUGIN_DEV
		/* If are listening to a dev manager, let that remove
		 * the interface rather than the kernel. */
		if (dev_listening(ctx) < 1)
#endif
			dhcpcd_handleinterface(ctx, -1, ifn);
		return 0;
	}

	/* Virtual interfaces may not get a valid hardware address
	 * at this point.
	 * To trigger a valid hardware address pickup we need to pretend
	 * that that don't exist until they have one. */
	if (ifi->ifi_flags & IFF_MASTER && !hwaddr) {
		dhcpcd_handleinterface(ctx, -1, ifn);
		return 0;
	}

	/* Check for a new interface */
	ifp = if_findindex(ctx->ifaces, (unsigned int)ifi->ifi_index);
	if (ifp == NULL) {
#ifdef PLUGIN_DEV
		/* If are listening to a dev manager, let that announce
		 * the interface rather than the kernel. */
		if (dev_listening(ctx) < 1)
#endif
			dhcpcd_handleinterface(ctx, 1, ifn);
		return 0;
	}

	/* Handle interface being renamed */
	if (strcmp(ifp->name, ifn) != 0) {
		dhcpcd_handleinterface(ctx, -1, ifn);
		dhcpcd_handleinterface(ctx, 1, ifn);
		return 0;
	}

	/* Re-read hardware address and friends */
	if (!(ifi->ifi_flags & IFF_UP)) {
		void *hwa = hwaddr != NULL ? RTA_DATA(hwaddr) : NULL;
		uint8_t hwl = l2addr_len(ifi->ifi_type);

		if (hwaddr != NULL && hwaddr->rta_len != RTA_LENGTH(hwl))
			hwa = NULL;
		dhcpcd_handlehwaddr(ifp, ifi->ifi_type, hwa, hwl);
	}

	dhcpcd_handlecarrier(ifp,
	    ifi->ifi_flags & IFF_RUNNING ? LINK_UP : LINK_DOWN,
	    ifi->ifi_flags);
	return 0;
}

int
if_handlelink(struct dhcpcd_ctx *ctx)
{
	unsigned char buf[16 * 1024];
	struct iovec iov = {
		.iov_base = buf,
		.iov_len = sizeof(buf),
	};

	return if_getnetlink(ctx, &iov, ctx->link_fd, MSG_DONTWAIT,
	    &link_netlink, NULL);
}

#ifdef PRIVSEP
static bool
if_netlinkpriv(int protocol, struct nlmsghdr *nlm)
{

	if (protocol != NETLINK_ROUTE)
		return false;

	switch(nlm->nlmsg_type) {
	case RTM_NEWADDR:	/* FALLTHROUGH */
	case RTM_DELADDR:	/* FALLTHROUGH */
	case RTM_NEWROUTE:	/* FALLTHROUGH */
	case RTM_DELROUTE:	/* FALLTHROUGH */
	case RTM_NEWLINK:
		return true;
	default:
		return false;
	}
}
#endif

static int
if_sendnetlink(struct dhcpcd_ctx *ctx, int protocol, struct nlmsghdr *hdr,
    int (*cb)(struct dhcpcd_ctx *, void *, struct nlmsghdr *), void *cbarg)
{
	int s;
	struct sockaddr_nl snl = { .nl_family = AF_NETLINK };
	struct iovec iov = { .iov_base = hdr, .iov_len = hdr->nlmsg_len };
	struct msghdr msg = {
	    .msg_name = &snl, .msg_namelen = sizeof(snl),
	    .msg_iov = &iov, .msg_iovlen = 1
	};
	struct priv *priv = (struct priv *)ctx->priv;
	unsigned char buf[16 * 1024];
	struct iovec riov = {
		.iov_base = buf,
		.iov_len = sizeof(buf),
	};

	/* Request a reply */
	hdr->nlmsg_flags |= NLM_F_ACK;
	hdr->nlmsg_seq = (uint32_t)++ctx->seq;
	if ((unsigned int)ctx->seq > UINT32_MAX)
		ctx->seq = 0;

#ifdef PRIVSEP
	if (ctx->options & DHCPCD_PRIVSEP && if_netlinkpriv(protocol, hdr))
		return (int)ps_root_sendnetlink(ctx, protocol, &msg);
#endif

	switch (protocol) {
	case NETLINK_ROUTE:
		if (hdr->nlmsg_type != RTM_GETADDR) {
			s = priv->route_fd;
			break;
		}
		/* FALLTHROUGH */
	case NETLINK_GENERIC:
		s = priv->generic_fd;
#if 0
#ifdef NETLINK_GET_STRICT_CHK
		if (hdr->nlmsg_type == RTM_GETADDR) {
			int on = 1;

			if (setsockopt(s, SOL_NETLINK, NETLINK_GET_STRICT_CHK,
			    &on, sizeof(on)) == -1 && errno != ENOPROTOOPT)
				logerr("%s: NETLINK_GET_STRICT_CHK", __func__);
		}
#endif
#endif
		break;
	default:
		errno = EINVAL;
		return -1;
	}

	if (sendmsg(s, &msg, 0) == -1)
		return -1;

	return if_getnetlink(ctx, &riov, s, 0, cb, cbarg);
}

#define NLMSG_TAIL(nmsg)						\
	((struct rtattr *)(((ptrdiff_t)(nmsg))+NLMSG_ALIGN((nmsg)->nlmsg_len)))

static int
add_attr_l(struct nlmsghdr *n, unsigned short maxlen, unsigned short type,
    const void *data, unsigned short alen)
{
	unsigned short len = (unsigned short)RTA_LENGTH(alen);
	struct rtattr *rta;

	if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) {
		errno = ENOBUFS;
		return -1;
	}

	rta = NLMSG_TAIL(n);
	rta->rta_type = type;
	rta->rta_len = len;
	if (alen)
		memcpy(RTA_DATA(rta), data, alen);
	n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);

	return 0;
}

static int
add_attr_8(struct nlmsghdr *n, unsigned short maxlen, unsigned short type,
    uint8_t data)
{

	return add_attr_l(n, maxlen, type, &data, sizeof(data));
}

static int
add_attr_32(struct nlmsghdr *n, unsigned short maxlen, unsigned short type,
    uint32_t data)
{
	unsigned short len = RTA_LENGTH(sizeof(data));
	struct rtattr *rta;

	if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) {
		errno = ENOBUFS;
		return -1;
	}

	rta = NLMSG_TAIL(n);
	rta->rta_type = type;
	rta->rta_len = len;
	memcpy(RTA_DATA(rta), &data, sizeof(data));
	n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;

	return 0;
}

static int
rta_add_attr_32(struct rtattr *rta, unsigned short maxlen,
    unsigned short type, uint32_t data)
{
	unsigned short len = RTA_LENGTH(sizeof(data));
	struct rtattr *subrta;

	if (RTA_ALIGN(rta->rta_len) + len > maxlen) {
		errno = ENOBUFS;
		return -1;
	}

	subrta = (void *)((char*)rta + RTA_ALIGN(rta->rta_len));
	subrta->rta_type = type;
	subrta->rta_len = len;
	memcpy(RTA_DATA(subrta), &data, sizeof(data));
	rta->rta_len = (unsigned short)(NLMSG_ALIGN(rta->rta_len) + len);
	return 0;
}

#ifdef HAVE_NL80211_H
static struct nlattr *
nla_next(struct nlattr *nla, size_t *rem)
{

	*rem -= (size_t)NLA_ALIGN(nla->nla_len);
	return (void *)((char *)nla + NLA_ALIGN(nla->nla_len));
}

#define NLA_TYPE(nla) ((nla)->nla_type & NLA_TYPE_MASK)
#define NLA_LEN(nla) (unsigned int)((nla)->nla_len - NLA_HDRLEN)
#define NLA_OK(nla, rem) \
	((rem) >= sizeof(struct nlattr) && \
	(nla)->nla_len >= sizeof(struct nlattr) && \
	(nla)->nla_len <= rem)
#define NLA_DATA(nla) (void *)((char *)(nla) + NLA_HDRLEN)
#define NLA_FOR_EACH_ATTR(pos, head, len, rem) \
	for (pos = head, rem = len; \
	     NLA_OK(pos, rem); \
	     pos = nla_next(pos, &(rem)))

struct nlmg
{
	struct nlmsghdr hdr;
	struct genlmsghdr ghdr;
	char buffer[64];
};

static int
nla_put_32(struct nlmsghdr *n, unsigned short maxlen,
    unsigned short type, uint32_t data)
{
	unsigned short len;
	struct nlattr *nla;

	len = NLA_ALIGN(NLA_HDRLEN + sizeof(data));
	if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) {
		errno = ENOBUFS;
		return -1;
	}

	nla = (struct nlattr *)NLMSG_TAIL(n);
	nla->nla_type = type;
	nla->nla_len = len;
	memcpy(NLA_DATA(nla), &data, sizeof(data));
	n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;

	return 0;
}

static int
nla_put_string(struct nlmsghdr *n, unsigned short maxlen,
    unsigned short type, const char *data)
{
	struct nlattr *nla;
	size_t len, sl;

	sl = strlen(data) + 1;
	len = NLA_ALIGN(NLA_HDRLEN + sl);
	if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) {
		errno = ENOBUFS;
		return -1;
	}

	nla = (struct nlattr *)NLMSG_TAIL(n);
	nla->nla_type = type;
	nla->nla_len = (unsigned short)len;
	memcpy(NLA_DATA(nla), data, sl);
	n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + (unsigned short)len;
	return 0;
}

static int
nla_parse(struct nlattr *tb[], struct nlattr *head, size_t len, int maxtype)
{
	struct nlattr *nla;
	size_t rem;
	int type;

	memset(tb, 0, sizeof(*tb) * ((unsigned int)maxtype + 1));
	NLA_FOR_EACH_ATTR(nla, head, len, rem) {
		type = NLA_TYPE(nla);
		if (type > maxtype)
			continue;
		tb[type] = nla;
	}
	return 0;
}

static int
genl_parse(struct nlmsghdr *nlm, struct nlattr *tb[], int maxtype)
{
	struct genlmsghdr *ghdr;
	struct nlattr *head;
	size_t len;

	ghdr = NLMSG_DATA(nlm);
	head = (void *)((char *)ghdr + GENL_HDRLEN);
	len = nlm->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN;
	return nla_parse(tb, head, len, maxtype);
}

static int
_gnl_getfamily(__unused struct dhcpcd_ctx *ctx, __unused void *arg,
    struct nlmsghdr *nlm)
{
	struct nlattr *tb[CTRL_ATTR_FAMILY_ID + 1];
	uint16_t family;

	if (genl_parse(nlm, tb, CTRL_ATTR_FAMILY_ID) == -1)
		return -1;
	if (tb[CTRL_ATTR_FAMILY_ID] == NULL) {
		errno = ENOENT;
		return -1;
	}
	memcpy(&family, NLA_DATA(tb[CTRL_ATTR_FAMILY_ID]), sizeof(family));
	return (int)family;
}

static int
gnl_getfamily(struct dhcpcd_ctx *ctx, const char *name)
{
	struct nlmg nlm;

	memset(&nlm, 0, sizeof(nlm));
	nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr));
	nlm.hdr.nlmsg_type = GENL_ID_CTRL;
	nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
	nlm.ghdr.cmd = CTRL_CMD_GETFAMILY;
	nlm.ghdr.version = 1;
	if (nla_put_string(&nlm.hdr, sizeof(nlm),
	    CTRL_ATTR_FAMILY_NAME, name) == -1)
		return -1;
	return if_sendnetlink(ctx, NETLINK_GENERIC, &nlm.hdr,
	    &_gnl_getfamily, NULL);
}

static int
_if_getssid_nl80211(__unused struct dhcpcd_ctx *ctx, void *arg,
    struct nlmsghdr *nlm)
{
	struct interface *ifp = arg;
	struct nlattr *tb[NL80211_ATTR_BSS + 1];
	struct nlattr *bss[NL80211_BSS_STATUS + 1];
	uint32_t status;
	unsigned char *ie;
	int ie_len;

	if (genl_parse(nlm, tb, NL80211_ATTR_BSS) == -1)
		return 0;

	if (tb[NL80211_ATTR_BSS] == NULL)
		return 0;

	if (nla_parse(bss,
	    NLA_DATA(tb[NL80211_ATTR_BSS]),
	    NLA_LEN(tb[NL80211_ATTR_BSS]),
	    NL80211_BSS_STATUS) == -1)
		return 0;

	if (bss[NL80211_BSS_BSSID] == NULL || bss[NL80211_BSS_STATUS] == NULL)
		return 0;

	memcpy(&status, NLA_DATA(bss[NL80211_BSS_STATUS]), sizeof(status));
	if (status != NL80211_BSS_STATUS_ASSOCIATED)
		return 0;

	if (bss[NL80211_BSS_INFORMATION_ELEMENTS] == NULL)
		return 0;

	ie = NLA_DATA(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
	ie_len = (int)NLA_LEN(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
	/* ie[0] is type, ie[1] is lenth, ie[2..] is data */
	while (ie_len >= 2 && ie_len >= ie[1]) {
		if (ie[0] == 0) {
			/* SSID */
			if (ie[1] > IF_SSIDLEN) {
				errno = ENOBUFS;
				return -1;
			}
			ifp->ssid_len = ie[1];
			memcpy(ifp->ssid, ie + 2, ifp->ssid_len);
			return (int)ifp->ssid_len;
		}
		ie_len -= ie[1] + 2;
		ie += ie[1] + 2;
	}

	return 0;
}

static int
if_getssid_nl80211(struct interface *ifp)
{
	int family;
	struct nlmg nlm;

	errno = 0;
	family = gnl_getfamily(ifp->ctx, "nl80211");
	if (family == -1)
		return -1;

	/* Is this a wireless interface? */
	memset(&nlm, 0, sizeof(nlm));
	nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr));
	nlm.hdr.nlmsg_type = (unsigned short)family;
	nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
	nlm.ghdr.cmd = NL80211_CMD_GET_WIPHY;
	nla_put_32(&nlm.hdr, sizeof(nlm), NL80211_ATTR_IFINDEX, ifp->index);
	if (if_sendnetlink(ifp->ctx, NETLINK_GENERIC, &nlm.hdr,
	    NULL, NULL) == -1)
		return -1;

	/* We need to parse out the list of scan results and find the one
	 * we are connected to. */
	memset(&nlm, 0, sizeof(nlm));
	nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr));
	nlm.hdr.nlmsg_type = (unsigned short)family;
	nlm.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
	nlm.ghdr.cmd = NL80211_CMD_GET_SCAN;
	nla_put_32(&nlm.hdr, sizeof(nlm), NL80211_ATTR_IFINDEX, ifp->index);

	return if_sendnetlink(ifp->ctx, NETLINK_GENERIC, &nlm.hdr,
	    &_if_getssid_nl80211, ifp);
}
#endif

int
if_getssid(struct interface *ifp)
{
	int r;

#ifdef HAVE_NL80211_H
	r = if_getssid_nl80211(ifp);
	if (r == -1)
		ifp->ssid_len = 0;
#else
	r = if_getssid_wext(ifp->name, ifp->ssid);
	if (r != -1)
		ifp->ssid_len = (unsigned int)r;
#endif

	ifp->ssid[ifp->ssid_len] = '\0';
	return r;
}

struct nlma
{
	struct nlmsghdr hdr;
	struct ifaddrmsg ifa;
	char buffer[64];
};

#ifdef INET
struct ifiaddr
{
	unsigned int ifa_ifindex;
	struct in_addr ifa_addr;
};

static int
_if_addressexists(__unused struct dhcpcd_ctx *ctx,
    void *arg, struct nlmsghdr *nlm)
{
	struct ifiaddr *ia = arg;
	in_addr_t this_addr;
	size_t len;
	struct rtattr *rta;
	struct ifaddrmsg *ifa;

	ifa = NLMSG_DATA(nlm);
	if (ifa->ifa_index != ia->ifa_ifindex || ifa->ifa_family != AF_INET)
		return 0;
	rta = IFA_RTA(ifa);
	len = NLMSG_PAYLOAD(nlm, sizeof(*ifa));
	for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
		switch (rta->rta_type) {
		case IFA_LOCAL:
			memcpy(&this_addr, RTA_DATA(rta), sizeof(this_addr));
			return this_addr == ia->ifa_addr.s_addr ? 1 : 0;
		}
	}
	return 0;
}

static int
if_addressexists(struct interface *ifp, struct in_addr *addr)
{
	struct ifiaddr ia = {
		.ifa_ifindex = ifp->index,
		.ifa_addr = *addr,
	};
	struct nlma nlm = {
	    .hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
	    .hdr.nlmsg_type = RTM_GETADDR,
	    .hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
	    .ifa.ifa_family = AF_INET,
	    .ifa.ifa_index = ifp->index,
	};

	return if_sendnetlink(ifp->ctx, NETLINK_ROUTE, &nlm.hdr,
	    &_if_addressexists, &ia);
}
#endif

struct nlmr
{
	struct nlmsghdr hdr;
	struct rtmsg rt;
	char buffer[256];
};

int
if_route(unsigned char cmd, const struct rt *rt)
{
	struct nlmr nlm;
	bool gateway_unspec;

	memset(&nlm, 0, sizeof(nlm));
	nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
	switch (cmd) {
	case RTM_CHANGE:
		nlm.hdr.nlmsg_type = RTM_NEWROUTE;
		nlm.hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_REPLACE;
		break;
	case RTM_ADD:
		nlm.hdr.nlmsg_type = RTM_NEWROUTE;
		nlm.hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL;
		break;
	case RTM_DELETE:
		nlm.hdr.nlmsg_type = RTM_DELROUTE;
		break;
	}
	nlm.hdr.nlmsg_flags |= NLM_F_REQUEST;
	nlm.rt.rtm_family = (unsigned char)rt->rt_dest.sa_family;
	nlm.rt.rtm_table = RT_TABLE_MAIN;

	gateway_unspec = sa_is_unspecified(&rt->rt_gateway);

	if (cmd == RTM_DELETE) {
		nlm.rt.rtm_scope = RT_SCOPE_NOWHERE;
	} else {
		/* Address generated routes are RTPROT_KERNEL,
		 * otherwise RTPROT_BOOT */
#ifdef RTPROT_RA
		if (rt->rt_dflags & RTDF_RA)
			nlm.rt.rtm_protocol = RTPROT_RA;
		else
#endif
#ifdef RTPROT_DHCP
		if (rt->rt_dflags & RTDF_DHCP)
			nlm.rt.rtm_protocol = RTPROT_DHCP;
		else
#endif
		if (rt->rt_dflags & RTDF_IFA_ROUTE)
			nlm.rt.rtm_protocol = RTPROT_KERNEL;
		else
			nlm.rt.rtm_protocol = RTPROT_BOOT;
		if (rt->rt_ifp->flags & IFF_LOOPBACK)
			nlm.rt.rtm_scope = RT_SCOPE_HOST;
		else if (gateway_unspec)
			nlm.rt.rtm_scope = RT_SCOPE_LINK;
		else
			nlm.rt.rtm_scope = RT_SCOPE_UNIVERSE;
		if (rt->rt_flags & RTF_REJECT)
			nlm.rt.rtm_type = RTN_UNREACHABLE;
		else
			nlm.rt.rtm_type = RTN_UNICAST;
	}

#define ADDSA(type, sa)							\
	add_attr_l(&nlm.hdr, sizeof(nlm), (type),			\
	    (const char *)(sa) + sa_addroffset((sa)),			\
	    (unsigned short)sa_addrlen((sa)));
	nlm.rt.rtm_dst_len = (unsigned char)sa_toprefix(&rt->rt_netmask);
	/* rt->rt_dest and rt->gateway are unions where sockaddr_in6
	 * is the biggest member. However, we access them as the
	 * generic sockaddr and coverity thinks this will overrun. */
	/* coverity[overrun-buffer-arg] */
	ADDSA(RTA_DST, &rt->rt_dest);
	if (cmd == RTM_ADD || cmd == RTM_CHANGE) {
		if (!gateway_unspec) {
			/* coverity[overrun-buffer-arg] */
			ADDSA(RTA_GATEWAY, &rt->rt_gateway);
		}
		/* Cannot add tentative source addresses.
		 * We don't know this here, so just skip INET6 ifa's.*/
		if (!sa_is_unspecified(&rt->rt_ifa) &&
		    rt->rt_ifa.sa_family != AF_INET6)
			ADDSA(RTA_PREFSRC, &rt->rt_ifa);
		if (rt->rt_mtu) {
			char metricsbuf[32];
			struct rtattr *metrics = (void *)metricsbuf;

			metrics->rta_type = RTA_METRICS;
			metrics->rta_len = RTA_LENGTH(0);
			rta_add_attr_32(metrics, sizeof(metricsbuf),
			    RTAX_MTU, rt->rt_mtu);
			add_attr_l(&nlm.hdr, sizeof(nlm), RTA_METRICS,
			    RTA_DATA(metrics),
			    (unsigned short)RTA_PAYLOAD(metrics));
		}

#ifdef HAVE_ROUTE_PREF
		if (rt->rt_dflags & RTDF_RA) {
			uint8_t pref;

			switch(rt->rt_pref) {
			case RTPREF_LOW:
				pref = ICMPV6_ROUTER_PREF_LOW;
				break;
			case RTPREF_MEDIUM:
				pref = ICMPV6_ROUTER_PREF_MEDIUM;
				break;
			case RTPREF_HIGH:
				pref = ICMPV6_ROUTER_PREF_HIGH;
				break;
			default:
				pref = ICMPV6_ROUTER_PREF_INVALID;
				break;
			}
			add_attr_8(&nlm.hdr, sizeof(nlm), RTA_PREF, pref);
		}
#endif
	}

	if (!sa_is_loopback(&rt->rt_gateway))
		add_attr_32(&nlm.hdr, sizeof(nlm), RTA_OIF, rt->rt_ifp->index);

	if (rt->rt_metric != 0)
		add_attr_32(&nlm.hdr, sizeof(nlm), RTA_PRIORITY,
		    rt->rt_metric);

	return if_sendnetlink(rt->rt_ifp->ctx, NETLINK_ROUTE, &nlm.hdr,
	    NULL, NULL);
}

static int
_if_initrt(struct dhcpcd_ctx *ctx, void *arg,
    struct nlmsghdr *nlm)
{
	struct rt rt, *rtn;
	rb_tree_t *kroutes = arg;

	if (if_copyrt(ctx, &rt, nlm) != 0)
		return 0;
	if ((rtn = rt_new(rt.rt_ifp)) == NULL) {
		logerr(__func__);
		return 0;
	}
	memcpy(rtn, &rt, sizeof(*rtn));
	if (rb_tree_insert_node(kroutes, rtn) != rtn)
		rt_free(rtn);
	return 0;
}

int
if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *kroutes, int af)
{
	struct nlmr nlm = {
	    .hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)),
	    .hdr.nlmsg_type = RTM_GETROUTE,
	    .hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH,
	    .rt.rtm_table = RT_TABLE_MAIN,
	    .rt.rtm_family = (unsigned char)af,
	};

	return if_sendnetlink(ctx, NETLINK_ROUTE, &nlm.hdr,
	    &_if_initrt, kroutes);
}

#ifdef INET
/* Linux is a special snowflake when it comes to BPF. */
const char *bpf_name = "Packet Socket";

/* Linux is a special snowflake for opening BPF. */
struct bpf *
bpf_open(const struct interface *ifp,
    int (*filter)(const struct bpf *, const struct in_addr *),
    const struct in_addr *ia)
{
	struct bpf *bpf;
	union sockunion {
		struct sockaddr sa;
		struct sockaddr_ll sll;
		struct sockaddr_storage ss;
	} su = {
		.sll = {
			.sll_family = PF_PACKET,
			.sll_protocol = htons(ETH_P_ALL),
			.sll_ifindex = (int)ifp->index,
		}
	};
#ifdef PACKET_AUXDATA
	int n;
#endif

	bpf = calloc(1, sizeof(*bpf));
	if (bpf == NULL)
		return NULL;
	bpf->bpf_ifp = ifp;

	/* Allocate a suitably large buffer for a single packet. */
	bpf->bpf_size = ETH_DATA_LEN;
	bpf->bpf_buffer = malloc(bpf->bpf_size);
	if (bpf->bpf_buffer == NULL)
		goto eexit;

	bpf->bpf_fd = xsocket(PF_PACKET, SOCK_RAW|SOCK_CXNB,htons(ETH_P_ALL));
	if (bpf->bpf_fd == -1)
		goto eexit;

	/* We cannot validate the correct interface,
	 * so we MUST set this first. */
	if (bind(bpf->bpf_fd, &su.sa, sizeof(su.sll)) == -1)
		goto eexit;

	if (filter(bpf, ia) != 0)
		goto eexit;

	/* In the ideal world, this would be set before the bind and filter. */
#ifdef PACKET_AUXDATA
	n = 1;
	if (setsockopt(bpf->bpf_fd, SOL_PACKET, PACKET_AUXDATA,
	    &n, sizeof(n)) != 0) {
		if (errno != ENOPROTOOPT)
			goto eexit;
	}
#endif

	/*
	 * At this point we could have received packets for the wrong
	 * interface or which don't pass the filter.
	 * Linux should flush upon setting the filter like every other OS.
	 * There is no way of flushing them from userland.
	 * As such, consumers need to inspect each packet to ensure it's valid.
	 * Or to put it another way, don't trust the Linux BPF filter.
	*/

	return bpf;

eexit:
	if (bpf->bpf_fd != -1)
		close(bpf->bpf_fd);
	free(bpf->bpf_buffer);
	free(bpf);
	return NULL;
}

/* BPF requires that we read the entire buffer.
 * So we pass the buffer in the API so we can loop on >1 packet. */
ssize_t
bpf_read(struct bpf *bpf, void *data, size_t len)
{
	ssize_t bytes;
	struct iovec iov = {
		.iov_base = bpf->bpf_buffer,
		.iov_len = bpf->bpf_size,
	};
	struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
#ifdef PACKET_AUXDATA
	union {
		struct cmsghdr hdr;
		uint8_t buf[CMSG_SPACE(sizeof(struct tpacket_auxdata))];
	} cmsgbuf = { .buf = { 0 } };
	struct cmsghdr *cmsg;
	struct tpacket_auxdata *aux;
#endif

#ifdef PACKET_AUXDATA
	msg.msg_control = cmsgbuf.buf;
	msg.msg_controllen = sizeof(cmsgbuf.buf);
#endif

	bytes = recvmsg(bpf->bpf_fd, &msg, 0);
	if (bytes == -1)
		return -1;
	bpf->bpf_flags |= BPF_EOF; /* We only ever read one packet. */
	bpf->bpf_flags &= ~BPF_PARTIALCSUM;
	if (bytes) {
		if (bpf_frame_bcast(bpf->bpf_ifp, bpf->bpf_buffer) == 0)
			bpf->bpf_flags |= BPF_BCAST;
		else
			bpf->bpf_flags &= ~BPF_BCAST;
		if ((size_t)bytes > len)
			bytes = (ssize_t)len;
		memcpy(data, bpf->bpf_buffer, (size_t)bytes);
#ifdef PACKET_AUXDATA
		for (cmsg = CMSG_FIRSTHDR(&msg);
		     cmsg;
		     cmsg = CMSG_NXTHDR(&msg, cmsg))
		{
			if (cmsg->cmsg_level == SOL_PACKET &&
			    cmsg->cmsg_type == PACKET_AUXDATA) {
				aux = (void *)CMSG_DATA(cmsg);
				if (aux->tp_status & TP_STATUS_CSUMNOTREADY)
					bpf->bpf_flags |= BPF_PARTIALCSUM;
			}
		}
#endif
	}
	return bytes;
}

int
bpf_attach(int s, void *filter, unsigned int filter_len)
{
	struct sock_fprog pf = {
		.filter = filter,
		.len = (unsigned short)filter_len,
	};

	/* Install the filter. */
	if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)) == -1)
		return -1;

#ifdef SO_LOCK_FILTER
	int on = 1;

	if (setsockopt(s, SOL_SOCKET, SO_LOCK_FILTER, &on, sizeof(on)) == -1)
		return -1;
#endif

	return 0;
}

int
if_address(unsigned char cmd, const struct ipv4_addr *ia)
{
	struct nlma nlm;
	struct ifa_cacheinfo cinfo;
	int retval = 0;
#ifdef IFA_F_NOPREFIXROUTE
	uint32_t flags = 0;
#endif

	memset(&nlm, 0, sizeof(nlm));
	nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
	nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
	nlm.hdr.nlmsg_type = cmd;
	if (cmd == RTM_NEWADDR)
		nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE;
	nlm.ifa.ifa_index = ia->iface->index;
	nlm.ifa.ifa_family = AF_INET;

	nlm.ifa.ifa_prefixlen = inet_ntocidr(ia->mask);

#if 0
	/* This creates the aliased interface */
	add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL,
	    ia->iface->alias,
	    (unsigned short)(strlen(ia->iface->alias) + 1));
#endif

	add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LOCAL,
	    &ia->addr.s_addr, sizeof(ia->addr.s_addr));

	if (cmd == RTM_NEWADDR) {
#ifdef IFA_F_NOPREFIXROUTE
		if (nlm.ifa.ifa_prefixlen < 32)
			flags |= IFA_F_NOPREFIXROUTE;
		add_attr_32(&nlm.hdr, sizeof(nlm), IFA_FLAGS, flags);
#endif

		add_attr_l(&nlm.hdr, sizeof(nlm), IFA_BROADCAST,
		    &ia->brd.s_addr, sizeof(ia->brd.s_addr));

		memset(&cinfo, 0, sizeof(cinfo));
		cinfo.ifa_prefered = ia->pltime;
		cinfo.ifa_valid = ia->vltime;
		add_attr_l(&nlm.hdr, sizeof(nlm), IFA_CACHEINFO,
		    &cinfo, sizeof(cinfo));
	}

	if (if_sendnetlink(ia->iface->ctx, NETLINK_ROUTE, &nlm.hdr,
	    NULL, NULL) == -1)
		retval = -1;
	return retval;
}

int
if_addrflags(__unused const struct interface *ifp,
__unused const struct in_addr *addr, __unused const char *alias)
{

	/* Linux has no support for IPv4 address flags */
	return 0;
}
#endif

#ifdef INET6
int
if_address6(unsigned char cmd, const struct ipv6_addr *ia)
{
	struct nlma nlm;
	struct ifa_cacheinfo cinfo;
#if defined(IFA_F_MANAGETEMPADDR) || defined(IFA_F_NOPREFIXROUTE)
	uint32_t flags = 0;
#endif

	memset(&nlm, 0, sizeof(nlm));
	nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
	nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
	nlm.hdr.nlmsg_type = cmd;
	if (cmd == RTM_NEWADDR)
		nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE;
	nlm.ifa.ifa_index = ia->iface->index;
	nlm.ifa.ifa_family = AF_INET6;

	/* Add as /128 if no IFA_F_NOPREFIXROUTE ? */
	nlm.ifa.ifa_prefixlen = ia->prefix_len;

#if 0
	/* This creates the aliased interface */
	add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL,
	    ia->iface->alias, (unsigned short)(strlen(ia->iface->alias) + 1));
#endif
	add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LOCAL,
	    &ia->addr.s6_addr, sizeof(ia->addr.s6_addr));

	if (cmd == RTM_NEWADDR) {
#ifdef IPV6_MANAGETEMPADDR
		if (ia->flags & IPV6_AF_TEMPORARY) {
			/* Currently the kernel filters out these flags */
#ifdef IFA_F_NOPREFIXROUTE
			flags |= IFA_F_TEMPORARY;
#else
			nlm.ifa.ifa_flags |= IFA_F_TEMPORARY;
#endif
		}
#elif IFA_F_MANAGETEMPADDR
		if (ia->flags & IPV6_AF_AUTOCONF && IA6_CANAUTOCONF(ia))
			flags |= IFA_F_MANAGETEMPADDR;
#endif
#ifdef IFA_F_NOPREFIXROUTE
		if (!IN6_IS_ADDR_LINKLOCAL(&ia->addr))
			flags |= IFA_F_NOPREFIXROUTE;
#endif
#if defined(IFA_F_MANAGETEMPADDR) || defined(IFA_F_NOPREFIXROUTE)
		add_attr_32(&nlm.hdr, sizeof(nlm), IFA_FLAGS, flags);
#endif

		memset(&cinfo, 0, sizeof(cinfo));
		cinfo.ifa_prefered = ia->prefix_pltime;
		cinfo.ifa_valid = ia->prefix_vltime;
		add_attr_l(&nlm.hdr, sizeof(nlm), IFA_CACHEINFO,
		    &cinfo, sizeof(cinfo));
	}

	return if_sendnetlink(ia->iface->ctx, NETLINK_ROUTE, &nlm.hdr,
	    NULL, NULL);
}

int
if_addrflags6(const struct interface *ifp, const struct in6_addr *addr,
    __unused const char *alias)
{
	char buf[PS_BUFLEN], *bp = buf, *line;
	ssize_t buflen;
	char *p, ifaddress[33], address[33], name[IF_NAMESIZE + 1];
	unsigned int ifindex;
	int prefix, scope, flags, i;

	buflen = dhcp_readfile(ifp->ctx, PROC_INET6, buf, sizeof(buf));
	if (buflen == -1)
		return -1;
	if ((size_t)buflen == sizeof(buf)) {
		errno = ENOBUFS;
		return -1;
	}

	p = ifaddress;
	for (i = 0; i < (int)sizeof(addr->s6_addr); i++) {
		p += snprintf(p, 3, "%.2x", addr->s6_addr[i]);
	}
	*p = '\0';

	while ((line = get_line(&bp, &buflen)) != NULL) {
		if (sscanf(line,
		    "%32[a-f0-9] %x %x %x %x %"TOSTRING(IF_NAMESIZE)"s\n",
		    address, &ifindex, &prefix, &scope, &flags, name) != 6 ||
		    strlen(address) != 32)
		{
			errno = EINVAL;
			return -1;
		}
		if (strcmp(name, ifp->name) == 0 &&
		    strcmp(ifaddress, address) == 0)
			return flags;
	}

	errno = ESRCH;
	return -1;
}

int
if_getlifetime6(__unused struct ipv6_addr *ia)
{

	/* God knows how to work out address lifetimes on Linux */
	errno = ENOTSUP;
	return -1;
}

struct nlml
{
	struct nlmsghdr hdr;
	struct ifinfomsg i;
	char buffer[32];
};

#ifdef HAVE_IN6_ADDR_GEN_MODE_NONE
static struct rtattr *
add_attr_nest(struct nlmsghdr *n, unsigned short maxlen, unsigned short type)
{
	struct rtattr *nest;

	nest = NLMSG_TAIL(n);
	add_attr_l(n, maxlen, type, NULL, 0);
	return nest;
}

static void
add_attr_nest_end(struct nlmsghdr *n, struct rtattr *nest)
{

	nest->rta_len = (unsigned short)((char *)NLMSG_TAIL(n) - (char *)nest);
}
#endif

static int
if_disable_autolinklocal(struct dhcpcd_ctx *ctx, unsigned int ifindex)
{
#ifdef HAVE_IN6_ADDR_GEN_MODE_NONE
	struct nlml nlm;
	struct rtattr *afs, *afs6;

	memset(&nlm, 0, sizeof(nlm));
	nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
	nlm.hdr.nlmsg_type = RTM_NEWLINK;
	nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
	nlm.i.ifi_family = AF_INET6;
	nlm.i.ifi_index = (int)ifindex;
	afs = add_attr_nest(&nlm.hdr, sizeof(nlm), IFLA_AF_SPEC);
	afs6 = add_attr_nest(&nlm.hdr, sizeof(nlm), AF_INET6);
	add_attr_8(&nlm.hdr, sizeof(nlm), IFLA_INET6_ADDR_GEN_MODE,
	    IN6_ADDR_GEN_MODE_NONE);
	add_attr_nest_end(&nlm.hdr, afs6);
	add_attr_nest_end(&nlm.hdr, afs);

	return if_sendnetlink(ctx, NETLINK_ROUTE, &nlm.hdr, NULL, NULL);
#else
	UNUSED(ctx);
	UNUSED(ifindex);
	errno = ENOTSUP;
	return -1;
#endif
}

static const char *p_conf = "/proc/sys/net/ipv6/conf";
static const char *p_neigh = "/proc/sys/net/ipv6/neigh";

void
if_setup_inet6(const struct interface *ifp)
{
	struct dhcpcd_ctx *ctx = ifp->ctx;
	int ra;
	char path[256];

	/* The kernel cannot make stable private addresses.
	 * However, a lot of distros ship newer kernel headers than
	 * the kernel itself so sweep that error under the table. */
	if (if_disable_autolinklocal(ctx, ifp->index) == -1 &&
	    errno != ENODEV && errno != ENOTSUP && errno != EINVAL)
		logdebug("%s: if_disable_autolinklocal", ifp->name);

	/*
	 * If not doing autoconf, don't disable the kernel from doing it.
	 * If we need to, we should have another option actively disable it.
	 */
	if (!(ifp->options->options & DHCPCD_IPV6RS))
		return;

	snprintf(path, sizeof(path), "%s/%s/autoconf", p_conf, ifp->name);
	ra = check_proc_int(ctx, path);
	if (ra != 1 && ra != -1) {
		if (if_writepathuint(ctx, path, 0) == -1)
			logerr("%s: %s", __func__, path);
	}

	snprintf(path, sizeof(path), "%s/%s/accept_ra", p_conf, ifp->name);
	ra = check_proc_int(ctx, path);
	if (ra == -1) {
		/* The sysctl probably doesn't exist, but this isn't an
		 * error as such so just log it and continue */
		if (errno != ENOENT)
			logerr("%s: %s", __func__, path);
	} else if (ra != 0) {
		if (if_writepathuint(ctx, path, 0) == -1)
			logerr("%s: %s", __func__, path);
	}
}

int
if_applyra(const struct ra *rap)
{
	char path[256];
	const char *ifname = rap->iface->name;
	struct dhcpcd_ctx *ctx = rap->iface->ctx;
	int error = 0;

	if (rap->hoplimit != 0) {
		snprintf(path, sizeof(path), "%s/%s/hop_limit", p_conf, ifname);
		if (if_writepathuint(ctx, path, rap->hoplimit) == -1)
			error = -1;
	}

	if (rap->retrans != 0) {
		snprintf(path, sizeof(path), "%s/%s/retrans_time_ms",
		    p_neigh, ifname);
		if (if_writepathuint(ctx, path, rap->retrans) == -1)
			error = -1;
	}

	if (rap->reachable != 0) {
		snprintf(path, sizeof(path), "%s/%s/base_reachable_time_ms",
		    p_neigh, ifname);
		if (if_writepathuint(ctx, path, rap->reachable) == -1)
			error = -1;
	}

	return error;
}

int
ip6_forwarding(const char *ifname)
{
	char path[256], buf[64];
	int error, i;

	if (ifname == NULL)
		ifname = "all";
	snprintf(path, sizeof(path), "%s/%s/forwarding", p_conf, ifname);
	if (readfile(path, buf, sizeof(buf)) == -1)
		return 0;
	i = (int)strtoi(buf, NULL, 0, INT_MIN, INT_MAX, &error);
	if (error != 0 && error != ENOTSUP)
		return 0;
	return i;
}

#endif /* INET6 */