view src/if-sun.c @ 5557:e65d193a1960 draft

Linux: Support wireless IP roaming This is achieved by checking that the interface is wireless, IFF_UP and IFF_LOWER_UP are present, but IFF_RUNNING is missing. This gives exactly the same support as modern NetBSD when carrier loss is detected, but without the address verifications when the carrier comes back as that needs to be handled in the kernel. While IP setup is maintained, other configuration data is discarded. Note that this should be improved in the future. Thanks to Boris Krasnovskiy <borkra@gmail.com> for helping with this.
author Roy Marples <roy@marples.name>
date Sat, 12 Dec 2020 13:12:26 +0000
parents 8e2b8ce8c972
children
line wrap: on
line source

/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Solaris interface driver for dhcpcd
 * Copyright (c) 2016-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 <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <libdlpi.h>
#include <kstat.h>
#include <stddef.h>
#include <stdlib.h>
#include <stropts.h>
#include <string.h>
#include <unistd.h>

#include <inet/ip.h>

#include <net/if_dl.h>
#include <net/if_types.h>

#include <netinet/if_ether.h>
#include <netinet/udp.h>

#include <sys/ioctl.h>
#include <sys/mac.h>
#include <sys/pfmod.h>
#include <sys/tihdr.h>
#include <sys/utsname.h>

/* Private libsocket interface we can hook into to get
 * a better getifaddrs(3).
 * From libsocket_priv.h, which is not always distributed so is here. */
extern int getallifaddrs(sa_family_t, struct ifaddrs **, int64_t);

#include "config.h"
#include "bpf.h"
#include "common.h"
#include "dhcp.h"
#include "if.h"
#include "if-options.h"
#include "ipv4.h"
#include "ipv6.h"
#include "ipv6nd.h"
#include "logerr.h"
#include "route.h"
#include "sa.h"

#ifndef ARP_MOD_NAME
#  define ARP_MOD_NAME        "arp"
#endif

#ifndef RT_ROUNDUP
#define RT_ROUNDUP(a)                                                        \
       ((a) > 0 ? (1 + (((a) - 1) | (sizeof(int32_t) - 1))) : sizeof(int32_t))
#define RT_ADVANCE(x, n) ((x) += RT_ROUNDUP(sa_len((n))))
#endif

#define COPYOUT(sin, sa) do {						      \
	if ((sa) && ((sa)->sa_family == AF_INET))			      \
		(sin) = ((const struct sockaddr_in *)(const void *)	      \
		    (sa))->sin_addr;					      \
	} while (0)

#define COPYOUT6(sin, sa) do {						      \
	if ((sa) && ((sa)->sa_family == AF_INET6))			      \
		(sin) = ((const struct sockaddr_in6 *)(const void *)	      \
		    (sa))->sin6_addr;					      \
	} while (0)

#define COPYSA(dst, src) memcpy((dst), (src), sa_len((src)))

struct priv {
#ifdef INET6
	int pf_inet6_fd;
#endif
};

struct rtm
{
	struct rt_msghdr hdr;
	char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX];
};

static int if_plumb(int, const struct dhcpcd_ctx *, int, const char *);

int
os_init(void)
{
	return 0;
}

int
if_init(struct interface *ifp)
{

#ifdef INET
	if (if_plumb(RTM_NEWADDR, ifp->ctx, AF_INET, ifp->name) == -1 &&
	    errno != EEXIST)
		return -1;
#endif

#ifdef INET6
	if (if_plumb(RTM_NEWADDR, ifp->ctx, AF_INET6, ifp->name) == -1 &&
	    errno != EEXIST)
		return -1;
#endif

	if (ifp->index == 0)
		ifp->index = if_nametoindex(ifp->name);

	return 0;
}

int
if_conf(__unused struct interface *ifp)
{

	return 0;
}

int
if_opensockets_os(struct dhcpcd_ctx *ctx)
{
	struct priv		*priv;
	int			n;

	if ((priv = malloc(sizeof(*priv))) == NULL)
		return -1;
	ctx->priv = priv;

#ifdef INET6
	priv->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
	/* Don't return an error so we at least work on kernels witout INET6
	 * even though we expect INET6 support.
	 * We will fail noisily elsewhere anyway. */
#endif

	ctx->link_fd = socket(PF_ROUTE,
	    SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);

	if (ctx->link_fd == -1) {
		free(ctx->priv);
		return -1;
	}

	/* Ignore our own route(4) messages.
	 * Sadly there is no way of doing this for route(4) messages
	 * generated from addresses we add/delete. */
	n = 0;
	if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_USELOOPBACK,
	    &n, sizeof(n)) == -1)
		logerr("%s: SO_USELOOPBACK", __func__);

	return 0;
}

void
if_closesockets_os(struct dhcpcd_ctx *ctx)
{
#ifdef INET6
	struct priv		*priv;

	priv = (struct priv *)ctx->priv;
	if (priv->pf_inet6_fd != -1)
		close(priv->pf_inet6_fd);
#endif

	/* each interface should have closed itself */
	free(ctx->priv);
}

int
if_setmac(struct interface *ifp, void *mac, uint8_t maclen)
{

	errno = ENOTSUP;
	return -1;
}

int
if_carrier(struct interface *ifp, __unused const void *ifadata)
{
	kstat_ctl_t		*kcp;
	kstat_t			*ksp;
	kstat_named_t		*knp;
	link_state_t		linkstate;

	if (if_getflags(ifp) == -1)
		return LINK_UNKNOWN;

	kcp = kstat_open();
	if (kcp == NULL)
		goto err;
	ksp = kstat_lookup(kcp, UNCONST("link"), 0, ifp->name);
	if (ksp == NULL)
		goto err;
	if (kstat_read(kcp, ksp, NULL) == -1)
		goto err;
	knp = kstat_data_lookup(ksp, UNCONST("link_state"));
	if (knp == NULL)
		goto err;
	if (knp->data_type != KSTAT_DATA_UINT32)
		goto err;
	linkstate = (link_state_t)knp->value.ui32;
	kstat_close(kcp);

	switch (linkstate) {
	case LINK_STATE_UP:
		ifp->flags |= IFF_UP;
		return LINK_UP;
	case LINK_STATE_DOWN:
		return LINK_DOWN;
	default:
		return LINK_UNKNOWN;
	}

err:
	if (kcp != NULL)
		kstat_close(kcp);
	return LINK_UNKNOWN;
}

bool
if_roaming(__unused struct interface *ifp)
{

	return false;
}

int
if_mtu_os(const struct interface *ifp)
{
	dlpi_handle_t		dh;
	dlpi_info_t		dlinfo;
	int			mtu;

	if (dlpi_open(ifp->name, &dh, 0) != DLPI_SUCCESS)
		return -1;
	if (dlpi_info(dh, &dlinfo, 0) == DLPI_SUCCESS)
		mtu = dlinfo.di_max_sdu;
	else
		mtu = -1;
	dlpi_close(dh);
	return mtu;
}

