/*
 * Solaris interface driver for dhcpcd
 * Copyright (c) 2016 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 <stddef.h>
#include <stdlib.h>
#include <stropts.h>
#include <string.h>
#include <unistd.h>

#include <inet/ip.h>

/* private interface we can hook into to get
 * a better getifaddrs(3). */
#include <libsocket_priv.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/pfmod.h>
#include <sys/tihdr.h>
#include <sys/utsname.h>

#include "config.h"
#include "common.h"
#include "dhcp.h"
#include "if.h"
#include "if-options.h"
#include "ipv4.h"
#include "ipv6.h"
#include "ipv6nd.h"

#ifndef ARP_MOD_NAME
#  define ARP_MOD_NAME        "arp"
#endif

#ifndef RT_ROUNDUP
#define RT_ROUNDUP(a)							      \
	((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
#define RT_ADVANCE(x, n) (x += RT_ROUNDUP(salen(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)

#ifndef CLLADDR
#  define CLLADDR(s) (const void *)((s)->sdl_data + (s)->sdl_nlen)
#endif

#ifdef INET
/* Instead of using DLPI directly, we use libdlpi which is
 * Solaris sepcific. */
struct dl_if {
	TAILQ_ENTRY(dl_if)	next;
	struct interface	*iface;
	int			fd;
	dlpi_handle_t		dh;
	uint8_t			broadcast[DLPI_PHYSADDR_MAX];
};
TAILQ_HEAD(dl_if_head, dl_if);
#endif

struct priv {
#ifdef INET
	struct dl_if_head dl_ifs;
#endif
#ifdef INET6
	int pf_inet6_fd;
#endif
};

int
if_init(__unused struct interface *ifp)
{

	return 0;
}

int
if_conf(__unused struct interface *ifp)
{

	return 0;
}

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

	if ((priv = malloc(sizeof(*priv))) == NULL)
		return -1;
	ctx->priv = priv;
#ifdef INET
	TAILQ_INIT(&priv->dl_ifs);
#endif

#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);
#ifdef INET
	if (ctx->link_fd == -1)
		free(ctx->priv);
#endif
	return ctx->link_fd == -1 ? -1 : 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_getssid(struct interface *ifp)
{

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

int
if_vimaster(__unused const 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;
	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;
	if (dlpi_open(ifname, &dh, 0) != 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 int
salen(const struct sockaddr *sa)
{

	switch (sa->sa_family) {
	case AF_LINK:
		return sizeof(struct sockaddr_dl);
	case AF_INET:
		return sizeof(struct sockaddr_in);
	case AF_INET6:
		return sizeof(struct sockaddr_in6);
	default:
		return sizeof(struct sockaddr);
	}
}

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, const struct sockaddr **sa)
{
	const char *cp;
	int i;

	cp = data;
	for (i = 0; i < RTAX_MAX; i++) {
		if (type & (1 << i)) {
			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;
}

#ifdef INET
static void
if_finishrt(struct rt *rt)
{

	/* Solaris has a subnet route with the gateway
	 * of the owning address.
	 * dhcpcd has a blank gateway here to indicate a
	 * subnet route. */
	if (rt->gate.s_addr != ntohl(INADDR_ANY) &&
	    ipv4_iffindaddr(UNCONST(rt->iface), &rt->gate, NULL))
		rt->gate.s_addr = ntohl(INADDR_ANY);

	/* 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. */
	if (rt->mtu == (unsigned int)if_getmtu(rt->iface))
		rt->mtu = 0;
}

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

	sa = (const void *)(rtm + 1);
	if (sa->sa_family != AF_INET)
		return -1;
	if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY))
		return -1;

	get_addrs(rtm->rtm_addrs, sa, rti_info);
	memset(rt, 0, sizeof(*rt));
	rt->flags = (unsigned int)rtm->rtm_flags;
	COPYOUT(rt->dest, rti_info[RTAX_DST]);
	if (rtm->rtm_addrs & RTA_NETMASK)
		COPYOUT(rt->mask, rti_info[RTAX_NETMASK]);
	else
		rt->mask.s_addr = INADDR_BROADCAST;
	COPYOUT(rt->gate, rti_info[RTAX_GATEWAY]);
	COPYOUT(rt->src, rti_info[RTAX_SRC]);
	rt->mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu;

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

	/* If we don't have an interface and it's a host route, it maybe
	 * to a local ip via the loopback interface. */
	if (rt->iface == NULL &&
	    !(~rtm->rtm_flags & (RTF_HOST | RTF_GATEWAY)))
	{
		struct ipv4_addr *ia;

		if ((ia = ipv4_findaddr(ctx, &rt->dest)))
			rt->iface = ia->iface;
	}

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

	if_finishrt(rt);

	return 0;
}
#endif

#ifdef INET6
static void
if_finishrt6(struct rt6 *rt)
{

	/* Solaris has a subnet route with the gateway
	 * of the owning address.
	 * dhcpcd has a blank gateway here to indicate a
	 * subnet route. */
	if (!IN6_IS_ADDR_UNSPECIFIED(&rt->gate) &&
	    ipv6_iffindaddr(UNCONST(rt->iface), &rt->gate, 0))
		rt->gate = in6addr_any;

	/* Solarais 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. */
	if (rt->mtu == (unsigned int)if_getmtu(rt->iface))
		rt->mtu = 0;
}

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

	sa = (const void *)(rtm + 1);
	if (sa->sa_family != AF_INET6)
		return -1;
	if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY))
		return -1;

	get_addrs(rtm->rtm_addrs, sa, rti_info);
	memset(rt, 0, sizeof(*rt));
	rt->flags = (unsigned int)rtm->rtm_flags;
	COPYOUT6(rt->dest, rti_info[RTAX_DST]);
	if (rtm->rtm_addrs & RTA_NETMASK)
		COPYOUT6(rt->mask, rti_info[RTAX_NETMASK]);
	else
		ipv6_mask(&rt->mask, 128);
	COPYOUT6(rt->gate, rti_info[RTAX_GATEWAY]);
	rt->mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu;

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

	/* If we don't have an interface and it's a host route, it maybe
	 * to a local ip via the loopback interface. */
	if (rt->iface == NULL &&
	    !(~rtm->rtm_flags & (RTF_HOST | RTF_GATEWAY)))
	{
		struct ipv6_addr *ia;

		if ((ia = ipv6_findaddr(ctx, &rt->dest, 0)))
			rt->iface = ia->iface;
	}

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

	if_finishrt6(rt);

	return 0;
}
#endif

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

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

	return lifr.lifr_flags;
}

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

	/* Ignore messages generated by us */
	if (rtm->rtm_pid == getpid()) {
		ctx->options &= ~DHCPCD_RTM_PPID;
		return;
	}

	/* Ignore messages sent by the parent after forking */
	if ((ctx->options &
	    (DHCPCD_RTM_PPID | DHCPCD_DAEMONISED)) ==
	    (DHCPCD_RTM_PPID | DHCPCD_DAEMONISED) &&
	    rtm->rtm_pid == ctx->ppid)
	{
		/* If this is the last successful message sent,
		 * clear the check flag as it's possible another
		 * process could re-use the same pid and also
		 * manipulate therouting table. */
		if (rtm->rtm_seq == ctx->pseq)
			ctx->options &= ~DHCPCD_RTM_PPID;
		return;
	}

	sa = (const void *)(rtm + 1);
	switch (sa->sa_family) {
#ifdef INET
	case AF_INET:
	{
		struct rt rt;

		if (if_copyrt(ctx, &rt, rtm) == 0)
			ipv4_handlert(ctx, rtm->rtm_type, &rt, 0);
		break;
	}
#endif
#ifdef INET6
	case AF_INET6:
	{
		struct rt6 rt6;

		if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY))
			break;
		/*
		 * BSD announces host routes.
		 * But does this work on Solaris?
		 * As such, we should be notified of reachability by its
		 * existance with a hardware address.
		 */
		if (rtm->rtm_flags & (RTF_HOST)) {
			const struct sockaddr *rti_info[RTAX_MAX];
			struct in6_addr dst6;
			struct sockaddr_dl sdl;

			get_addrs(rtm->rtm_addrs, sa, rti_info);
			COPYOUT6(dst6, rti_info[RTAX_DST]);
			if (rti_info[RTAX_GATEWAY]->sa_family == AF_LINK)
				memcpy(&sdl, rti_info[RTAX_GATEWAY],
				    sizeof(sdl));
			else
				sdl.sdl_alen = 0;
			ipv6nd_neighbour(ctx, &dst6,
			    rtm->rtm_type != RTM_DELETE && sdl.sdl_alen ?
			    IPV6ND_REACHABLE : 0);
			break;
		}

		if (if_copyrt6(ctx, &rt6, rtm) == 0)
			ipv6_handlert(ctx, rtm->rtm_type, &rt6);
		break;
	}
