/* 
 * dhcpcd - DHCP client daemon
 * Copyright 2006-2008 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 <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/param.h>

#include <arpa/inet.h>

/* Netlink suff */
#ifdef __linux__
#include <asm/types.h> /* Needed for 2.4 kernels */
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <netinet/ether.h>
#include <netpacket/packet.h>
#else
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/in.h>
#endif

#include <ctype.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "config.h"
#include "common.h"
#include "dhcp.h"
#include "interface.h"
#include "logger.h"

void free_address (struct address_head *addresses)
{
	address_t *p;
	address_t *n;

	if (! addresses)
		return;

	p = STAILQ_FIRST (addresses);
	while (p) {
		n = STAILQ_NEXT (p, entries); 
		free (p);
		p = n;
	}
	free (addresses);
}

void free_route (struct route_head *routes)
{
	route_t *p;
	route_t *n;

	if (! routes)
		return;

	p = STAILQ_FIRST (routes);
	while (p) {
		n = STAILQ_NEXT (p, entries);
		free (p);
		p = n;
	}
	free (routes);
}

int inet_ntocidr (struct in_addr address)
{
	int cidr = 0;
	uint32_t mask = htonl (address.s_addr);

	while (mask) {
		cidr++;
		mask <<= 1;
	}

	return (cidr);
}

int inet_cidrtoaddr (int cidr, struct in_addr *addr) {
	int ocets;

	if (cidr < 0 || cidr > 32) {
		errno = EINVAL;
		return (-1);
	}
	ocets = (cidr + 7) / 8;

	memset (addr, 0, sizeof (*addr));
	if (ocets > 0) {
		memset (&addr->s_addr, 255, (size_t) ocets - 1);
		memset ((unsigned char *) &addr->s_addr + (ocets - 1),
			(256 - (1 << (32 - cidr) % 8)), 1);
	}

	return (0);
}

uint32_t get_netmask (uint32_t addr)
{
	uint32_t dst;

	if (addr == 0)
		return (0);

	dst = htonl (addr);
	if (IN_CLASSA (dst))
		return (ntohl (IN_CLASSA_NET));
	if (IN_CLASSB (dst))
		return (ntohl (IN_CLASSB_NET));
	if (IN_CLASSC (dst))
		return (ntohl (IN_CLASSC_NET));

	return (0);
}

char *hwaddr_ntoa (const unsigned char *hwaddr, size_t hwlen)
{
	static char buffer[(HWADDR_LEN * 3) + 1];
	char *p = buffer;
	size_t i;

	for (i = 0; i < hwlen && i < HWADDR_LEN; i++) {
		if (i > 0)
			*p ++= ':';
		p += snprintf (p, 3, "%.2x", hwaddr[i]);
	}

	*p ++= '\0';

	return (buffer);
}

size_t hwaddr_aton (unsigned char *buffer, const char *addr)
{
	char c[3];
	const char *p = addr;
	unsigned char *bp = buffer;
	size_t len = 0;

	c[2] = '\0';
	while (*p) {
		c[0] = *p++;
		c[1] = *p++;
		/* Ensure that next data is EOL or a seperator with data */
		if (! (*p == '\0' || (*p == ':' && *(p + 1) != '\0'))) {
			errno = EINVAL;
			return (0);
		}
		/* Ensure that digits are hex */
		if (isxdigit ((int) c[0]) == 0 || isxdigit ((int) c[1]) == 0) {
			errno = EINVAL;
			return (0);
		}
		p++;
		if (bp)
			*bp++ = (unsigned char) strtol (c, NULL, 16);
		else
			len++;
	}

	if (bp)
		return (bp - buffer);
	return (len);
}