int
if_getssid(__unused struct interface *ifp)
{

	errno = ENOTSUP;
	return -1;
}

/* XXX work out TAP interfaces? */
bool
if_ignore(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname)
{

	return false;
}

unsigned short
if_vlanid(__unused const struct interface *ifp)
{

	return 0;
}

int
if_vimaster(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname)
{

	return 0;
}

int
if_machinearch(__unused char *str, __unused size_t len)
{

	/* There is no extra data really.
	 * isainfo -v does return amd64, but also i386. */
	return 0;
}

struct linkwalk {
	struct ifaddrs		*lw_ifa;
	int			lw_error;
};

static boolean_t
if_newaddr(const char *ifname, void *arg)
{
	struct linkwalk		*lw = arg;
	int error;
	struct ifaddrs		*ifa;
	dlpi_handle_t		dh;
	dlpi_info_t		dlinfo;
	uint8_t			pa[DLPI_PHYSADDR_MAX];
	size_t			pa_len;
	struct sockaddr_dl	*sdl;

	ifa = NULL;
	error = dlpi_open(ifname, &dh, 0);
	if (error == DLPI_ENOLINK) /* Just vanished or in global zone */
		return B_FALSE;
	if (error != DLPI_SUCCESS)
		goto failed1;
	if (dlpi_info(dh, &dlinfo, 0) != DLPI_SUCCESS)
		goto failed;

	/* For some reason, dlpi_info won't return the
	 * physical address, it's all zero's.
	 * So cal dlpi_get_physaddr. */
	pa_len = DLPI_PHYSADDR_MAX;
	if (dlpi_get_physaddr(dh, DL_CURR_PHYS_ADDR,
	    pa, &pa_len) != DLPI_SUCCESS)
		goto failed;

	if ((ifa = calloc(1, sizeof(*ifa))) == NULL)
		goto failed;
	if ((ifa->ifa_name = strdup(ifname)) == NULL)
		goto failed;
	if ((sdl = calloc(1, sizeof(*sdl))) == NULL)
		goto failed;

	ifa->ifa_addr = (struct sockaddr *)sdl;
	sdl->sdl_index = if_nametoindex(ifname);
	sdl->sdl_family = AF_LINK;
	switch (dlinfo.di_mactype) {
	case DL_ETHER:
		sdl->sdl_type = IFT_ETHER;
		break;
	case DL_IB:
		sdl->sdl_type = IFT_IB;
		break;
	default:
		sdl->sdl_type = IFT_OTHER;
		break;
	}

	sdl->sdl_alen = pa_len;
	memcpy(sdl->sdl_data, pa, pa_len);

	ifa->ifa_next = lw->lw_ifa;
	lw->lw_ifa = ifa;
	dlpi_close(dh);
	return B_FALSE;

failed:
	dlpi_close(dh);
	if (ifa != NULL) {
		free(ifa->ifa_name);
		free(ifa->ifa_addr);
		free(ifa);
	}
failed1:
	lw->lw_error = errno;
	return B_TRUE;
}

/* Creates an empty sockaddr_dl for lo0. */
static struct ifaddrs *
if_ifa_lo0(void)
{
	struct ifaddrs		*ifa;
	struct sockaddr_dl	*sdl;

	if ((ifa = calloc(1, sizeof(*ifa))) == NULL)
		return NULL;
	if ((sdl = calloc(1, sizeof(*sdl))) == NULL) {
		free(ifa);
		return NULL;
	}
	if ((ifa->ifa_name = strdup("lo0")) == NULL) {
		free(ifa);
		free(sdl);
		return NULL;
	}

	ifa->ifa_addr = (struct sockaddr *)sdl;
	ifa->ifa_flags = IFF_LOOPBACK;
	sdl->sdl_family = AF_LINK;
	sdl->sdl_index = if_nametoindex("lo0");

	return ifa;
}

/* getifaddrs(3) does not support AF_LINK, strips aliases and won't
 * report addresses that are not UP.
 * As such it's just totally useless, so we need to roll our own. */
int
if_getifaddrs(struct ifaddrs **ifap)
{
	struct linkwalk		lw;
	struct ifaddrs		*ifa;

	/* Private libc function which we should not have to call
	 * to get non UP addresses. */
	if (getallifaddrs(AF_UNSPEC, &lw.lw_ifa, 0) == -1)
		return -1;

	/* Start with some AF_LINK addresses. */
	lw.lw_error = 0;
	dlpi_walk(if_newaddr, &lw, 0);
	if (lw.lw_error != 0) {
		freeifaddrs(lw.lw_ifa);
		errno = lw.lw_error;
		return -1;
	}

	/* lo0 doesn't appear in dlpi_walk, so fudge it. */
	if ((ifa = if_ifa_lo0()) == NULL) {
		freeifaddrs(lw.lw_ifa);
		return -1;
	}
	ifa->ifa_next = lw.lw_ifa;

	*ifap = ifa;
	return 0;
}

static void
if_linkaddr(struct sockaddr_dl *sdl, const struct interface *ifp)
{

	memset(sdl, 0, sizeof(*sdl));
	sdl->sdl_family = AF_LINK;
	sdl->sdl_nlen = sdl->sdl_alen = sdl->sdl_slen = 0;
	sdl->sdl_index = (unsigned short)ifp->index;
}

static int
get_addrs(int type, const void *data, size_t data_len,
    const struct sockaddr **sa)
{
	const char *cp, *ep;
	int i;

	cp = data;
	ep = cp + data_len;
	for (i = 0; i < RTAX_MAX; i++) {
		if (type & (1 << i)) {
			if (cp >= ep) {
				errno = EINVAL;
				return -1;
			}
			sa[i] = (const struct sockaddr *)cp;
			RT_ADVANCE(cp, sa[i]);
		} else
			sa[i] = NULL;
	}

	return 0;
}

static struct interface *
if_findsdl(struct dhcpcd_ctx *ctx, const struct sockaddr_dl *sdl)
{

	if (sdl->sdl_index)
		return if_findindex(ctx->ifaces, sdl->sdl_index);

	if (sdl->sdl_nlen) {
		char ifname[IF_NAMESIZE];

		memcpy(ifname, sdl->sdl_data, sdl->sdl_nlen);
		ifname[sdl->sdl_nlen] = '\0';
		return if_find(ctx->ifaces, ifname);
	}
	if (sdl->sdl_alen) {
		struct interface *ifp;

		TAILQ_FOREACH(ifp, ctx->ifaces, next) {
			if (ifp->hwlen == sdl->sdl_alen &&
			    memcmp(ifp->hwaddr,
			    sdl->sdl_data, sdl->sdl_alen) == 0)
				return ifp;
		}
	}

	errno = ENOENT;
	return NULL;
}