#endif
	}
}

static int
sa_cmp(const struct sockaddr *x, const struct sockaddr *y)
{

	if (x->sa_family != y->sa_family)
		return x->sa_family < y->sa_family ? -1 : 1;
	switch (x->sa_family) {
#ifdef INET
	case AF_INET:
	{
		const struct sockaddr_in *xin, *yin;

		xin = (const void *)x;
		yin = (const void *)y;
		if (xin->sin_addr.s_addr != yin->sin_addr.s_addr)
			return xin->sin_addr.s_addr < yin->sin_addr.s_addr ?
			    -1 : 1;
		break;
	}
#endif
#ifdef INET6
	case AF_INET6:
	{
		const struct sockaddr_in6 *xin6, *yin6;

		xin6 = (const void *)x;
		yin6 = (const void *)y;
		return memcmp(xin6->sin6_addr.s6_addr,
		    yin6->sin6_addr.s6_addr,
		    sizeof(xin6->sin6_addr.s6_addr));
	}
#endif
	default:
		assert(!"unknown sa_family");
	}

	return 0;
}

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

	/* 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;
	sa = (const void *)(ifam + 1);
	get_addrs(ifam->ifam_addrs, sa, rti_info);
	if ((sa = rti_info[RTAX_IFA]) == NULL)
		return;

	/*
	 * 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
	 */

	ifalias = ifp->name;
	if (ifam->ifam_type != RTM_DELADDR && sa->sa_family != AF_LINK) {
		struct ifaddrs	*ifaddrs, *ifa;

		ifaddrs = NULL;
		if (getallifaddrs(sa->sa_family, &ifaddrs, 0) == -1)
			return;
		for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
			if (ifa->ifa_addr != NULL) {
				if (sa_cmp(sa, ifa->ifa_addr) == 0) {
					/* Check it's for the right interace. */
					struct interface	*ifpx;

					ifpx = if_find(ctx->ifaces,
					    ifa->ifa_name);
					if (ifp == ifpx) {
						ifalias = ifa->ifa_name;
						break;
					}
				}
			}
		}
		freeifaddrs(ifaddrs);
	}

	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(ctx, ifp->name, 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) {
			flags = if_addrflags0(ctx->pf_inet_fd, ifalias);
			if (flags == -1)
				break;
		} else
			flags = 0;

		ipv4_handleifa(ctx,
		    ifam->ifam_type == RTM_CHGADDR ?
		    RTM_NEWADDR : ifam->ifam_type,
		    NULL, ifalias, &addr, &mask, &bcast, flags);
		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) {
			const struct priv	 *priv;

			priv = (struct priv *)ctx->priv;
			flags = if_addrflags0(priv->pf_inet6_fd, ifalias);
			if (flags == -1)
				break;
		} else
			flags = 0;

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

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

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

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

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

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

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

	memset(&msg, 0, sizeof(msg));
	msg.msg_iov = ctx->iov;
	msg.msg_iovlen = 1;

	if ((len = recvmsg_realloc(ctx->link_fd, &msg, 0)) == -1)
		return -1;
	if (len != 0)
		if_dispatch(ctx, ctx->iov[0].iov_base);
	return 0;
}

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_parsert(struct dhcpcd_ctx *ctx, unsigned int level, unsigned int name,
    int (*walkrt)(struct dhcpcd_ctx *, 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, databuf.buf, req->len) == -1)
				break;
		}
	}

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