static int _do_interface (const char *ifname,
			  _unused unsigned char *hwaddr, _unused size_t *hwlen,
			  struct in_addr *addr,
			  bool flush, bool get)
{
	int s;
	struct ifconf ifc;
	int retval = 0;
	int len = 10 * sizeof (struct ifreq);
	int lastlen = 0;
	char *p;

	if ((s = socket (AF_INET, SOCK_DGRAM, 0)) == -1) {
		logger (LOG_ERR, "socket: %s", strerror (errno));
		return -1;
	}

	/* Not all implementations return the needed buffer size for
	 * SIOGIFCONF so we loop like so for all until it works */
	memset (&ifc, 0, sizeof (ifc));
	for (;;) {
		ifc.ifc_len = len;
		ifc.ifc_buf = xmalloc ((size_t) len);
		if (ioctl (s, SIOCGIFCONF, &ifc) == -1) {
			if (errno != EINVAL || lastlen != 0) {
				logger (LOG_ERR, "ioctl SIOCGIFCONF: %s",
					strerror (errno));
				close (s);
				free (ifc.ifc_buf);	
				return -1;
			}
		} else {
			if (ifc.ifc_len == lastlen)
				break;
			lastlen = ifc.ifc_len;
		}

		free (ifc.ifc_buf);
		ifc.ifc_buf = NULL;
		len *= 2;
	}

	for (p = ifc.ifc_buf; p < ifc.ifc_buf + ifc.ifc_len;) {
		union {
			char *buffer;
			struct ifreq *ifr;
		} ifreqs;
		struct sockaddr_in address;
		struct ifreq *ifr;

		/* Cast the ifc buffer to an ifreq cleanly */
		ifreqs.buffer = p;
		ifr = ifreqs.ifr;

#ifdef __linux__
		p += sizeof (*ifr);
#else
		p += offsetof (struct ifreq, ifr_ifru) + ifr->ifr_addr.sa_len;
#endif

		if (strcmp (ifname, ifr->ifr_name) != 0)
			continue;

#ifdef AF_LINK
		if (hwaddr && hwlen && ifr->ifr_addr.sa_family == AF_LINK) {
			struct sockaddr_dl sdl;

			memcpy (&sdl, &ifr->ifr_addr, sizeof (sdl));
			*hwlen = sdl.sdl_alen;
			memcpy (hwaddr, sdl.sdl_data + sdl.sdl_nlen,
				(size_t) sdl.sdl_alen);
			retval = 1;
			break;
		}
#endif

		if (ifr->ifr_addr.sa_family == AF_INET)	{
			memcpy (&address, &ifr->ifr_addr, sizeof (address));
			if (flush) {
				struct sockaddr_in netmask;

				if (ioctl (s, SIOCGIFNETMASK, ifr) == -1) {
					logger (LOG_ERR,
						"ioctl SIOCGIFNETMASK: %s",
						strerror (errno));
					continue;
				}
				memcpy (&netmask, &ifr->ifr_addr,
					sizeof (netmask));

				if (del_address (ifname,
						 address.sin_addr,
						 netmask.sin_addr) == -1)
					retval = -1;
			} else if (get) {
				addr->s_addr = address.sin_addr.s_addr;
				retval = 1;
				break;
			} else if (address.sin_addr.s_addr == addr->s_addr) {
				retval = 1;
				break;
			}
		}

	}

	close (s);
	free (ifc.ifc_buf);
	return retval;
}

interface_t *read_interface (const char *ifname, _unused int metric)
{
	int s;
	struct ifreq ifr;
	interface_t *iface = NULL;
	unsigned char *hwaddr = NULL;
	size_t hwlen = 0;
	sa_family_t family = 0;
	unsigned short mtu;
#ifdef __linux__
	char *p;
#endif

	if (! ifname)
		return NULL;

	memset (&ifr, 0, sizeof (ifr));
	strlcpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));

	if ((s = socket (AF_INET, SOCK_DGRAM, 0)) == -1) {
		logger (LOG_ERR, "socket: %s", strerror (errno));
		return NULL;
	}

#ifdef __linux__
	strlcpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
	if (ioctl (s, SIOCGIFHWADDR, &ifr) == -1) {
		logger (LOG_ERR, "ioctl SIOCGIFHWADDR: %s", strerror (errno));
		goto exit;
	}

	switch (ifr.ifr_hwaddr.sa_family) {
		case ARPHRD_ETHER:
		case ARPHRD_IEEE802:
			hwlen = ETHER_ADDR_LEN;
			break;
		case ARPHRD_IEEE1394:
			hwlen = EUI64_ADDR_LEN;
		case ARPHRD_INFINIBAND:
			hwlen = INFINIBAND_ADDR_LEN;
			break;
		default:
			logger (LOG_ERR,
				"interface is not Ethernet, FireWire, " \
				"InfiniBand or Token Ring");
			goto exit;
	}

	hwaddr = xmalloc (sizeof (unsigned char) * HWADDR_LEN);
	memcpy (hwaddr, ifr.ifr_hwaddr.sa_data, hwlen);
	family = ifr.ifr_hwaddr.sa_family;