static struct interface *
if_findsa(struct dhcpcd_ctx *ctx, const struct sockaddr *sa)
{
	if (sa == NULL) {
		errno = EINVAL;
		return NULL;
	}

	switch (sa->sa_family) {
	case AF_LINK:
	{
		const struct sockaddr_dl *sdl;

		sdl = (const void *)sa;
		return if_findsdl(ctx, sdl);
	}
#ifdef INET
	case AF_INET:
	{
		const struct sockaddr_in *sin;
		struct ipv4_addr *ia;

		sin = (const void *)sa;
		if ((ia = ipv4_findmaskaddr(ctx, &sin->sin_addr)))
			return ia->iface;
		break;
	}
#endif
#ifdef INET6
	case AF_INET6:
	{
		const struct sockaddr_in6 *sin;
		struct ipv6_addr *ia;

		sin = (const void *)sa;
		if ((ia = ipv6_findmaskaddr(ctx, &sin->sin6_addr)))
			return ia->iface;
		break;
	}
#endif
	default:
		errno = EAFNOSUPPORT;
		return NULL;
	}

	errno = ENOENT;
	return NULL;
}

static void
if_route0(struct dhcpcd_ctx *ctx, struct rtm *rtmsg,
    unsigned char cmd, const struct rt *rt)
{
	struct rt_msghdr *rtm;
	char *bp = rtmsg->buffer;
	socklen_t sl;
	bool gateway_unspec;

	/* WARNING: Solaris will not allow you to delete RTF_KERNEL routes.
	 * This includes subnet/prefix routes. */

#define ADDSA(sa) do {							\
		sl = sa_len((sa));					\
		memcpy(bp, (sa), sl);					\
		bp += RT_ROUNDUP(sl);					\
	} while (/* CONSTCOND */ 0)

	memset(rtmsg, 0, sizeof(*rtmsg));
	rtm = &rtmsg->hdr;
	rtm->rtm_version = RTM_VERSION;
	rtm->rtm_type = cmd;
	rtm->rtm_seq = ++ctx->seq;
	rtm->rtm_flags = rt->rt_flags;
	rtm->rtm_addrs = RTA_DST | RTA_GATEWAY;

	gateway_unspec = sa_is_unspecified(&rt->rt_gateway);

	if (cmd == RTM_ADD || cmd == RTM_CHANGE) {
		bool netmask_bcast = sa_is_allones(&rt->rt_netmask);

		rtm->rtm_flags |= RTF_UP;
		if (!(rtm->rtm_flags & RTF_REJECT) &&
		    !sa_is_loopback(&rt->rt_gateway))
		{
			rtm->rtm_addrs |= RTA_IFP;
			/* RTA_IFA is currently ignored by the kernel.
			 * RTA_SRC and RTF_SETSRC look like what we want,
			 * but they don't work with RTF_GATEWAY.
			 * We set RTA_IFA just in the hope that the
			 * kernel will one day support this. */
			if (!sa_is_unspecified(&rt->rt_ifa))
				rtm->rtm_addrs |= RTA_IFA;
		}

		if (netmask_bcast)
			rtm->rtm_flags |= RTF_HOST;
		else if (!gateway_unspec)
			rtm->rtm_flags |= RTF_GATEWAY;

		if (rt->rt_dflags & RTDF_STATIC)
			rtm->rtm_flags |= RTF_STATIC;

		if (rt->rt_mtu != 0) {
			rtm->rtm_inits |= RTV_MTU;
			rtm->rtm_rmx.rmx_mtu = rt->rt_mtu;
		}
	}

	if (!(rtm->rtm_flags & RTF_HOST))
		rtm->rtm_addrs |= RTA_NETMASK;

	ADDSA(&rt->rt_dest);

	if (gateway_unspec)
		ADDSA(&rt->rt_ifa);
	else
		ADDSA(&rt->rt_gateway);

	if (rtm->rtm_addrs & RTA_NETMASK)
		ADDSA(&rt->rt_netmask);

	if (rtm->rtm_addrs & RTA_IFP) {
		struct sockaddr_dl sdl;

		if_linkaddr(&sdl, rt->rt_ifp);
		ADDSA((struct sockaddr *)&sdl);
	}

	if (rtm->rtm_addrs & RTA_IFA)
		ADDSA(&rt->rt_ifa);

#if 0
	if (rtm->rtm_addrs & RTA_SRC)
		ADDSA(&rt->rt_ifa);
#endif

	rtm->rtm_msglen = (unsigned short)(bp - (char *)rtm);
}

int
if_route(unsigned char cmd, const struct rt *rt)
{
	struct rtm rtm;
	struct dhcpcd_ctx *ctx = rt->rt_ifp->ctx;

	if_route0(ctx, &rtm, cmd, rt);

	if (write(ctx->link_fd, &rtm, rtm.hdr.rtm_msglen) == -1)
		return -1;
	return 0;
}

static int
if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, const struct rt_msghdr *rtm)
{
	const struct sockaddr *rti_info[RTAX_MAX];

	if (~rtm->rtm_addrs & RTA_DST) {
		errno = EINVAL;
		return -1;
	}

	if (get_addrs(rtm->rtm_addrs, (const char *)rtm + sizeof(*rtm),
		      rtm->rtm_msglen - sizeof(*rtm), rti_info) == -1)
		return -1;

	memset(rt, 0, sizeof(*rt));

	rt->rt_flags = (unsigned int)rtm->rtm_flags;
	COPYSA(&rt->rt_dest, rti_info[RTAX_DST]);
	if (rtm->rtm_addrs & RTA_NETMASK)
		COPYSA(&rt->rt_netmask, rti_info[RTAX_NETMASK]);

	/* dhcpcd likes an unspecified gateway to indicate via the link.
	 * However we need to know if gateway was a link with an address. */
	if (rtm->rtm_addrs & RTA_GATEWAY) {
		if (rti_info[RTAX_GATEWAY]->sa_family == AF_LINK) {
			const struct sockaddr_dl *sdl;

			sdl = (const struct sockaddr_dl*)
			    (const void *)rti_info[RTAX_GATEWAY];
			if (sdl->sdl_alen != 0)
				rt->rt_dflags |= RTDF_GATELINK;
		} else if (rtm->rtm_flags & RTF_GATEWAY)
			COPYSA(&rt->rt_gateway, rti_info[RTAX_GATEWAY]);
	}

	if (rtm->rtm_addrs & RTA_SRC)
		COPYSA(&rt->rt_ifa, rti_info[RTAX_SRC]);
	rt->rt_mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu;

	if (rtm->rtm_index)
		rt->rt_ifp = if_findindex(ctx->ifaces, rtm->rtm_index);
	else if (rtm->rtm_addrs & RTA_IFP)
		rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_IFP]);
	else if (rtm->rtm_addrs & RTA_GATEWAY)
		rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_GATEWAY]);
	else
		rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_DST]);

	if (rt->rt_ifp == NULL && rtm->rtm_type == RTM_MISS)
		rt->rt_ifp = if_loopback(ctx);

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

	return 0;
}