static int
if_addaddr(int fd, const char *ifname,
    struct sockaddr_storage *addr, struct sockaddr_storage *mask)
{
	struct lifreq		lifr;

	memset(&lifr, 0, sizeof(lifr));
	strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));

	/* First assign the netmask. */
	lifr.lifr_addr = *mask;
	if (ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1)
		return -1;

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

	/* Now bring it up. */
	if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1)
		return -1;
	if (!(lifr.lifr_flags & IFF_UP)) {
		lifr.lifr_flags |= IFF_UP;
		if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1)
			return -1;
	}
	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;
	if (af == AF_INET)
		s = ctx->pf_inet_fd;
	else {
		struct priv	*priv;

		priv = (struct priv *)ctx->priv;
		s = priv->pf_inet6_fd;
	}
	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;
	int			fd, af_fd, mux_fd, retval;
	struct lifreq		lifr;
	const char		*udp_dev;

	memset(&lifr, 0, sizeof(lifr));
	switch (af) {
	case AF_INET:
		lifr.lifr_flags = IFF_IPV4;
		af_fd = ctx->pf_inet_fd;
		udp_dev = UDP_DEV_NAME;
		break;
	case AF_INET6:
	{
		struct priv *priv;

		/* We will take care of setting the link local address. */
		lifr.lifr_flags = IFF_IPV6 | IFF_NOLINKLOCAL;
		priv = (struct priv *)ctx->priv;
		af_fd = priv->pf_inet6_fd;
		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;
	strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
	if (ioctl(fd, SIOCSLIFNAME, &lifr) == -1)
		goto out;

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

	/* 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 (lifr.lifr_flags & IFF_IPV4 && !(lifr.lifr_flags & IFF_NOARP)) {
		if (ioctl(mux_fd, I_PUSH, ARP_MOD_NAME) == -1)
			goto out;
	}

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

	retval = 0;

out:
	dlpi_close(dh);
	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, mask;
	int				fd;

	/* For the time being, don't unplumb the interface, just
	 * set the address to zero. */
	memset(&addr, 0, sizeof(addr));
	addr.ss_family = af;
	memset(&mask, 0, sizeof(mask));
	mask.ss_family = af;
	switch (af) {
#ifdef INET
	case AF_INET:
		fd = ctx->pf_inet_fd;
		break;
#endif
#ifdef INET6
	case AF_INET6:
	{
		struct priv		*priv;

		priv = (struct priv *)ctx->priv;
		fd = priv->pf_inet6_fd;
		break;
	}
#endif
	default:
		errno = EAFNOSUPPORT;
		return -1;
	}
	return if_addaddr(fd, ifname, &addr, &mask);
}

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);
}