#else
	ifr.ifr_metric = metric;
	strlcpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
	if (ioctl (s, SIOCSIFMETRIC, &ifr) == -1) {
		logger (LOG_ERR, "ioctl SIOCSIFMETRIC: %s", strerror (errno));
		goto exit;
	}

	hwaddr = xmalloc (sizeof (unsigned char) * HWADDR_LEN);
	if (_do_interface (ifname, hwaddr, &hwlen, NULL, false, false) != 1) {
		logger (LOG_ERR, "could not find interface %s", ifname);
		goto exit;
	}

	family = ARPHRD_ETHER;
#endif

	strlcpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
	if (ioctl (s, SIOCGIFMTU, &ifr) == -1) {
		logger (LOG_ERR, "ioctl SIOCGIFMTU: %s", strerror (errno));
		goto exit;
	}

	if (ifr.ifr_mtu < MTU_MIN) {
		logger (LOG_DEBUG, "MTU of %d is too low, setting to %d",
			ifr.ifr_mtu, MTU_MIN);
		ifr.ifr_mtu = MTU_MIN;
		strlcpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
		if (ioctl (s, SIOCSIFMTU, &ifr) == -1) {
			logger (LOG_ERR, "ioctl SIOCSIFMTU,: %s",
				strerror (errno));
			goto exit;
		}
	}
	mtu = ifr.ifr_mtu;

	strlcpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
#ifdef __linux__
	/* We can only bring the real interface up */
	if ((p = strchr (ifr.ifr_name, ':')))
		*p = '\0';
#endif
	if (ioctl (s, SIOCGIFFLAGS, &ifr) == -1) {
		logger (LOG_ERR, "ioctl SIOCGIFFLAGS: %s", strerror (errno));
		goto exit;
	}

	if (! (ifr.ifr_flags & IFF_UP) || ! (ifr.ifr_flags & IFF_RUNNING)) {
		ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
		if (ioctl (s, SIOCSIFFLAGS, &ifr) != 0) {
			logger (LOG_ERR, "ioctl SIOCSIFFLAGS: %s",
				strerror (errno));
			goto exit;
		}
	}

	iface = xzalloc (sizeof (*iface));
	strlcpy (iface->name, ifname, IF_NAMESIZE);
#ifdef ENABLE_INFO
	snprintf (iface->infofile, PATH_MAX, INFOFILE, ifname);
#endif
	memcpy (&iface->hwaddr, hwaddr, hwlen);
	iface->hwlen = hwlen;

	iface->family = family;
	iface->arpable = ! (ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK));
	iface->mtu = iface->previous_mtu = mtu;

	logger (LOG_INFO, "hardware address = %s",
		hwaddr_ntoa (iface->hwaddr, iface->hwlen));

	/* 0 is a valid fd, so init to -1 */
	iface->fd = -1;
#ifdef __linux__
	iface->listen_fd = -1;
#endif

exit:
	close (s);
	free (hwaddr);
	return iface;
}

int get_mtu (const char *ifname)
{
	struct ifreq ifr;
	int r;
	int s;

	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		logger (LOG_ERR, "socket: %s", strerror (errno));
		return (-1);
	}

	memset (&ifr, 0, sizeof (ifr));
	strlcpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
	r = ioctl (s, SIOCGIFMTU, &ifr);
	close (s);

	if (r == -1) {
		logger (LOG_ERR, "ioctl SIOCGIFMTU: %s", strerror (errno));
		return (-1);
	}

	return (ifr.ifr_mtu);
}

int set_mtu (const char *ifname, short int mtu)
{
	struct ifreq ifr;
	int r;
	int s;

	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		logger (LOG_ERR, "socket: %s", strerror (errno));
		return (-1);
	}

	memset (&ifr, 0, sizeof (ifr));
	logger (LOG_DEBUG, "setting MTU to %d", mtu);
	strlcpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
	ifr.ifr_mtu = mtu;
	r = ioctl (s, SIOCSIFMTU, &ifr);
	close (s);

	if (r == -1)
		logger (LOG_ERR, "ioctl SIOCSIFMTU: %s", strerror (errno));

	return (r == 0 ? 0 : -1);
}