static struct rt *
if_route_get(struct dhcpcd_ctx *ctx, struct rt *rt)
{
	struct rtm rtm;
	int s;
	struct iovec iov = { .iov_base = &rtm, .iov_len = sizeof(rtm) };
	struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
	ssize_t len;
	struct rt *rtw = rt;

	if_route0(ctx, &rtm, RTM_GET, rt);
	rt = NULL;
	s = socket(PF_ROUTE, SOCK_RAW | SOCK_CLOEXEC, 0);
	if (s == -1)
		return NULL;
	if (write(s, &rtm, rtm.hdr.rtm_msglen) == -1)
		goto out;
	if ((len = recvmsg(s, &msg, 0)) == -1)
		goto out;
	if ((size_t)len < sizeof(rtm.hdr) || len < rtm.hdr.rtm_msglen) {
		errno = EINVAL;
		goto out;
	}
	if (if_copyrt(ctx, rtw, &rtm.hdr) == -1)
		goto out;
	rt = rtw;

out:
	close(s);
	return rt;
}

static int
if_finishrt(struct dhcpcd_ctx *ctx, struct rt *rt)
{
	int mtu;

	/* Solaris has a subnet route with the gateway
	 * of the owning address.
	 * dhcpcd has a blank gateway here to indicate a
	 * subnet route. */
	if (!sa_is_unspecified(&rt->rt_dest) &&
	    !sa_is_unspecified(&rt->rt_gateway))
	{
		switch(rt->rt_gateway.sa_family) {
#ifdef INET
		case AF_INET:
		{
			struct in_addr *in;

			in = &satosin(&rt->rt_gateway)->sin_addr;
			if (ipv4_findaddr(ctx, in))
				in->s_addr = INADDR_ANY;
			break;
		}
#endif
#ifdef INET6
		case AF_INET6:
		{
			struct in6_addr *in6;

			in6 = &satosin6(&rt->rt_gateway)->sin6_addr;
			if (ipv6_findaddr(ctx, in6, 0))
				*in6 = in6addr_any;
			break;
		}
#endif
		}
	}

	/* Solaris doesn't set interfaces for some routes.
	 * This sucks, so we need to call RTM_GET to
	 * work out the interface. */
	if (rt->rt_ifp == NULL) {
		if (if_route_get(ctx, rt) == NULL) {
			rt->rt_ifp = if_loopback(ctx);
			if (rt->rt_ifp == NULL)
				return - 1;
		}
	}

	/* Solaris likes to set route MTU to match
	 * interface MTU when adding routes.
	 * This confuses dhcpcd as it expects MTU to be 0
	 * when no explicit MTU has been set. */
	mtu = if_getmtu(rt->rt_ifp);
	if (mtu == -1)
		return -1;
	if (rt->rt_mtu == (unsigned int)mtu)
		rt->rt_mtu = 0;

	return 0;
}

static int
if_addrflags0(int fd, int af, const char *ifname)
{
	struct lifreq		lifr;
	int flags;

	memset(&lifr, 0, sizeof(lifr));
	strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
	if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1)
		return -1;

	flags = 0;
	if (lifr.lifr_flags & IFF_DUPLICATE)
		flags |= af == AF_INET6 ? IN6_IFF_DUPLICATED:IN_IFF_DUPLICATED;
	else if (!(lifr.lifr_flags & IFF_UP))
		flags |= af == AF_INET6 ? IN6_IFF_TENTATIVE:IN_IFF_TENTATIVE;
	return flags;
}

static int
if_rtm(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm)
{
	const struct sockaddr *sa;
	struct rt rt;

	if (rtm->rtm_msglen < sizeof(*rtm) + sizeof(*sa)) {
		errno = EINVAL;
		return -1;
	}

	if (if_copyrt(ctx, &rt, rtm) == -1 && errno != ESRCH)
		return -1;

#ifdef INET6
	/*
	 * BSD announces host routes.
	 * As such, we should be notified of reachability by its
	 * existance with a hardware address.
	 * Ensure we don't call this for a newly incomplete state.
	 */
	if (rt.rt_dest.sa_family == AF_INET6 &&
	    (rt.rt_flags & RTF_HOST || rtm->rtm_type == RTM_MISS) &&
	    !(rtm->rtm_type == RTM_ADD && !(rt.rt_dflags & RTDF_GATELINK)))
	{
		bool reachable;

		reachable = (rtm->rtm_type == RTM_ADD ||
		    rtm->rtm_type == RTM_CHANGE) &&
		    rt.rt_dflags & RTDF_GATELINK;
		ipv6nd_neighbour(ctx, &rt.rt_ss_dest.sin6.sin6_addr, reachable);
	}
#endif

	if (if_finishrt(ctx, &rt) == -1)
		return -1;
	rt_recvrt(rtm->rtm_type, &rt, rtm->rtm_pid);
	return 0;
}

static bool
if_getalias(struct interface *ifp, const struct sockaddr *sa, char *alias)
{
	struct ifaddrs		*ifaddrs, *ifa;
	struct interface	*ifpx;
	bool			found;

	ifaddrs = NULL;
	if (getallifaddrs(sa->sa_family, &ifaddrs, 0) == -1)
		return false;
	found = false;
	for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr == NULL)
			continue;
		if (sa_cmp(sa, ifa->ifa_addr) != 0)
			continue;
		/* Check it's for the right interace. */
		ifpx = if_find(ifp->ctx->ifaces, ifa->ifa_name);
		if (ifp == ifpx) {
			strlcpy(alias, ifa->ifa_name, IF_NAMESIZE);
			found = true;
			break;
		}
	}
	freeifaddrs(ifaddrs);
	return found;
}

static int
if_getbrdaddr(struct dhcpcd_ctx *ctx, const char *ifname, struct in_addr *brd)
{
	struct lifreq lifr = { 0 };
	int r;

	memset(&lifr, 0, sizeof(lifr));
	strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
	errno = 0;
	r = ioctl(ctx->pf_inet_fd, SIOCGLIFBRDADDR, &lifr, sizeof(lifr));
	if (r != -1)
		COPYOUT(*brd, (struct sockaddr *)&lifr.lifr_broadaddr);
	return r;
}