static int
if_rtmsg(unsigned char cmd, const struct interface *ifp,
    int addrs, int flags,
    const struct sockaddr *dst, const struct sockaddr *mask,
    const struct sockaddr *gate, const struct sockaddr *src,
    uint32_t mtu)
{
	struct rtm
	{
		struct rt_msghdr hdr;
		char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX];
	} rtm;
	char *bp = rtm.buffer;
	size_t l;

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

	if ((cmd == RTM_ADD || cmd == RTM_DELETE || cmd == RTM_CHANGE) &&
	    ifp->ctx->options & DHCPCD_DAEMONISE &&
	    !(ifp->ctx->options & DHCPCD_DAEMONISED))
		ifp->ctx->options |= DHCPCD_RTM_PPID;

#define ADDSA(sa) {							      \
		l = RT_ROUNDUP(salen((sa)));				      \
		memcpy(bp, (sa), l);					      \
		bp += l;						      \
	}

	memset(&rtm, 0, sizeof(rtm));
	rtm.hdr.rtm_version = RTM_VERSION;
	rtm.hdr.rtm_type = cmd;
	rtm.hdr.rtm_seq = ++ifp->ctx->seq;
	rtm.hdr.rtm_flags = flags;
	rtm.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | addrs;

	if (cmd == RTM_ADD || cmd == RTM_CHANGE) {
		rtm.hdr.rtm_flags |= RTF_UP;
		if (!(rtm.hdr.rtm_flags & RTF_REJECT) ||
		    !(rtm.hdr.rtm_flags & RTF_GATEWAY))
			rtm.hdr.rtm_addrs |= RTA_IFP;
		if (mtu != 0) {
			rtm.hdr.rtm_inits |= RTV_MTU;
			rtm.hdr.rtm_rmx.rmx_mtu = mtu;
		}
	}

	ADDSA(dst);
	ADDSA(gate);
	if (rtm.hdr.rtm_addrs & RTA_NETMASK)
		ADDSA(mask);

	if (rtm.hdr.rtm_addrs & RTA_IFP) {
		struct sockaddr_dl sdl;

		if_linkaddr(&sdl, ifp);
		ADDSA((struct sockaddr *)&sdl);
	}