static void log_route (struct in_addr destination,
		       struct in_addr netmask,
		       struct in_addr gateway,
		       _unused int metric,
		       int change, int del)
{
	char *dstd = xstrdup (inet_ntoa (destination));

#ifdef __linux__
#define METRIC " metric %d"
#else
#define METRIC ""
#endif

	if (gateway.s_addr == destination.s_addr ||
	    gateway.s_addr == INADDR_ANY)
		logger (LOG_INFO, "%s route to %s/%d" METRIC,
			change ? "changing" : del ? "removing" : "adding",
			dstd, inet_ntocidr (netmask)
#ifdef __linux__
			, metric
#endif
		       );
	else if (destination.s_addr == INADDR_ANY)
		logger (LOG_INFO, "%s default route via %s" METRIC,
			change ? "changing" : del ? "removing" : "adding",
			inet_ntoa (gateway)

#ifdef __linux__
			, metric
#endif
		       );
	else
		logger (LOG_INFO, "%s route to %s/%d via %s" METRIC,
			change ? "changing" : del ? "removing" : "adding",
			dstd, inet_ntocidr (netmask), inet_ntoa (gateway)
#ifdef __linux__
			, metric
#endif
		       );

	free (dstd);
}

#if defined(BSD) || defined(__FreeBSD_kernel__)

/* Darwin doesn't define this for some very odd reason */
#ifndef SA_SIZE
# define SA_SIZE(sa)						\
	(  (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ?	\
	   sizeof(long)		:				\
	   1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(long) - 1) ) )
#endif

static int do_address (const char *ifname, struct in_addr address,
		       struct in_addr netmask, struct in_addr broadcast,
		       int del)
{
	int s;
	struct ifaliasreq ifa;

	if (! ifname)
		return -1;

	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		logger (LOG_ERR, "socket: %s", strerror (errno));
		return -1;
	}

	memset (&ifa, 0, sizeof (ifa));
	strlcpy (ifa.ifra_name, ifname, sizeof (ifa.ifra_name));

#define ADDADDR(_var, _addr) { \
	union { struct sockaddr *sa; struct sockaddr_in *sin; } _s; \
	_s.sa = &_var; \
	_s.sin->sin_family = AF_INET; \
	_s.sin->sin_len = sizeof (*_s.sin); \
	memcpy (&_s.sin->sin_addr, &_addr, sizeof (_s.sin->sin_addr)); \
}

	ADDADDR (ifa.ifra_addr, address);
	ADDADDR (ifa.ifra_mask, netmask);
if (! del)
	ADDADDR (ifa.ifra_broadaddr, broadcast);

#undef ADDADDR

	if (ioctl (s, del ? SIOCDIFADDR : SIOCAIFADDR, &ifa) == -1) {
		logger (LOG_ERR, "ioctl %s: %s",
			del ? "SIOCDIFADDR" : "SIOCAIFADDR",
			strerror (errno));
		close (s);
		return -1;
	}

close (s);
return 0;
}