static int
if_ifa(struct dhcpcd_ctx *ctx, const struct ifa_msghdr *ifam)
{
	struct interface	*ifp;
	const struct sockaddr	*sa, *rti_info[RTAX_MAX];
	int			flags;
	char			ifalias[IF_NAMESIZE];

	if (ifam->ifam_msglen < sizeof(*ifam)) {
		errno = EINVAL;
		return -1;
	}
	if (~ifam->ifam_addrs & RTA_IFA)
		return 0;

	if (get_addrs(ifam->ifam_addrs, (const char *)ifam + sizeof(*ifam),
		      ifam->ifam_msglen - sizeof(*ifam), rti_info) == -1)
		return -1;
	sa = rti_info[RTAX_IFA];

	/* XXX We have no way of knowing who generated these
	 * messages wich truely sucks because we want to
	 * avoid listening to our own delete messages. */
	if ((ifp = if_findindex(ctx->ifaces, ifam->ifam_index)) == NULL)
		return 0;

	/*
	 * ifa_msghdr does not supply the alias, just the interface index.
	 * This is very bad, because it means we have to call getifaddrs
	 * and trawl the list of addresses to find the added address.
	 * To make life worse, you can have the same address on the same
	 * interface with different aliases.
	 * So this hack is not entirely accurate.
	 *
	 * IF anyone is going to fix Solaris, plesse consider adding the
	 * following fields to extend ifa_msghdr:
	 *   ifam_alias
	 *   ifam_pid
	 */
	if (ifam->ifam_type != RTM_DELADDR && !if_getalias(ifp, sa, ifalias))
		return 0;

	switch (sa->sa_family) {
	case AF_LINK:
	{
		struct sockaddr_dl sdl;

		if (ifam->ifam_type != RTM_CHGADDR &&
		    ifam->ifam_type != RTM_NEWADDR)
			break;
		memcpy(&sdl, rti_info[RTAX_IFA], sizeof(sdl));
		dhcpcd_handlehwaddr(ifp, ifp->hwtype,
		    CLLADDR(&sdl), sdl.sdl_alen);
		break;
	}
#ifdef INET
	case AF_INET:
	{
		struct in_addr addr, mask, bcast;

		COPYOUT(addr, rti_info[RTAX_IFA]);
		COPYOUT(mask, rti_info[RTAX_NETMASK]);
		COPYOUT(bcast, rti_info[RTAX_BRD]);

		if (ifam->ifam_type == RTM_DELADDR) {
			struct ipv4_addr *ia;

			ia = ipv4_iffindaddr(ifp, &addr, &mask);
			if (ia == NULL)
				return 0;
			strlcpy(ifalias, ia->alias, sizeof(ifalias));
		} else if (bcast.s_addr == INADDR_ANY) {
			/* Work around a bug where broadcast
			 * address is not correctly reported. */
			if (if_getbrdaddr(ctx, ifalias, &bcast) == -1)
				return 0;
		}
		flags = if_addrflags(ifp, NULL, ifalias);
		if (ifam->ifam_type == RTM_DELADDR) {
			if (flags != -1)
				return 0;
		} else if (flags == -1)
			return 0;

		ipv4_handleifa(ctx,
		    ifam->ifam_type == RTM_CHGADDR ?
		    RTM_NEWADDR : ifam->ifam_type,
		    NULL, ifalias, &addr, &mask, &bcast, flags, 0);
		break;
	}
#endif
#ifdef INET6
	case AF_INET6:
	{
		struct in6_addr			addr6, mask6;
		const struct sockaddr_in6	*sin6;

		sin6 = (const void *)rti_info[RTAX_IFA];
		addr6 = sin6->sin6_addr;
		sin6 = (const void *)rti_info[RTAX_NETMASK];
		mask6 = sin6->sin6_addr;

		if (ifam->ifam_type == RTM_DELADDR) {
			struct ipv6_addr	*ia;

			ia = ipv6_iffindaddr(ifp, &addr6, 0);
			if (ia == NULL)
				return 0;
			strlcpy(ifalias, ia->alias, sizeof(ifalias));
		}
		flags = if_addrflags6(ifp, NULL, ifalias);
		if (ifam->ifam_type == RTM_DELADDR) {
			if (flags != -1)
				return 0;
		} else if (flags == -1)
			return 0;

		ipv6_handleifa(ctx,
		    ifam->ifam_type == RTM_CHGADDR ?
		    RTM_NEWADDR : ifam->ifam_type,
		    NULL, ifalias, &addr6, ipv6_prefixlen(&mask6), flags, 0);
		break;
	}
#endif
	}

	return 0;
}

static int
if_ifinfo(struct dhcpcd_ctx *ctx, const struct if_msghdr *ifm)
{
	struct interface *ifp;
	int state;
	unsigned int flags;

	if (ifm->ifm_msglen < sizeof(*ifm)) {
		errno = EINVAL;
		return -1;
	}

	if ((ifp = if_findindex(ctx->ifaces, ifm->ifm_index)) == NULL)
		return 0;
	flags = (unsigned int)ifm->ifm_flags;
	if (ifm->ifm_flags & IFF_OFFLINE)
		state = LINK_DOWN;
	else {
		state = LINK_UP;
		flags |= IFF_UP;
	}
	dhcpcd_handlecarrier(ifp, state, flags);
	return 0;
}

static int
if_dispatch(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm)
{

	if (rtm->rtm_version != RTM_VERSION)
		return 0;

	switch(rtm->rtm_type) {
	case RTM_IFINFO:
		return if_ifinfo(ctx, (const void *)rtm);
	case RTM_ADD:		/* FALLTHROUGH */
	case RTM_CHANGE:	/* FALLTHROUGH */
	case RTM_DELETE:	/* FALLTHROUGH */
	case RTM_MISS:
		return if_rtm(ctx, (const void *)rtm);
	case RTM_CHGADDR:	/* FALLTHROUGH */
	case RTM_DELADDR:	/* FALLTHROUGH */
	case RTM_NEWADDR:
		return if_ifa(ctx, (const void *)rtm);
	}

	return 0;
}

int
if_handlelink(struct dhcpcd_ctx *ctx)
{
	struct rtm rtm;
	ssize_t len;

	len = read(ctx->link_fd, &rtm, sizeof(rtm));
	if (len == -1)
		return -1;
	if (len == 0)
		return 0;
	if ((size_t)len < sizeof(rtm.hdr.rtm_msglen) ||
	    len != rtm.hdr.rtm_msglen)
	{
		errno = EINVAL;
		return -1;
	}
	/*
	 * Coverity thinks that the data could be tainted from here.
	 * I have no idea how because the length of the data we read
	 * is guarded by len and checked to match rtm_msglen.
	 * The issue seems to be related to extracting the addresses
	 * at the end of the header, but seems to have no issues with the
	 * equivalent call in if_initrt.
	 */
	/* coverity[tainted_data] */
	return if_dispatch(ctx, &rtm.hdr);
}