/* This no workie :/ */
#if 0
	/* route(1M) says RTA_IFA is accepted but ignored
	 * it's unclear how RTA_SRC is different. */
	if (rtm.hdr.rtm_addrs & RTA_IFA) {
		rtm.hdr.rtm_addrs &= ~RTA_IFA;
		rtm.hdr.rtm_addrs |= RTA_SRC;
	}
	if (rtm.hdr.rtm_addrs & RTA_SRC)
		ADDSA(src);
#endif

#undef ADDSA

	rtm.hdr.rtm_msglen = (unsigned short)(bp - (char *)&rtm);
	if (write(ifp->ctx->link_fd, &rtm, rtm.hdr.rtm_msglen) == -1)
		return -1;
	ifp->ctx->sseq = ifp->ctx->seq;
	return 0;
}

#ifdef INET
const char *if_pfname = "DLPI";

static struct dl_if *
if_findraw(struct interface *ifp, int fd)
{
	struct dl_if		*di;
	struct priv		*priv;

	priv = (struct priv *)ifp->ctx->priv;
	TAILQ_FOREACH(di, &priv->dl_ifs, next) {
		if (di->fd == fd)
			return di;
	}
	errno = ENXIO;
	return NULL;
}

void
if_closeraw(struct interface *ifp, int fd)
{
	struct dl_if		*di;

	if ((di = if_findraw(ifp, fd)) != NULL) {
		struct priv	*priv;

		priv = (struct priv *)ifp->ctx->priv;
		TAILQ_REMOVE(&priv->dl_ifs, di, next);
		dlpi_close(di->dh);
		free(di);
	}
}

int
if_openraw(struct interface *ifp, uint16_t protocol)
{
	dlpi_handle_t		dh;
	struct priv		*priv;
	struct dl_if		*di;
	dlpi_info_t		dlinfo;
	struct packetfilt	pf;
	ushort_t		*pfp;
	struct strioctl		sioc;

	if (dlpi_open(ifp->name, &dh, 0) != DLPI_SUCCESS)
		return -1;

	/* We need to register pfmod, which is similar to BPF
	 * so the kernel can filter out the packets we don't need. */
	if (dlpi_info(dh, &dlinfo, 0) != DLPI_SUCCESS ||
	    dlpi_bind(dh, protocol, NULL) != DLPI_SUCCESS ||
	    (di = malloc(sizeof(*di))) == NULL)
		goto failed1;

	di->iface	= ifp;
	di->dh		= dh;
	di->fd		= dlpi_fd(dh);
	memcpy(di->broadcast, dlinfo.di_bcastaddr, dlinfo.di_bcastaddrlen);
	priv = (struct priv *)ifp->ctx->priv;

	pf.Pf_Priority = 0;
	pfp = pf.Pf_Filter;
	/* pfmod operates on 16 bits, so divide offsets by 2.
	 * When working on a 8 bits, mask off the bits not teested. */
	switch (protocol) {
	case ETHERTYPE_IP:
		/* Filter fragments. */
		*pfp++ = ENF_PUSHWORD + (offsetof(struct ip, ip_off) / 2);
		*pfp++ = ENF_PUSHLIT | ENF_AND;
		*pfp++ = htons(0x1fff | IP_MF);
		*pfp++ = ENF_PUSHZERO | ENF_CAND;

		/* Filter UDP. */
		*pfp++ = ENF_PUSHWORD + (offsetof(struct ip, ip_p) / 2);
		*pfp++ = ENF_PUSHFF00 | ENF_AND;
		*pfp++ = ENF_PUSHLIT | ENF_CAND;
		*pfp++ = htons(IPPROTO_UDP);

		/* Filter BOOTPC. */
		*pfp++ = ENF_PUSHWORD +
		    ((sizeof(struct ip) +
		    offsetof(struct udphdr, uh_dport)) / 2);
		*pfp++ = ENF_PUSHLIT | ENF_CAND;
		*pfp++ = htons(BOOTPC);
		break;

	case ETHERTYPE_ARP:
		/* We are only interested in IP. */
		*pfp++ = ENF_PUSHWORD + (offsetof(struct arphdr, ar_hrd) / 2);
		*pfp++ = ENF_PUSHLIT | ENF_CAND;
		*pfp++ = htons(ARPHRD_ETHER);

		/* Must be REQUEST or REPLY. */
		*pfp++ = ENF_PUSHWORD + (offsetof(struct arphdr, ar_op) / 2);
		*pfp++ = ENF_PUSHLIT | ENF_CNAND;
		*pfp++ = htons(ARPOP_REQUEST);
		*pfp++ = ENF_PUSHWORD + (offsetof(struct arphdr, ar_op) / 2);
		*pfp++ = ENF_PUSHLIT | ENF_CAND;
		*pfp++ = htons(ARPOP_REPLY);
		break;

	default:
		errno = EPROTOTYPE;
		goto failed;
	}
	pf.Pf_FilterLen = pfp - pf.Pf_Filter;

	sioc.ic_cmd	= PFIOCSETF;
	sioc.ic_timout	= INFTIM;
	sioc.ic_len	= sizeof(pf);
	sioc.ic_dp	= (void *)&pf;

	/* Install the filter and then flush the stream. */
	if (ioctl(di->fd, I_PUSH, "pfmod") == -1 ||
	    ioctl(di->fd, I_STR, &sioc) == -1 ||
	    ioctl(di->fd, I_FLUSH, FLUSHR) == -1)
		goto failed;

	TAILQ_INSERT_TAIL(&priv->dl_ifs, di, next);
	return di->fd;

failed:
	free(di);
failed1:
	dlpi_close(dh);
	return -1;
}