static int do_route (const char *ifname,
		     struct in_addr destination,
		     struct in_addr netmask,
		     struct in_addr gateway,
		     int metric,
		     int change, int del)
{
	int s;
	static int seq;
	union sockunion {
		struct sockaddr sa;
		struct sockaddr_in sin;
#ifdef INET6
		struct sockaddr_in6 sin6;
#endif
		struct sockaddr_dl sdl;
		struct sockaddr_storage ss;
	} su;
	struct rtm 
	{
		struct rt_msghdr hdr;
		char buffer[sizeof (su) * 3];
	} rtm;
	char *bp = rtm.buffer;
	size_t l;

	if (! ifname)
		return -1;

	log_route (destination, netmask, gateway, metric, change, del);

	if ((s = socket (PF_ROUTE, SOCK_RAW, 0)) == -1) {
		logger (LOG_ERR, "socket: %s", strerror (errno));
		return -1;
	}

	memset (&rtm, 0, sizeof (rtm));

	rtm.hdr.rtm_version = RTM_VERSION;
	rtm.hdr.rtm_seq = ++seq;
	rtm.hdr.rtm_type = change ? RTM_CHANGE : del ? RTM_DELETE : RTM_ADD;
	rtm.hdr.rtm_flags = RTF_UP | RTF_STATIC;

	/* This order is important */
	rtm.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;

#define ADDADDR(_addr) \
	memset (&su, 0, sizeof (su)); \
	su.sin.sin_family = AF_INET; \
	su.sin.sin_len = sizeof (su.sin); \
	memcpy (&su.sin.sin_addr, &_addr, sizeof (su.sin.sin_addr)); \
	l = SA_SIZE (&(su.sa)); \
	memcpy (bp, &(su), l); \
	bp += l;

	ADDADDR (destination);

	if (netmask.s_addr == INADDR_BROADCAST ||
	    gateway.s_addr == INADDR_ANY)
	{
		/* Make us a link layer socket */
		unsigned char *hwaddr;
		size_t hwlen = 0;

		if (netmask.s_addr == INADDR_BROADCAST) 
			rtm.hdr.rtm_flags |= RTF_HOST;

		hwaddr = xmalloc (sizeof (unsigned char) * HWADDR_LEN);
		_do_interface (ifname, hwaddr, &hwlen, NULL, false, false);
		memset (&su, 0, sizeof (su));
		su.sdl.sdl_len = sizeof (su.sdl);
		su.sdl.sdl_family = AF_LINK;
		su.sdl.sdl_nlen = strlen (ifname);
		memcpy (&su.sdl.sdl_data, ifname, (size_t) su.sdl.sdl_nlen);
		su.sdl.sdl_alen = hwlen;
		memcpy (((unsigned char *) &su.sdl.sdl_data) + su.sdl.sdl_nlen,
			hwaddr, (size_t) su.sdl.sdl_alen);

		l = SA_SIZE (&(su.sa));
		memcpy (bp, &su, l);
		bp += l;
		free (hwaddr);
	} else {
		rtm.hdr.rtm_flags |= RTF_GATEWAY;
		ADDADDR (gateway);
	}

	ADDADDR (netmask);
#undef ADDADDR

	rtm.hdr.rtm_msglen = l = bp - (char *)&rtm;
	if (write (s, &rtm, l) == -1) {
		/* Don't report error about routes already existing */
		if (errno != EEXIST)
			logger (LOG_ERR, "write: %s", strerror (errno));
		close (s);
		return -1;
	}

	close (s);
	return 0;
}

#elif __linux__
/* This netlink stuff is overly compex IMO.
 * The BSD implementation is much cleaner and a lot less code.
 * send_netlink handles the actual transmission so we can work out
 * if there was an error or not. */