static void
if_octetstr(char *buf, const Octet_t *o, ssize_t len)
{
	int			i;
	char			*p;

	p = buf;
	for (i = 0; i < o->o_length; i++) {
		if ((p + 1) - buf < len)
			*p++ = o->o_bytes[i];
		else
			break;
	}
	*p = '\0';
}

static int
if_setflags(int fd, const char *ifname, uint64_t flags)
{
	struct lifreq		lifr = { .lifr_addrlen = 0 };

	strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
	if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1)
		return -1;
	if ((lifr.lifr_flags & flags) != flags) {
		lifr.lifr_flags |= flags;
		if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1)
			return -1;
	}
	return 0;
}

static int
if_addaddr(int fd, const char *ifname,
    struct sockaddr_storage *addr, struct sockaddr_storage *mask,
    struct sockaddr_storage *brd, uint8_t plen)
{
	struct lifreq		lifr = { .lifr_addrlen = plen };

	strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));

	/* First assign the netmask. */
	lifr.lifr_addr = *mask;
	if (addr == NULL) {
		lifr.lifr_addrlen = plen;
		if (ioctl(fd, SIOCSLIFSUBNET, &lifr) == -1)
			return -1;
		goto up;
	} else {
		if (ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1)
			return -1;
	}

	/* Then assign the address. */
	lifr.lifr_addr = *addr;
	if (ioctl(fd, SIOCSLIFADDR, &lifr) == -1)
		return -1;

	/* Then assign the broadcast address. */
	if (brd != NULL) {
		lifr.lifr_broadaddr = *brd;
		if (ioctl(fd, SIOCSLIFBRDADDR, &lifr) == -1)
			return -1;
	}

up:
	return if_setflags(fd, ifname, IFF_UP);
}

static int
if_getaf_fd(const struct dhcpcd_ctx *ctx, int af)
{

	if (af == AF_INET)
		return ctx->pf_inet_fd;
	if (af == AF_INET6) {
		struct priv	*priv;

		priv = (struct priv *)ctx->priv;
		return priv->pf_inet6_fd;
	}

	errno = EAFNOSUPPORT;
	return -1;
}

int
if_getsubnet(struct dhcpcd_ctx *ctx, const char *ifname, int af,
    void *subnet, size_t subnet_len)
{
	struct lifreq		lifr = { .lifr_addrlen = 0 };
	int fd;

	fd = if_getaf_fd(ctx, af);
	strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
	if (ioctl(fd, SIOCGLIFSUBNET, &lifr) == -1)
		return -1;
	memcpy(subnet, &lifr.lifr_addr, MIN(subnet_len,sizeof(lifr.lifr_addr)));
	return 0;
}

static int
if_plumblif(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname)
{
	struct lifreq		lifr;
	int			s;

	memset(&lifr, 0, sizeof(lifr));
	strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
	lifr.lifr_addr.ss_family = af;
	s = if_getaf_fd(ctx, af);
	return ioctl(s,
	    cmd == RTM_NEWADDR ? SIOCLIFADDIF : SIOCLIFREMOVEIF,
	    &lifr) == -1 && errno != EEXIST ? -1 : 0;
}

static int
if_plumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname)
{
	dlpi_handle_t		dh, dh_arp = NULL;
	int			fd, af_fd, mux_fd, arp_fd = -1, mux_id, retval;
	uint64_t		flags;
	struct lifreq		lifr;
	const char		*udp_dev;
	struct strioctl		ioc;
	struct if_spec		spec;

	if (if_nametospec(ifname, &spec) == -1)
		return -1;

	af_fd = if_getaf_fd(ctx, af);

	switch (af) {
	case AF_INET:
		flags = IFF_IPV4;
		udp_dev = UDP_DEV_NAME;
		break;
	case AF_INET6:
		/* We will take care of setting the link local address. */
		flags = IFF_IPV6 | IFF_NOLINKLOCAL;
		udp_dev = UDP6_DEV_NAME;
		break;
	default:
		errno = EPROTONOSUPPORT;
		return -1;
	}

	if (dlpi_open(ifname, &dh, DLPI_NOATTACH) != DLPI_SUCCESS) {
		errno = EINVAL;
		return -1;
	}

	fd = dlpi_fd(dh);
	retval = -1;
	mux_fd = -1;
	if (ioctl(fd, I_PUSH, IP_MOD_NAME) == -1)
		goto out;
	memset(&lifr, 0, sizeof(lifr));
	strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
	lifr.lifr_ppa = spec.ppa;
	lifr.lifr_flags = flags;
	if (ioctl(fd, SIOCSLIFNAME, &lifr) == -1)
		goto out;

	/* Get full flags. */
	if (ioctl(af_fd, SIOCGLIFFLAGS, &lifr) == -1)
		goto out;
	flags = lifr.lifr_flags;

	/* Open UDP as a multiplexor to PLINK the interface stream.
	 * UDP is used because STREAMS will not let you PLINK a driver
	 * under itself and IP is generally  at the bottom of the stream. */
	if ((mux_fd = open(udp_dev, O_RDWR)) == -1)
		goto out;
	/* POP off all undesired modules. */
	while (ioctl(mux_fd, I_POP, 0) != -1)
		;
	if (errno != EINVAL)
		goto out;
	if(ioctl(mux_fd, I_PUSH, ARP_MOD_NAME) == -1)
		goto out;

	if (flags & (IFF_NOARP | IFF_IPV6)) {
		/* PLINK the interface stream so it persists. */
		if (ioctl(mux_fd, I_PLINK, fd) == -1)
			goto out;
		goto done;
	}

	if (dlpi_open(ifname, &dh_arp, DLPI_NOATTACH) != DLPI_SUCCESS)
		goto out;
	arp_fd = dlpi_fd(dh_arp);
	if (ioctl(arp_fd, I_PUSH, ARP_MOD_NAME) == -1)
		goto out;

	memset(&lifr, 0, sizeof(lifr));
	strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
	lifr.lifr_ppa = spec.ppa;
	lifr.lifr_flags = flags;
	memset(&ioc, 0, sizeof(ioc));
	ioc.ic_cmd = SIOCSLIFNAME;
	ioc.ic_dp = (char *)&lifr;
	ioc.ic_len = sizeof(lifr);
	if (ioctl(arp_fd, I_STR, &ioc) == -1)
		goto out;

	/* PLINK the interface stream so it persists. */
	mux_id = ioctl(mux_fd, I_PLINK, fd);
	if (mux_id == -1)
		goto out;
	if (ioctl(mux_fd, I_PLINK, arp_fd) == -1) {
		ioctl(mux_fd, I_PUNLINK, mux_id);
		goto out;
	}

done:
	retval = 0;

out:
	dlpi_close(dh);
	if (dh_arp != NULL)
		dlpi_close(dh_arp);
	if (mux_fd != -1)
		close(mux_fd);
	return retval;
}