ssize_t
if_sendraw(const struct interface *cifp, int fd, __unused uint16_t protocol,
    const void *data, size_t len)
{
	struct dl_if		*di;
	int			r;
	struct interface	*ifp = UNCONST(cifp);

	if ((di = if_findraw(ifp, fd)) == NULL)
		return -1;
	r = dlpi_send(di->dh, di->broadcast, ifp->hwlen, data, len, NULL);
	return r == DLPI_SUCCESS ? (ssize_t)len : -1;
}

ssize_t
if_readraw(struct interface *ifp, int fd,
    void *data, size_t len, int *flags)
{
	struct dl_if		*di;
	int			r;
	size_t			mlen;

	if ((di = if_findraw(ifp, fd)) == NULL)
		return -1;
	*flags = RAW_EOF; /* We only ever read one packet. */
	mlen = len;
	*flags = RAW_EOF; /* We only ever read one packet. */
	r = dlpi_recv(di->dh, NULL, NULL, data, &mlen, -1, NULL);
	return r == DLPI_SUCCESS ? (ssize_t)mlen : -1;
}

int
if_address(unsigned char cmd, const struct ipv4_addr *ia)
{
	struct sockaddr_storage	ss_addr, ss_mask;
	struct sockaddr_in	*sin_addr, *sin_mask;

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

	if (cmd == RTM_DELADDR)
		return 0;

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

	sin_addr = (struct sockaddr_in *)&ss_addr;
	sin_addr->sin_family = AF_INET;
	sin_addr->sin_addr = ia->addr;
	sin_mask = (struct sockaddr_in *)&ss_mask;
	sin_mask->sin_family = AF_INET;
	sin_mask->sin_addr = ia->mask;
	return if_addaddr(ia->iface->ctx->pf_inet_fd,
	    ia->alias, &ss_addr, &ss_mask);
}

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

	aflags = if_addrflags0(ifp->ctx->pf_inet_fd, alias);
	if (aflags == -1)
		return -1;
	flags = 0;
	if (aflags & IFF_DUPLICATE)
		flags |= IN_IFF_DUPLICATED;
	return flags;
}