#define BUFFERLEN 256
int send_netlink (struct nlmsghdr *hdr, netlink_callback callback, void *arg)
{
	int s;
	pid_t mypid = getpid ();
	struct sockaddr_nl nl;
	struct iovec iov;
	struct msghdr msg;
	static unsigned int seq;
	char *buffer;
	ssize_t bytes;
	union
	{
		char *buffer;
		struct nlmsghdr *nlm;
	} h;

	if ((s = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) {
		logger (LOG_ERR, "socket: %s", strerror (errno));
		return -1;
	}

	memset (&nl, 0, sizeof (nl));
	nl.nl_family = AF_NETLINK;
	if (bind (s, (struct sockaddr *) &nl, sizeof (nl)) == -1) {
		logger (LOG_ERR, "bind: %s", strerror (errno));
		close (s);
		return -1;
	}

	memset (&iov, 0, sizeof (iov));
	iov.iov_base = hdr;
	iov.iov_len = hdr->nlmsg_len;

	memset (&msg, 0, sizeof (msg));
	msg.msg_name = &nl;
	msg.msg_namelen = sizeof (nl);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

	/* Request a reply */
	hdr->nlmsg_flags |= NLM_F_ACK;
	hdr->nlmsg_seq = ++seq;

	if (sendmsg (s, &msg, 0) == -1) {
		logger (LOG_ERR, "write: %s", strerror (errno));
		close (s);
		return -1;
	}

	buffer = xzalloc (sizeof (char) * BUFFERLEN);
	iov.iov_base = buffer;

	for (;;) {
		iov.iov_len = BUFFERLEN;
		bytes = recvmsg (s, &msg, 0);

		if (bytes == -1) {
			if (errno != EINTR)
				logger (LOG_ERR, "recvmsg: %s",
					strerror (errno));
			continue;
		}

		if (bytes == 0) {
			logger (LOG_ERR, "netlink: EOF");
			goto eexit;
		}

		if (msg.msg_namelen != sizeof (nl)) {
			logger (LOG_ERR,
				"netlink: sender address length mismatch");
			goto eexit;
		}

		for (h.buffer = buffer; bytes >= (signed) sizeof (*h.nlm); ) {
			int len = h.nlm->nlmsg_len;
			int l = len - sizeof (*h.nlm);
			struct nlmsgerr *err = (struct nlmsgerr *) NLMSG_DATA (h.nlm);

			if (l < 0 || len > bytes) {
				if (msg.msg_flags & MSG_TRUNC)
					logger (LOG_ERR, "netlink: truncated message");
				else
					logger (LOG_ERR, "netlink: malformed message");
				goto eexit;
			}

			/* Ensure it's our message */
			if (nl.nl_pid != 0 ||
			    (pid_t) h.nlm->nlmsg_pid != mypid ||
			    h.nlm->nlmsg_seq != seq)
			{
				/* Next Message */
				bytes -= NLMSG_ALIGN (len);
				h.buffer += NLMSG_ALIGN (len);
				continue;
			}

			/* We get an NLMSG_ERROR back with a code of zero for success */
			if (h.nlm->nlmsg_type != NLMSG_ERROR) {
				logger (LOG_ERR, "netlink: unexpected reply %d",
				       	h.nlm->nlmsg_type);
				goto eexit;
			}

			if ((unsigned) l < sizeof (*err)) {
				logger (LOG_ERR, "netlink: error truncated");
				goto eexit;
			}

			if (err->error == 0) {
				int retval = 0;

				close (s);
				if (callback) {
					if ((retval = callback (hdr, arg)) == -1)
						logger (LOG_ERR, "netlink: callback failed");
				}
				free (buffer);
				return (retval);
			}

			errno = -err->error;
			/* Don't report on something already existing */
			if (errno != EEXIST)
				logger (LOG_ERR, "netlink: %s",
					strerror (errno));
			goto eexit;
		}
	}

eexit:
	close (s);
	free (buffer);
	return -1;
}

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

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

	if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen) {
		logger (LOG_ERR, "add_attr_l: message exceeded bound of %d\n",
			maxlen);
		return -1;
	}

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

	return 0;
}

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

	if (NLMSG_ALIGN (n->nlmsg_len) + len > maxlen) {
		logger (LOG_ERR, "add_attr32: message exceeded bound of %d\n",
			maxlen);
		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;
}

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

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

static int do_address(const char *ifname,
		      struct in_addr address, struct in_addr netmask,
		      struct in_addr broadcast, int del)
{
	struct nlma *nlm;
	int retval;

	if (!ifname)
		return -1;

	nlm = xzalloc (sizeof (*nlm));
	nlm->hdr.nlmsg_len = NLMSG_LENGTH (sizeof (struct ifaddrmsg));
	nlm->hdr.nlmsg_flags = NLM_F_REQUEST;
	if (! del)
		nlm->hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE;
	nlm->hdr.nlmsg_type = del ? RTM_DELADDR : RTM_NEWADDR;
	if (! (nlm->ifa.ifa_index = if_nametoindex (ifname))) {
		logger (LOG_ERR, "if_nametoindex: no index for interface `%s'",
			ifname);
		free (nlm);
		return -1;
	}
	nlm->ifa.ifa_family = AF_INET;

	nlm->ifa.ifa_prefixlen = inet_ntocidr (netmask);

	/* This creates the aliased interface */
	add_attr_l (&nlm->hdr, sizeof (*nlm), IFA_LABEL,
		    ifname, strlen (ifname) + 1);

	add_attr_l (&nlm->hdr, sizeof (*nlm), IFA_LOCAL,
		    &address.s_addr, sizeof (address.s_addr));
	if (! del)
		add_attr_l (&nlm->hdr, sizeof (*nlm), IFA_BROADCAST,
			    &broadcast.s_addr, sizeof (broadcast.s_addr));

	retval = send_netlink (&nlm->hdr, NULL, NULL);
	free (nlm);
	return retval;
}