static int
if_unplumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname)
{
	struct sockaddr_storage		addr = { .ss_family = af };
	int				fd;

	/* For the time being, don't unplumb the interface, just
	 * set the address to zero. */
	fd = if_getaf_fd(ctx, af);
	return if_addaddr(fd, ifname, &addr, &addr,
	    af == AF_INET ? &addr : NULL, 0);
}

static int
if_plumb(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname)
{
	struct if_spec		spec;

	if (if_nametospec(ifname, &spec) == -1)
		return -1;
	if (spec.lun != -1)
		return if_plumblif(cmd, ctx, af, ifname);
	if (cmd == RTM_NEWADDR)
		return if_plumbif(ctx, af, ifname);
	else
		return if_unplumbif(ctx, af, ifname);
}

#ifdef INET
static int
if_walkrt(struct dhcpcd_ctx *ctx, rb_tree_t *routes, char *data, size_t len)
{
	mib2_ipRouteEntry_t *re, *e;
	struct rt rt, *rtn;
	char ifname[IF_NAMESIZE];
	struct in_addr in;

	if (len % sizeof(*re) != 0) {
		errno = EINVAL;
		return -1;
	}

	re = (mib2_ipRouteEntry_t *)data;
	e = (mib2_ipRouteEntry_t *)(data + len);
	do {
		/* Skip route types we don't want. */
		switch (re->ipRouteInfo.re_ire_type) {
		case IRE_IF_CLONE:
		case IRE_BROADCAST:
		case IRE_MULTICAST:
		case IRE_NOROUTE:
		case IRE_LOCAL:
			continue;
		default:
			break;
		}

		memset(&rt, 0, sizeof(rt));
		in.s_addr = re->ipRouteDest;
		sa_in_init(&rt.rt_dest, &in);
		in.s_addr = re->ipRouteMask;
		sa_in_init(&rt.rt_netmask, &in);
		in.s_addr = re->ipRouteNextHop;
		sa_in_init(&rt.rt_gateway, &in);
		rt.rt_flags = re->ipRouteInfo.re_flags;
		in.s_addr = re->ipRouteInfo.re_src_addr;
		sa_in_init(&rt.rt_ifa, &in);
		rt.rt_mtu = re->ipRouteInfo.re_max_frag;
		if_octetstr(ifname, &re->ipRouteIfIndex, sizeof(ifname));
		rt.rt_ifp = if_find(ctx->ifaces, ifname);
		if (if_finishrt(ctx, &rt) == -1) {
			logerr(__func__);
			continue;
		}
		if ((rtn = rt_new(rt.rt_ifp)) == NULL) {
			logerr(__func__);
			break;
		}
		memcpy(rtn, &rt, sizeof(*rtn));
		if (rb_tree_insert_node(routes, rtn) != rtn)
			rt_free(rtn);
	} while (++re < e);
	return 0;
}
#endif

#ifdef INET6
static int
if_walkrt6(struct dhcpcd_ctx *ctx, rb_tree_t *routes, char *data, size_t len)
{
	mib2_ipv6RouteEntry_t *re, *e;
	struct rt rt, *rtn;
	char ifname[IF_NAMESIZE];
	struct in6_addr in6;

	if (len % sizeof(*re) != 0) {
		errno = EINVAL;
		return -1;
	}

	re = (mib2_ipv6RouteEntry_t *)data;
	e = (mib2_ipv6RouteEntry_t *)(data + len);

	do {
		/* Skip route types we don't want. */
		switch (re->ipv6RouteInfo.re_ire_type) {
		case IRE_IF_CLONE:
		case IRE_BROADCAST:
		case IRE_MULTICAST:
		case IRE_NOROUTE:
		case IRE_LOCAL:
			continue;
		default:
			break;
		}

		memset(&rt, 0, sizeof(rt));
		sa_in6_init(&rt.rt_dest, &re->ipv6RouteDest);
		ipv6_mask(&in6, re->ipv6RoutePfxLength);
		sa_in6_init(&rt.rt_netmask, &in6);
		sa_in6_init(&rt.rt_gateway, &re->ipv6RouteNextHop);
		sa_in6_init(&rt.rt_ifa, &re->ipv6RouteInfo.re_src_addr);
		rt.rt_mtu = re->ipv6RouteInfo.re_max_frag;
		if_octetstr(ifname, &re->ipv6RouteIfIndex, sizeof(ifname));
		rt.rt_ifp = if_find(ctx->ifaces, ifname);
		if (if_finishrt(ctx, &rt) == -1) {
			logerr(__func__);
			continue;
		}
		if ((rtn = rt_new(rt.rt_ifp)) == NULL) {
			logerr(__func__);
			break;
		}
		memcpy(rtn, &rt, sizeof(*rtn));
		if (rb_tree_insert_node(routes, rtn) != rtn)
			rt_free(rtn);
	} while (++re < e);
	return 0;
}
#endif

static int
if_parsert(struct dhcpcd_ctx *ctx, rb_tree_t *routes,
    unsigned int level, unsigned int name,
    int (*walkrt)(struct dhcpcd_ctx *, rb_tree_t *, char *, size_t))
{
	int			s, retval, code, flags;
	uintptr_t		buf[512 / sizeof(uintptr_t)];
	struct strbuf		ctlbuf, databuf;
	struct T_optmgmt_req	*tor = (struct T_optmgmt_req *)buf;
	struct T_optmgmt_ack	*toa = (struct T_optmgmt_ack *)buf;
	struct T_error_ack	*tea = (struct T_error_ack *)buf;
	struct opthdr		*req;

	if ((s = open("/dev/arp", O_RDWR)) == -1)
		return -1;

	/* Assume we are erroring. */
	retval = -1;

	tor->PRIM_type = T_SVR4_OPTMGMT_REQ;
	tor->OPT_offset = sizeof (struct T_optmgmt_req);
	tor->OPT_length = sizeof (struct opthdr);
	tor->MGMT_flags = T_CURRENT;

	req = (struct opthdr *)&tor[1];
	req->level = EXPER_IP_AND_ALL_IRES;
	req->name = 0;
	req->len = 1;

	ctlbuf.buf = (char *)buf;
	ctlbuf.len = tor->OPT_length + tor->OPT_offset;
	if (putmsg(s, &ctlbuf, NULL, 0) == 1)
		goto out;

	req = (struct opthdr *)&toa[1];
	ctlbuf.maxlen = sizeof(buf);

	/* Create a reasonable buffer to start with */
	databuf.maxlen = BUFSIZ * 2;
	if ((databuf.buf = malloc(databuf.maxlen)) == NULL)
		goto out;

	for (;;) {
		flags = 0;
		if ((code = getmsg(s, &ctlbuf, 0, &flags)) == -1)
			break;
		if (code == 0 &&
		    toa->PRIM_type == T_OPTMGMT_ACK &&
		    toa->MGMT_flags == T_SUCCESS &&
		    (size_t)ctlbuf.len >= sizeof(struct T_optmgmt_ack))
		{
			/* End of messages, so return success! */
			retval = 0;
			break;
		}
		if (tea->PRIM_type == T_ERROR_ACK) {
			errno = tea->TLI_error == TSYSERR ?
			    tea->UNIX_error : EPROTO;
			break;
		}
		if (code != MOREDATA ||
		    toa->PRIM_type != T_OPTMGMT_ACK ||
		    toa->MGMT_flags != T_SUCCESS)
		{
			errno = ENOMSG;
			break;
		}

		/* Try to ensure out buffer is big enough
		 * for future messages as well. */
		if ((size_t)databuf.maxlen < req->len) {
			size_t newlen;

			free(databuf.buf);
			newlen = roundup(req->len, BUFSIZ);
			if ((databuf.buf = malloc(newlen)) == NULL)
				break;
			databuf.maxlen = newlen;
		}

		flags = 0;
		if (getmsg(s, NULL, &databuf, &flags) == -1)
			break;

		/* We always have to get the data before moving onto
		 * the next item, so don't move this test higher up
		 * to avoid the buffer allocation and getmsg calls. */
		if (req->level == level && req->name == name) {
			if (walkrt(ctx, routes, databuf.buf, req->len) == -1)
				break;
		}
	}

	free(databuf.buf);
out:
	close(s);
	return retval;
}