int
if_route(unsigned char cmd, const struct rt *rt)
{
	struct sockaddr_in	dst = {
		.sin_family = AF_INET,
		.sin_addr = rt->dest
	};
	struct sockaddr_in	mask = {
		.sin_family = AF_INET,
		.sin_addr = rt->mask
	};
	struct sockaddr_in	gate = {
		.sin_family = AF_INET,
		.sin_addr = rt->gate
	};
	struct sockaddr_in	src = {
		.sin_family = AF_INET,
		.sin_addr = rt->src
	};
	struct sockaddr_in	*g;
	int			addrs, flags;

	addrs = 0;
	flags = 0;

	if (cmd == RTM_ADD || cmd == RTM_CHANGE) {
		addrs |= RTA_GATEWAY | RTA_IFP;
		/* Subnet routes are cloning or connected if supported.
		 * All other routes are static. */
		if (rt->gate.s_addr != ntohl(INADDR_ANY))
			flags |= RTF_STATIC;
		if (rt->src.s_addr != ntohl(INADDR_ANY))
			addrs |= RTA_IFA;
	}

	if (rt->mask.s_addr == htonl(INADDR_BROADCAST) &&
	    rt->gate.s_addr == htonl(INADDR_ANY))
	{
		flags |= RTF_HOST;
	} else if (rt->gate.s_addr == htonl(INADDR_LOOPBACK) &&
	    rt->mask.s_addr == htonl(INADDR_BROADCAST))
	{
		flags |= RTF_HOST | RTF_GATEWAY;
		/* Going via lo0 so remove the interface flags */
		addrs &= ~(RTA_IFA | RTA_IFP);
	} else {
		addrs |= RTA_NETMASK;
		if (flags & RTF_STATIC)
			flags |= RTF_GATEWAY;
		if (rt->mask.s_addr == htonl(INADDR_BROADCAST))
			flags |= RTF_HOST;
	}

	if ((flags & RTF_HOST && rt->gate.s_addr == htonl(INADDR_ANY)) ||
	    !(flags & RTF_STATIC))
		g = &src;
	else
		g = &gate;
	return if_rtmsg(cmd, rt->iface, addrs, flags,
	    (struct sockaddr *)&dst, (struct sockaddr *)&mask,
	    (struct sockaddr *)g, (struct sockaddr *)&src, rt->mtu);
}

static int
if_walkrt(struct dhcpcd_ctx *ctx, char *data, size_t len)
{
	mib2_ipRouteEntry_t *re, *e;
	struct rt rt;
	char ifname[IF_NAMESIZE];

	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));
		rt.dest.s_addr = re->ipRouteDest;
		rt.mask.s_addr = re->ipRouteMask;
		rt.gate.s_addr = re->ipRouteNextHop;
		rt.flags = re->ipRouteInfo.re_flags;
		rt.src.s_addr = re->ipRouteInfo.re_src_addr;
		rt.mtu = re->ipRouteInfo.re_max_frag;

		if_octetstr(ifname, &re->ipRouteIfIndex, sizeof(ifname));
		rt.iface = if_find(ctx->ifaces, ifname);
		if (rt.iface != NULL) {
			if_finishrt(&rt);
			ipv4_handlert(ctx, RTM_ADD, &rt, 1);
		} else {
			char		destbuf[INET6_ADDRSTRLEN];
			char		gatebuf[INET6_ADDRSTRLEN];
			const char	*dest, *gate;

			dest = inet_ntop(AF_INET, &rt.dest,
			    destbuf, INET6_ADDRSTRLEN);
			gate = inet_ntop(AF_INET, &rt.gate,
			    gatebuf, INET6_ADDRSTRLEN);
			logger(ctx, LOG_ERR,
			    "no iface (%s) for route to %s via %s",
			    ifname, dest, gate);
		}
	} while (++re < e);
	return 0;
}

int
if_initrt(struct dhcpcd_ctx *ctx)
{

	ipv4_freerts(ctx->ipv4_kroutes);
	return if_parsert(ctx, MIB2_IP,MIB2_IP_ROUTE, if_walkrt);
}
#endif