static int do_route (const char *ifname,
		     struct in_addr destination,
		     struct in_addr netmask,
		     struct in_addr gateway,
		     int metric, int change, int del)
{
	struct nlmr *nlm;
	unsigned int ifindex;
	int retval;

	if (! ifname)
		return -1;

	log_route (destination, netmask, gateway, metric, change, del);

	if (! (ifindex = if_nametoindex (ifname))) {
		logger (LOG_ERR, "if_nametoindex: no index for interface `%s'",
			ifname);
		return -1;
	}

	nlm = xzalloc (sizeof (*nlm));
	nlm->hdr.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtmsg));
	if (change)
		nlm->hdr.nlmsg_flags = NLM_F_REPLACE;
	else if (! del)
		nlm->hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL;
	nlm->hdr.nlmsg_flags |= NLM_F_REQUEST;
	nlm->hdr.nlmsg_type = del ? RTM_DELROUTE : RTM_NEWROUTE;
	nlm->rt.rtm_family = AF_INET;
	nlm->rt.rtm_table = RT_TABLE_MAIN;

	if (del)
		nlm->rt.rtm_scope = RT_SCOPE_NOWHERE;
	else {
		nlm->hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL;
		nlm->rt.rtm_protocol = RTPROT_BOOT;
		if (netmask.s_addr == INADDR_BROADCAST ||
		    gateway.s_addr == INADDR_ANY)
			nlm->rt.rtm_scope = RT_SCOPE_LINK;
		else
			nlm->rt.rtm_scope = RT_SCOPE_UNIVERSE;
		nlm->rt.rtm_type = RTN_UNICAST;
	}

	nlm->rt.rtm_dst_len = inet_ntocidr (netmask);
	add_attr_l (&nlm->hdr, sizeof (*nlm), RTA_DST,
		    &destination.s_addr, sizeof (destination.s_addr));
	if (netmask.s_addr != INADDR_BROADCAST &&
	    destination.s_addr != gateway.s_addr)
		add_attr_l (&nlm->hdr, sizeof (*nlm), RTA_GATEWAY,
			    &gateway.s_addr, sizeof (gateway.s_addr));

	add_attr_32 (&nlm->hdr, sizeof (*nlm), RTA_OIF, ifindex);
	add_attr_32 (&nlm->hdr, sizeof (*nlm), RTA_PRIORITY, metric);

	retval = send_netlink (&nlm->hdr, NULL, NULL);
	free (nlm);
	return retval;
}

#else
 #error "Platform not supported!"
 #error "We currently support BPF and Linux sockets."
 #error "Other platforms may work using BPF. If yours does, please let me know"
 #error "so I can add it to our list."
#endif

int add_address (const char *ifname, struct in_addr address,
		 struct in_addr netmask, struct in_addr broadcast)
{
	logger (LOG_INFO, "adding IP address %s/%d",
		inet_ntoa (address), inet_ntocidr (netmask));

	return (do_address (ifname, address, netmask, broadcast, 0));
}

int del_address (const char *ifname,
		 struct in_addr address, struct in_addr netmask)
{
	struct in_addr t;

	logger (LOG_INFO, "removing IP address %s/%d",
		inet_ntoa (address), inet_ntocidr (netmask));

	memset (&t, 0, sizeof (t));
	return (do_address (ifname, address, netmask, t, 1));
}

int add_route (const char *ifname, struct in_addr destination,
	       struct in_addr netmask, struct in_addr gateway, int metric)
{
	return (do_route (ifname, destination, netmask, gateway, metric, 0, 0));
}

int change_route (const char *ifname, struct in_addr destination,
		  struct in_addr netmask, struct in_addr gateway, int metric)
{
	return (do_route (ifname, destination, netmask, gateway, metric, 1, 0));
}

int del_route (const char *ifname, struct in_addr destination,
	       struct in_addr netmask, struct in_addr gateway, int metric)
{
	return (do_route (ifname, destination, netmask, gateway, metric, 0, 1));
}


int flush_addresses (const char *ifname)
{
	return (_do_interface (ifname, NULL, NULL, NULL, true, false));
}

in_addr_t get_address (const char *ifname)
{
	struct in_addr address;
	if (_do_interface (ifname, NULL, NULL, &address, false, true) > 0)
		return (address.s_addr);
	return (0);
}

int has_address (const char *ifname, struct in_addr address)
{
	return (_do_interface (ifname, NULL, NULL, &address, false, false));
}