int
if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *routes, int af)
{

#ifdef INET
	if ((af == AF_UNSPEC || af == AF_INET) &&
	    if_parsert(ctx, routes, MIB2_IP,MIB2_IP_ROUTE, if_walkrt) == -1)
		return -1;
#endif
#ifdef INET6
	if ((af == AF_UNSPEC || af == AF_INET6) &&
	    if_parsert(ctx, routes, MIB2_IP6, MIB2_IP6_ROUTE, if_walkrt6) == -1)
		return -1;
#endif
	return 0;
}


#ifdef INET
/* XXX We should fix this to write via the BPF interface. */
ssize_t
bpf_send(const struct bpf *bpf, uint16_t protocol, const void *data, size_t len)
{
	const struct interface *ifp = bpf->bpf_ifp;
	dlpi_handle_t		dh;
	dlpi_info_t		di;
	int			r;

	if (dlpi_open(ifp->name, &dh, 0) != DLPI_SUCCESS)
		return -1;
	if ((r = dlpi_info(dh, &di, 0)) == DLPI_SUCCESS &&
	    (r = dlpi_bind(dh, protocol, NULL)) == DLPI_SUCCESS)
		r = dlpi_send(dh, di.di_bcastaddr, ifp->hwlen, data, len, NULL);
	dlpi_close(dh);
	return r == DLPI_SUCCESS ? (ssize_t)len : -1;
}

int
if_address(unsigned char cmd, const struct ipv4_addr *ia)
{
	union {
		struct sockaddr		sa;
		struct sockaddr_storage ss;
	} addr, mask, brd;
	int fd = ia->iface->ctx->pf_inet_fd;

	/* Either remove the alias or ensure it exists. */
	if (if_plumb(cmd, ia->iface->ctx, AF_INET, ia->alias) == -1 &&
	    errno != EEXIST)
		return -1;

	if (cmd == RTM_DELADDR)
		return 0;

	if (cmd != RTM_NEWADDR) {
		errno = EINVAL;
		return -1;
	}

	/* We need to update the index now */
	ia->iface->index = if_nametoindex(ia->alias);

	sa_in_init(&addr.sa, &ia->addr);
	sa_in_init(&mask.sa, &ia->mask);
	sa_in_init(&brd.sa, &ia->brd);
	return if_addaddr(fd, ia->alias, &addr.ss, &mask.ss, &brd.ss, 0);
}

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

	return if_addrflags0(ifp->ctx->pf_inet_fd, AF_INET, alias);
}

#endif

#ifdef INET6
int
if_address6(unsigned char cmd, const struct ipv6_addr *ia)
{
	union {
		struct sockaddr		sa;
		struct sockaddr_in6	sin6;
		struct sockaddr_storage ss;
	} addr, mask;
	int			fd, r;

	/* Either remove the alias or ensure it exists. */
	if (if_plumb(cmd, ia->iface->ctx, AF_INET6, ia->alias) == -1 &&
	    errno != EEXIST)
		return -1;

	if (cmd == RTM_DELADDR)
		return 0;

	if (cmd != RTM_NEWADDR) {
		errno = EINVAL;
		return -1;
	}

	fd = if_getaf_fd(ia->iface->ctx, AF_INET6);

	if (!(ia->flags & IPV6_AF_AUTOCONF) && ia->flags & IPV6_AF_RAPFX) {
		if (if_setflags(fd, ia->alias, IFF_NOLOCAL) ==-1)
			return -1;
		sa_in6_init(&mask.sa, &ia->prefix);
		r = if_addaddr(fd, ia->alias,
		    NULL, &mask.ss, NULL, ia->prefix_len);
	} else {
		sa_in6_init(&addr.sa, &ia->addr);
		mask.sin6.sin6_family = AF_INET6;
		ipv6_mask(&mask.sin6.sin6_addr, ia->prefix_len);
		r = if_addaddr(fd, ia->alias,
		    &addr.ss, &mask.ss, NULL, ia->prefix_len);
	}
	if (r == -1 && errno == EEXIST)
		return 0;
	return r;
}

int
if_addrflags6(const struct interface *ifp, __unused const struct in6_addr *ia,
    const char *alias)
{
	int			fd;

	fd = if_getaf_fd(ifp->ctx, AF_INET6);
	return if_addrflags0(fd, AF_INET6, alias);
}

int
if_getlifetime6(struct ipv6_addr *addr)
{

	UNUSED(addr);
	errno = ENOTSUP;
	return -1;
}

int
if_applyra(const struct ra *rap)
{
	struct lifreq		lifr = {
		.lifr_ifinfo.lir_maxhops = rap->hoplimit,
		.lifr_ifinfo.lir_reachtime = rap->reachable,
		.lifr_ifinfo.lir_reachretrans = rap->retrans,
	};

	strlcpy(lifr.lifr_name, rap->iface->name, sizeof(lifr.lifr_name));
	return ioctl(rap->iface->ctx->pf_inet_fd, SIOCSLIFLNKINFO, &lifr);
}

void
if_setup_inet6(__unused const struct interface *ifp)
{

}

int
ip6_forwarding(__unused const char *ifname)
{

	return 1;
}
#endif