#ifdef INET6
int
if_address6(unsigned char cmd, const struct ipv6_addr *ia)
{
	struct sockaddr_storage	ss_addr, ss_mask;
	struct sockaddr_in6	*sin6_addr, *sin6_mask;
	struct priv		*priv;
	int			r;

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

	if (cmd == RTM_DELADDR)
		return 0;

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

	priv = (struct priv *)ia->iface->ctx->priv;
	sin6_addr = (struct sockaddr_in6 *)&ss_addr;
	sin6_addr->sin6_family = AF_INET6;
	sin6_addr->sin6_addr = ia->addr;
	sin6_mask = (struct sockaddr_in6 *)&ss_mask;
	sin6_mask->sin6_family = AF_INET6;
	ipv6_mask(&sin6_mask->sin6_addr, ia->prefix_len);
	r = if_addaddr(priv->pf_inet6_fd,
	    ia->alias, &ss_addr, &ss_mask);
	if (r == -1 && errno == EEXIST)
		return 0;
	return r;
}

int
if_addrflags6(const struct interface *ifp, __unused const struct in6_addr *addr,
    const char *alias)
{
	struct priv		*priv;
	int			aflags, flags;

	priv = (struct priv *)ifp->ctx->priv;
	aflags = if_addrflags0(priv->pf_inet6_fd, alias);
	flags = 0;
	if (aflags & IFF_DUPLICATE)
		flags |= IN6_IFF_DUPLICATED;
	return flags;
}

int
if_getlifetime6(struct ipv6_addr *addr)
{

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

int
if_route6(unsigned char cmd, const struct rt6 *rt)
{
	struct sockaddr_in6	dst = {
		.sin6_family = AF_INET6,
		.sin6_addr = rt->dest
	};
	struct sockaddr_in6	mask = {
		.sin6_family = AF_INET6,
		.sin6_addr = rt->mask
	};
	struct sockaddr_in6	gate = {
		.sin6_family = AF_INET6,
		.sin6_addr = rt->gate
	};
	struct sockaddr_in6	src = {
		.sin6_family = AF_INET6,
		.sin6_addr = rt->src
	};
	struct sockaddr_in6	*g;
	int			addrs, flags;

	addrs = RTA_NETMASK;
	flags = 0;

	if (IN6_IS_ADDR_UNSPECIFIED(&rt->gate)) {
		/* XXX FIXME: How to tell kernel route is subnet? */
	} else
		flags |= RTF_GATEWAY | RTF_STATIC;

	if (IN6_IS_ADDR_UNSPECIFIED(&rt->gate))
		g = &src;
	else
		g = &gate;

	return if_rtmsg(cmd, rt->iface, addrs, flags,
	    (struct sockaddr *)&dst, (struct sockaddr *)&mask,
	    (struct sockaddr *)g, (struct sockaddr *)&src, rt->mtu);
}

static int
if_walkrt6(struct dhcpcd_ctx *ctx, char *data, size_t len)
{
	mib2_ipv6RouteEntry_t *re, *e;
	struct rt6 rt;
	char ifname[IF_NAMESIZE];

	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));
		rt.dest = re->ipv6RouteDest;
		ipv6_mask(&rt.mask, re->ipv6RoutePfxLength);
		rt.gate = re->ipv6RouteNextHop;
		rt.mtu = re->ipv6RouteInfo.re_max_frag;

		if_octetstr(ifname, &re->ipv6RouteIfIndex, sizeof(ifname));
		rt.iface = if_find(ctx->ifaces, ifname);
		if (rt.iface != NULL) {
			if_finishrt6(&rt);
			ipv6_handlert(ctx, RTM_ADD, &rt);
		} else {
			char destbuf[INET6_ADDRSTRLEN];
			char gatebuf[INET6_ADDRSTRLEN];
			const char *dest, *gate;

			dest = inet_ntop(AF_INET6, &rt.dest,
			    destbuf, INET6_ADDRSTRLEN);
			gate = inet_ntop(AF_INET6, &rt.gate,
			    gatebuf, INET6_ADDRSTRLEN);
			logger(ctx, LOG_ERR,
			    "no iface (%s) for route to %s via %s",
			    ifname, dest, gate);
		}
	} while (++re < e);
	return 0;
}

int
if_initrt6(struct dhcpcd_ctx *ctx)
{

	ipv6_freerts(&ctx->ipv6->kroutes);
	return if_parsert(ctx, MIB2_IP6, MIB2_IP6_ROUTE, if_walkrt6);
}

int
if_checkipv6(__unused struct dhcpcd_ctx *ctx,
    __unused const struct interface *ifp, int __unused own)
{

	return 0;
}
#endif
