C++程序  |  1999行  |  48.67 KB

/*
 * dhcpcd - DHCP client daemon
 * Copyright (c) 2006-2015 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.
 */

const char dhcpcd_copyright[] = "Copyright (c) 2006-2015 Roy Marples";

#define _WITH_DPRINTF /* Stop FreeBSD bitching */

#include <sys/file.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uio.h>

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

#if defined(__ANDROID__)
#include <sys/capability.h>
#include <sys/prctl.h>
#include <private/android_filesystem_config.h>
#endif  /* __ANDROID__ */

#include "config.h"
#include "arp.h"
#include "common.h"
#include "control.h"
#include "dev.h"
#include "dhcpcd.h"
#include "dhcp6.h"
#include "duid.h"
#include "eloop.h"
#include "if.h"
#include "if-options.h"
#include "ipv4.h"
#include "ipv6.h"
#include "ipv6nd.h"
#include "rpc-interface.h"
#include "script.h"

#ifdef USE_SIGNALS
const int dhcpcd_handlesigs[] = {
	SIGTERM,
	SIGINT,
	SIGALRM,
	SIGHUP,
	SIGUSR1,
	SIGUSR2,
	SIGPIPE,
	0
};

/* Handling signals needs *some* context */
static struct dhcpcd_ctx *dhcpcd_ctx;
#endif

#if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK)
static pid_t
read_pid(const char *pidfile)
{
	FILE *fp;
	pid_t pid;

	if ((fp = fopen(pidfile, "r")) == NULL) {
		errno = ENOENT;
		return 0;
	}
	if (fscanf(fp, "%d", &pid) != 1)
		pid = 0;
	fclose(fp);
	return pid;
}

static int
write_pid(int fd, pid_t pid)
{

	if (ftruncate(fd, (off_t)0) == -1)
		return -1;
	lseek(fd, (off_t)0, SEEK_SET);
	return dprintf(fd, "%d\n", (int)pid);
}
#endif

static void
usage(void)
{

printf("usage: "PACKAGE"\t[-46ABbDdEGgHJKkLnpqTVw]\n"
	"\t\t[-C, --nohook hook] [-c, --script script]\n"
	"\t\t[-e, --env value] [-F, --fqdn FQDN] [-f, --config file]\n"
	"\t\t[-h, --hostname hostname] [-I, --clientid clientid]\n"
	"\t\t[-i, --vendorclassid vendorclassid] [-l, --leasetime seconds]\n"
	"\t\t[-m, --metric metric] [-O, --nooption option]\n"
	"\t\t[-o, --option option] [-Q, --require option]\n"
	"\t\t[-r, --request address] [-S, --static value]\n"
	"\t\t[-s, --inform address[/cidr]] [-t, --timeout seconds]\n"
	"\t\t[-u, --userclass class] [-v, --vendor code, value]\n"
	"\t\t[-W, --whitelist address[/cidr]] [-y, --reboot seconds]\n"
	"\t\t[-X, --blacklist address[/cidr]] [-Z, --denyinterfaces pattern]\n"
	"\t\t[-z, --allowinterfaces pattern] [interface] [...]\n"
	"       "PACKAGE"\t-k, --release [interface]\n"
	"       "PACKAGE"\t-U, --dumplease interface\n"
	"       "PACKAGE"\t--version\n"
	"       "PACKAGE"\t-x, --exit [interface]\n");
}

static void
free_globals(struct dhcpcd_ctx *ctx)
{
	struct dhcp_opt *opt;

	if (ctx->ifac) {
		for (; ctx->ifac > 0; ctx->ifac--)
			free(ctx->ifav[ctx->ifac - 1]);
		free(ctx->ifav);
		ctx->ifav = NULL;
	}
	if (ctx->ifdc) {
		for (; ctx->ifdc > 0; ctx->ifdc--)
			free(ctx->ifdv[ctx->ifdc - 1]);
		free(ctx->ifdv);
		ctx->ifdv = NULL;
	}
	if (ctx->ifcc) {
		for (; ctx->ifcc > 0; ctx->ifcc--)
			free(ctx->ifcv[ctx->ifcc - 1]);
		free(ctx->ifcv);
		ctx->ifcv = NULL;
	}

#ifdef INET
	if (ctx->dhcp_opts) {
		for (opt = ctx->dhcp_opts;
		    ctx->dhcp_opts_len > 0;
		    opt++, ctx->dhcp_opts_len--)
			free_dhcp_opt_embenc(opt);
		free(ctx->dhcp_opts);
		ctx->dhcp_opts = NULL;
	}
#endif
#ifdef INET6
	if (ctx->dhcp6_opts) {
		for (opt = ctx->dhcp6_opts;
		    ctx->dhcp6_opts_len > 0;
		    opt++, ctx->dhcp6_opts_len--)
			free_dhcp_opt_embenc(opt);
		free(ctx->dhcp6_opts);
		ctx->dhcp6_opts = NULL;
	}
#endif
	if (ctx->vivso) {
		for (opt = ctx->vivso;
		    ctx->vivso_len > 0;
		    opt++, ctx->vivso_len--)
			free_dhcp_opt_embenc(opt);
		free(ctx->vivso);
		ctx->vivso = NULL;
	}
}

static void
handle_exit_timeout(void *arg)
{
	struct dhcpcd_ctx *ctx;

	ctx = arg;
	logger(ctx, LOG_ERR, "timed out");
	if (!(ctx->options & DHCPCD_MASTER)) {
		eloop_exit(ctx->eloop, EXIT_FAILURE);
		return;
	}
	ctx->options |= DHCPCD_NOWAITIP;
	dhcpcd_daemonise(ctx);
}

int
dhcpcd_oneup(struct dhcpcd_ctx *ctx)
{
	const struct interface *ifp;

	TAILQ_FOREACH(ifp, ctx->ifaces, next) {
		if (D_STATE_RUNNING(ifp) ||
		    RS_STATE_RUNNING(ifp) ||
		    D6_STATE_RUNNING(ifp))
			return 1;
	}
	return 0;
}

int
dhcpcd_ipwaited(struct dhcpcd_ctx *ctx)
{

	if (ctx->options & DHCPCD_WAITIP4 &&
	    !ipv4_addrexists(ctx, NULL))
		return 0;
	if (ctx->options & DHCPCD_WAITIP6 &&
	    !ipv6nd_findaddr(ctx, NULL, 0) &&
	    !dhcp6_findaddr(ctx, NULL, 0))
		return 0;
	if (ctx->options & DHCPCD_WAITIP &&
	    !(ctx->options & (DHCPCD_WAITIP4 | DHCPCD_WAITIP6)) &&
	    !ipv4_addrexists(ctx, NULL) &&
	    !ipv6nd_findaddr(ctx, NULL, 0) &&
	    !dhcp6_findaddr(ctx, NULL, 0))
		return 0;
	return 1;
}

/* Returns the pid of the child, otherwise 0. */
pid_t
dhcpcd_daemonise(struct dhcpcd_ctx *ctx)
{
#ifdef THERE_IS_NO_FORK
	eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx);
	errno = ENOSYS;
	return 0;
#else
	pid_t pid;
	char buf = '\0';
	int sidpipe[2], fd;

	if (ctx->options & DHCPCD_DAEMONISE &&
	    !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP)))
	{
		if (!dhcpcd_ipwaited(ctx))
			return 0;
	}

	eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx);
	if (ctx->options & DHCPCD_DAEMONISED ||
	    !(ctx->options & DHCPCD_DAEMONISE))
		return 0;
	/* Setup a signal pipe so parent knows when to exit. */
	if (pipe(sidpipe) == -1) {
		logger(ctx, LOG_ERR, "pipe: %m");
		return 0;
	}
	logger(ctx, LOG_DEBUG, "forking to background");
	switch (pid = fork()) {
	case -1:
		logger(ctx, LOG_ERR, "fork: %m");
		return 0;
	case 0:
		setsid();
		/* Some polling methods don't survive after forking,
		 * so ensure we can requeue all our events. */
		if (eloop_requeue(ctx->eloop) == -1) {
			logger(ctx, LOG_ERR, "eloop_requeue: %m");
			eloop_exit(ctx->eloop, EXIT_FAILURE);
		}
		/* Notify parent it's safe to exit as we've detached. */
		close(sidpipe[0]);
		if (write(sidpipe[1], &buf, 1) == -1)
			logger(ctx, LOG_ERR, "failed to notify parent: %m");
		close(sidpipe[1]);
		if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
			dup2(fd, STDIN_FILENO);
			dup2(fd, STDOUT_FILENO);
			dup2(fd, STDERR_FILENO);
			close(fd);
		}
		break;
	default:
		/* Wait for child to detach */
		close(sidpipe[1]);
		if (read(sidpipe[0], &buf, 1) == -1)
			logger(ctx, LOG_ERR, "failed to read child: %m");
		close(sidpipe[0]);
		break;
	}
	/* Done with the fd now */
	if (pid != 0) {
		logger(ctx, LOG_INFO, "forked to background, child pid %d", pid);
		write_pid(ctx->pid_fd, pid);
		close(ctx->pid_fd);
		ctx->pid_fd = -1;
		ctx->options |= DHCPCD_FORKED;
		eloop_exit(ctx->eloop, EXIT_SUCCESS);
		return pid;
	}
	ctx->options |= DHCPCD_DAEMONISED;
	return pid;
#endif
}

static void
dhcpcd_drop(struct interface *ifp, int stop)
{

	dhcp6_drop(ifp, stop ? NULL : "EXPIRE6");
	ipv6nd_drop(ifp);
	ipv6_drop(ifp);
	dhcp_drop(ifp, stop ? "STOP" : "EXPIRE");
	arp_close(ifp);
}

static void
stop_interface(struct interface *ifp)
{
	struct dhcpcd_ctx *ctx;

	ctx = ifp->ctx;
	logger(ctx, LOG_INFO, "%s: removing interface", ifp->name);
	ifp->options->options |= DHCPCD_STOPPING;

	dhcpcd_drop(ifp, 1);
	if (ifp->options->options & DHCPCD_DEPARTED)
		script_runreason(ifp, "DEPARTED");
	else
		script_runreason(ifp, "STOPPED");

	/* Delete all timeouts for the interfaces */
	eloop_q_timeout_delete(ctx->eloop, 0, NULL, ifp);

	/* Remove the interface from our list */
	TAILQ_REMOVE(ifp->ctx->ifaces, ifp, next);
	if_free(ifp);

	if (!(ctx->options & (DHCPCD_MASTER | DHCPCD_TEST)))
		eloop_exit(ctx->eloop, EXIT_FAILURE);
}

static void
configure_interface1(struct interface *ifp)
{
	struct if_options *ifo = ifp->options;
	int ra_global, ra_iface;
#ifdef INET6
	size_t i;
#endif

	/* Do any platform specific configuration */
	if_conf(ifp);

	/* If we want to release a lease, we can't really persist the
	 * address either. */
	if (ifo->options & DHCPCD_RELEASE)
		ifo->options &= ~DHCPCD_PERSISTENT;

	if (ifp->flags & IFF_POINTOPOINT && !(ifo->options & DHCPCD_INFORM))
		ifo->options |= DHCPCD_STATIC;
	if (ifp->flags & IFF_NOARP ||
	    ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))
		ifo->options &= ~(DHCPCD_ARP | DHCPCD_IPV4LL);
	if (ifp->flags & (IFF_POINTOPOINT | IFF_LOOPBACK) ||
	    !(ifp->flags & IFF_MULTICAST))
		ifo->options &= ~DHCPCD_IPV6RS;

	if (ifo->metric != -1)
		ifp->metric = (unsigned int)ifo->metric;

	if (!(ifo->options & DHCPCD_IPV4))
		ifo->options &= ~(DHCPCD_DHCP | DHCPCD_IPV4LL);

	if (!(ifo->options & DHCPCD_IPV6))
		ifo->options &= ~(DHCPCD_IPV6RS | DHCPCD_DHCP6);

	if (ifo->options & DHCPCD_SLAACPRIVATE &&
	    !(ifp->ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST)))
		ifo->options |= DHCPCD_IPV6RA_OWN;

	/* If we're a psuedo interface, ensure we disable as much as we can */
	if (ifp->options->options & DHCPCD_PFXDLGONLY)
		ifp->options->options &= ~(DHCPCD_IPV4 | DHCPCD_IPV6RS);

	/* We want to disable kernel interface RA as early as possible. */
	if (ifo->options & DHCPCD_IPV6RS &&
	    !(ifp->ctx->options & DHCPCD_DUMPLEASE))
	{
		/* If not doing any DHCP, disable the RDNSS requirement. */
		if (!(ifo->options & (DHCPCD_DHCP | DHCPCD_DHCP6)))
			ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS;
		ra_global = if_checkipv6(ifp->ctx, NULL,
		    ifp->ctx->options & DHCPCD_IPV6RA_OWN ? 1 : 0);
		ra_iface = if_checkipv6(ifp->ctx, ifp,
		    ifp->options->options & DHCPCD_IPV6RA_OWN ? 1 : 0);
		if (ra_global == -1 || ra_iface == -1)
			ifo->options &= ~DHCPCD_IPV6RS;
		else if (ra_iface == 0 &&
		    !(ifp->ctx->options & DHCPCD_TEST))
			ifo->options |= DHCPCD_IPV6RA_OWN;
	}

	/* If we haven't specified a ClientID and our hardware address
	 * length is greater than DHCP_CHADDR_LEN then we enforce a ClientID
	 * of the hardware address family and the hardware address.
	 * If there is no hardware address and no ClientID set,
	 * force a DUID based ClientID. */
	if (ifp->hwlen > DHCP_CHADDR_LEN)
		ifo->options |= DHCPCD_CLIENTID;
	else if (ifp->hwlen == 0 && !(ifo->options & DHCPCD_CLIENTID))
		ifo->options |= DHCPCD_CLIENTID | DHCPCD_DUID;

	/* Firewire and InfiniBand interfaces require ClientID and
	 * the broadcast option being set. */
	switch (ifp->family) {
	case ARPHRD_IEEE1394:	/* FALLTHROUGH */
	case ARPHRD_INFINIBAND:
		ifo->options |= DHCPCD_CLIENTID | DHCPCD_BROADCAST;
		break;
	}

	if (!(ifo->options & DHCPCD_IAID)) {
		/*
		 * An IAID is for identifying a unqiue interface within
		 * the client. It is 4 bytes long. Working out a default
		 * value is problematic.
		 *
		 * Interface name and number are not stable
		 * between different OS's. Some OS's also cannot make
		 * up their mind what the interface should be called
		 * (yes, udev, I'm looking at you).
		 * Also, the name could be longer than 4 bytes.
		 * Also, with pluggable interfaces the name and index
		 * could easily get swapped per actual interface.
		 *
		 * The MAC address is 6 bytes long, the final 3
		 * being unique to the manufacturer and the initial 3
		 * being unique to the organisation which makes it.
		 * We could use the last 4 bytes of the MAC address
		 * as the IAID as it's the most stable part given the
		 * above, but equally it's not guaranteed to be
		 * unique.
		 *
		 * Given the above, and our need to reliably work
		 * between reboots without persitent storage,
		 * generating the IAID from the MAC address is the only
		 * logical default.
		 *
		 * dhclient uses the last 4 bytes of the MAC address.
		 * dibbler uses an increamenting counter.
		 * wide-dhcpv6 uses 0 or a configured value.
		 * odhcp6c uses 1.
		 * Windows 7 uses the first 3 bytes of the MAC address
		 * and an unknown byte.
		 * dhcpcd-6.1.0 and earlier used the interface name,
		 * falling back to interface index if name > 4.
		 */
		if (ifp->hwlen >= sizeof(ifo->iaid))
			memcpy(ifo->iaid,
			    ifp->hwaddr + ifp->hwlen - sizeof(ifo->iaid),
			    sizeof(ifo->iaid));
		else {
			uint32_t len;

			len = (uint32_t)strlen(ifp->name);
			if (len <= sizeof(ifo->iaid)) {
				memcpy(ifo->iaid, ifp->name, len);
				if (len < sizeof(ifo->iaid))
					memset(ifo->iaid + len, 0,
					    sizeof(ifo->iaid) - len);
			} else {
				/* IAID is the same size as a uint32_t */
				len = htonl(ifp->index);
				memcpy(ifo->iaid, &len, sizeof(len));
			}
		}
		ifo->options |= DHCPCD_IAID;
	}

#ifdef INET6
	if (ifo->ia_len == 0 && ifo->options & DHCPCD_IPV6 &&
	    ifp->name[0] != '\0')
	{
		ifo->ia = malloc(sizeof(*ifo->ia));
		if (ifo->ia == NULL)
			logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
		else {
			ifo->ia_len = 1;
			ifo->ia->ia_type = D6_OPTION_IA_NA;
			memcpy(ifo->ia->iaid, ifo->iaid, sizeof(ifo->iaid));
			memset(&ifo->ia->addr, 0, sizeof(ifo->ia->addr));
			ifo->ia->sla = NULL;
			ifo->ia->sla_len = 0;
		}
	} else {
		for (i = 0; i < ifo->ia_len; i++) {
			if (!ifo->ia[i].iaid_set) {
				memcpy(&ifo->ia[i].iaid, ifo->iaid,
				    sizeof(ifo->ia[i].iaid));
				ifo->ia[i].iaid_set = 1;
			}
		}
	}
#endif

	/* If we are not sending an authentication option, don't require it */
	if (!(ifo->auth.options & DHCPCD_AUTH_SEND))
		ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE;
}

int
dhcpcd_selectprofile(struct interface *ifp, const char *profile)
{
	struct if_options *ifo;
	char pssid[PROFILE_LEN];

	if (ifp->ssid_len) {
		ssize_t r;

		r = print_string(pssid, sizeof(pssid), ESCSTRING,
		    ifp->ssid, ifp->ssid_len);
		if (r == -1) {
			logger(ifp->ctx, LOG_ERR,
			    "%s: %s: %m", ifp->name, __func__);
			pssid[0] = '\0';
		}
	} else
		pssid[0] = '\0';
	ifo = read_config(ifp->ctx, ifp->name, pssid, profile);
	if (ifo == NULL) {
		logger(ifp->ctx, LOG_DEBUG, "%s: no profile %s",
		    ifp->name, profile);
		return -1;
	}
	if (profile != NULL) {
		strlcpy(ifp->profile, profile, sizeof(ifp->profile));
		logger(ifp->ctx, LOG_INFO, "%s: selected profile %s",
		    ifp->name, profile);
	} else
		*ifp->profile = '\0';

	free_options(ifp->options);
	ifp->options = ifo;
	if (profile)
		configure_interface1(ifp);
	return 1;
}

static void
configure_interface(struct interface *ifp, int argc, char **argv,
    unsigned long long options)
{
	time_t old;

	old = ifp->options ? ifp->options->mtime : 0;
	dhcpcd_selectprofile(ifp, NULL);
	add_options(ifp->ctx, ifp->name, ifp->options, argc, argv);
	ifp->options->options |= options;
	configure_interface1(ifp);

	/* If the mtime has changed drop any old lease */
	if (ifp->options && old != 0 && ifp->options->mtime != old) {
		logger(ifp->ctx, LOG_WARNING,
		    "%s: confile file changed, expiring leases", ifp->name);
		dhcpcd_drop(ifp, 0);
	}
}

static void
dhcpcd_pollup(void *arg)
{
	struct interface *ifp = arg;
	int carrier;

	carrier = if_carrier(ifp); /* will set ifp->flags */
	if (carrier == LINK_UP && !(ifp->flags & IFF_UP)) {
		struct timespec tv;

		tv.tv_sec = 0;
		tv.tv_nsec = IF_POLL_UP * MSEC_PER_NSEC;
		eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcpcd_pollup, ifp);
		return;
	}

	dhcpcd_handlecarrier(ifp->ctx, carrier, ifp->flags, ifp->name);
}

void
dhcpcd_handlecarrier(struct dhcpcd_ctx *ctx, int carrier, unsigned int flags,
    const char *ifname)
{
	struct interface *ifp;

	ifp = if_find(ctx->ifaces, ifname);
	if (ifp == NULL || !(ifp->options->options & DHCPCD_LINK))
		return;

	switch(carrier) {
	case LINK_UNKNOWN:
		carrier = if_carrier(ifp); /* will set ifp->flags */
		break;
	case LINK_UP:
		/* we have a carrier! Still need to check for IFF_UP */
		if (flags & IFF_UP)
			ifp->flags = flags;
		else {
			/* So we need to poll for IFF_UP as there is no
			 * kernel notification when it's set. */
			dhcpcd_pollup(ifp);
			return;
		}
		break;
	default:
		ifp->flags = flags;
	}

	/* If we here, we don't need to poll for IFF_UP any longer
	 * if generated by a kernel event. */
	eloop_timeout_delete(ifp->ctx->eloop, dhcpcd_pollup, ifp);

	if (carrier == LINK_UNKNOWN) {
		if (errno != ENOTTY) /* For example a PPP link on BSD */
			logger(ctx, LOG_ERR, "%s: carrier_status: %m", ifname);
	} else if (carrier == LINK_DOWN || (ifp->flags & IFF_UP) == 0) {
		if (ifp->carrier != LINK_DOWN) {
			if (ifp->carrier == LINK_UP)
				logger(ctx, LOG_INFO, "%s: carrier lost",
				    ifp->name);
			ifp->carrier = LINK_DOWN;
			script_runreason(ifp, "NOCARRIER");
#ifdef NOCARRIER_PRESERVE_IP
			arp_close(ifp);
			ipv4_buildroutes(ifp->ctx);
			ipv6nd_expire(ifp, 0);
#else
			dhcpcd_drop(ifp, 0);
#endif
		}
	} else if (carrier == LINK_UP && ifp->flags & IFF_UP) {
		if (ifp->carrier != LINK_UP) {
			logger(ctx, LOG_INFO, "%s: carrier acquired",
			    ifp->name);
			ifp->carrier = LINK_UP;
#if !defined(__linux__) && !defined(__NetBSD__)
			/* BSD does not emit RTM_NEWADDR or RTM_CHGADDR when the
			 * hardware address changes so we have to go
			 * through the disovery process to work it out. */
			dhcpcd_handleinterface(ctx, 0, ifp->name);
#endif
			if (ifp->wireless) {
				uint8_t ossid[IF_SSIDSIZE];
#ifdef NOCARRIER_PRESERVE_IP
				size_t olen;

				olen = ifp->ssid_len;
#endif
				memcpy(ossid, ifp->ssid, ifp->ssid_len);
				if_getssid(ifp);
#ifdef NOCARRIER_PRESERVE_IP
				/* If we changed SSID network, drop leases */
				if (ifp->ssid_len != olen ||
				    memcmp(ifp->ssid, ossid, ifp->ssid_len))
					dhcpcd_drop(ifp, 0);
#endif
			}
			dhcpcd_initstate(ifp, 0);
			script_runreason(ifp, "CARRIER");
#ifdef NOCARRIER_PRESERVE_IP
			/* Set any IPv6 Routers we remembered to expire
			 * faster than they would normally as we
			 * maybe on a new network. */
			ipv6nd_expire(ifp, RTR_CARRIER_EXPIRE);
#endif
			/* RFC4941 Section 3.5 */
			if (ifp->options->options & DHCPCD_IPV6RA_OWN)
				ipv6_gentempifid(ifp);
			dhcpcd_startinterface(ifp);
		}
	}
}

static void
warn_iaid_conflict(struct interface *ifp, uint8_t *iaid)
{
	struct interface *ifn;
	size_t i;

	TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
		if (ifn == ifp)
			continue;
		if (ifn->options->options & DHCPCD_PFXDLGONLY)
			continue;
		if (memcmp(ifn->options->iaid, iaid,
		    sizeof(ifn->options->iaid)) == 0)
			break;
		for (i = 0; i < ifn->options->ia_len; i++) {
			if (memcmp(&ifn->options->ia[i].iaid, iaid,
			    sizeof(ifn->options->ia[i].iaid)) == 0)
				break;
		}
	}

	/* This is only a problem if the interfaces are on the same network. */
	if (ifn && strcmp(ifp->name, ifn->name))
		logger(ifp->ctx, LOG_ERR,
		    "%s: IAID conflicts with one assigned to %s",
		    ifp->name, ifn->name);
}

static void
pre_start(struct interface *ifp)
{

	/* Add our link-local address before upping the interface
	 * so our RFC7217 address beats the hwaddr based one.
	 * This is also a safety check incase it was ripped out
	 * from under us. */
	if (ifp->options->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) {
		logger(ifp->ctx, LOG_ERR, "%s: ipv6_start: %m", ifp->name);
		ifp->options->options &= ~DHCPCD_IPV6;
	}
}

void
dhcpcd_startinterface(void *arg)
{
	struct interface *ifp = arg;
	struct if_options *ifo = ifp->options;
	size_t i;
	char buf[DUID_LEN * 3];
	int carrier;
	struct timespec tv;

	if (ifo->options & DHCPCD_LINK) {
		switch (ifp->carrier) {
		case LINK_UP:
			break;
		case LINK_DOWN:
			logger(ifp->ctx, LOG_INFO, "%s: waiting for carrier",
			    ifp->name);
			return;
		case LINK_UNKNOWN:
			/* No media state available.
			 * Loop until both IFF_UP and IFF_RUNNING are set */
			if ((carrier = if_carrier(ifp)) == LINK_UNKNOWN) {
				tv.tv_sec = 0;
				tv.tv_nsec = IF_POLL_UP * MSEC_PER_NSEC;
				eloop_timeout_add_tv(ifp->ctx->eloop,
				    &tv, dhcpcd_startinterface, ifp);
			} else
				dhcpcd_handlecarrier(ifp->ctx, carrier,
				    ifp->flags, ifp->name);
			return;
		}
	}

	if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6)) {
		/* Report client DUID */
		if (ifp->ctx->duid == NULL) {
			if (duid_init(ifp) == 0)
				return;
			if (!(ifo->options & DHCPCD_PFXDLGONLY))
				logger(ifp->ctx, LOG_INFO, "DUID %s",
				    hwaddr_ntoa(ifp->ctx->duid,
				    ifp->ctx->duid_len,
				    buf, sizeof(buf)));
		}
	}

	if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6) &&
	    !(ifo->options & DHCPCD_PFXDLGONLY))
	{
		/* Report IAIDs */
		logger(ifp->ctx, LOG_INFO, "%s: IAID %s", ifp->name,
		    hwaddr_ntoa(ifo->iaid, sizeof(ifo->iaid),
		    buf, sizeof(buf)));
		warn_iaid_conflict(ifp, ifo->iaid);
		for (i = 0; i < ifo->ia_len; i++) {
			if (memcmp(ifo->iaid, ifo->ia[i].iaid,
			    sizeof(ifo->iaid)))
			{
				logger(ifp->ctx, LOG_INFO, "%s: IAID %s",
				    ifp->name, hwaddr_ntoa(ifo->ia[i].iaid,
				    sizeof(ifo->ia[i].iaid),
				    buf, sizeof(buf)));
				warn_iaid_conflict(ifp, ifo->ia[i].iaid);
			}
		}
	}

	if (ifo->options & DHCPCD_IPV6) {
		if (ifo->options & DHCPCD_IPV6RS &&
		    !(ifo->options & DHCPCD_INFORM))
			ipv6nd_startrs(ifp);

		if (ifo->options & DHCPCD_DHCP6)
			dhcp6_find_delegates(ifp);

		if (!(ifo->options & DHCPCD_IPV6RS) ||
		    ifo->options & DHCPCD_IA_FORCED)
		{
			ssize_t nolease;

			if (ifo->options & DHCPCD_IA_FORCED)
				nolease = dhcp6_start(ifp, DH6S_INIT);
			else {
				nolease = 0;
				/* Enabling the below doesn't really make
				 * sense as there is currently no standard
				 * to push routes via DHCPv6.
				 * (There is an expired working draft,
				 * maybe abandoned?)
				 * You can also get it to work by forcing
				 * an IA as shown above. */
#if 0
				/* With no RS or delegates we might
				 * as well try and solicit a DHCPv6 address */
				if (nolease == 0)
					nolease = dhcp6_start(ifp, DH6S_INIT);
#endif
			}
			if (nolease == -1)
			        logger(ifp->ctx, LOG_ERR,
				    "%s: dhcp6_start: %m", ifp->name);
		}
	}

	if (ifo->options & DHCPCD_IPV4)
		dhcp_start(ifp);
}

static void
dhcpcd_prestartinterface(void *arg)
{
	struct interface *ifp = arg;

	pre_start(ifp);
	if (if_up(ifp) == -1)
		logger(ifp->ctx, LOG_ERR, "%s: if_up: %m", ifp->name);

	if (ifp->options->options & DHCPCD_LINK &&
	    ifp->carrier == LINK_UNKNOWN)
	{
		int carrier;

		if ((carrier = if_carrier(ifp)) != LINK_UNKNOWN) {
			dhcpcd_handlecarrier(ifp->ctx, carrier,
			    ifp->flags, ifp->name);
			return;
		}
		logger(ifp->ctx, LOG_INFO,
		    "%s: unknown carrier, waiting for interface flags",
		    ifp->name);
	}

	dhcpcd_startinterface(ifp);
}

static void
handle_link(void *arg)
{
	struct dhcpcd_ctx *ctx;

	ctx = arg;
	if (if_managelink(ctx) == -1) {
		logger(ctx, LOG_ERR, "if_managelink: %m");
		eloop_event_delete(ctx->eloop, ctx->link_fd, 0);
		close(ctx->link_fd);
		ctx->link_fd = -1;
	}
}

static void
dhcpcd_initstate1(struct interface *ifp, int argc, char **argv,
    unsigned long long options)
{
	struct if_options *ifo;

	configure_interface(ifp, argc, argv, options);
	ifo = ifp->options;

	if (ifo->options & DHCPCD_IPV4 && ipv4_init(ifp->ctx) == -1) {
		logger(ifp->ctx, LOG_ERR, "ipv4_init: %m");
		ifo->options &= ~DHCPCD_IPV4;
	}
	if (ifo->options & DHCPCD_IPV6 && ipv6_init(ifp->ctx) == NULL) {
		logger(ifp->ctx, LOG_ERR, "ipv6_init: %m");
		ifo->options &= ~DHCPCD_IPV6RS;
	}

	/* Add our link-local address before upping the interface
	 * so our RFC7217 address beats the hwaddr based one.
	 * This needs to happen before PREINIT incase a hook script
	 * inadvertently ups the interface. */
	if (ifo->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) {
		logger(ifp->ctx, LOG_ERR, "%s: ipv6_start: %m", ifp->name);
		ifo->options &= ~DHCPCD_IPV6;
	}
}

void
dhcpcd_initstate(struct interface *ifp, unsigned long long options)
{

	dhcpcd_initstate1(ifp, ifp->ctx->argc, ifp->ctx->argv, options);
}

static void
run_preinit(struct interface *ifp)
{

	pre_start(ifp);
	if (ifp->ctx->options & DHCPCD_TEST)
		return;

	script_runreason(ifp, "PREINIT");

	if (ifp->options->options & DHCPCD_LINK && ifp->carrier != LINK_UNKNOWN)
		script_runreason(ifp,
		    ifp->carrier == LINK_UP ? "CARRIER" : "NOCARRIER");
}

int
dhcpcd_handleinterface(void *arg, int action, const char *ifname)
{
	struct dhcpcd_ctx *ctx;
	struct if_head *ifs;
	struct interface *ifp, *iff, *ifn;
	const char * const argv[] = { ifname };
	int i;

	ctx = arg;
	if (action == -1) {
		ifp = if_find(ctx->ifaces, ifname);
		if (ifp == NULL) {
			errno = ESRCH;
			return -1;
		}
		logger(ctx, LOG_DEBUG, "%s: interface departed", ifp->name);
		ifp->options->options |= DHCPCD_DEPARTED;
		stop_interface(ifp);
		return 0;
	}

	/* If running off an interface list, check it's in it. */
	if (ctx->ifc && action != 2) {
		for (i = 0; i < ctx->ifc; i++)
			if (strcmp(ctx->ifv[i], ifname) == 0)
				break;
		if (i >= ctx->ifc)
			return 0;
	}

	i = -1;
	ifs = if_discover(ctx, -1, UNCONST(argv));
	if (ifs == NULL) {
		logger(ctx, LOG_ERR, "%s: if_discover: %m", __func__);
		return -1;
	}
	TAILQ_FOREACH_SAFE(ifp, ifs, next, ifn) {
		if (strcmp(ifp->name, ifname) != 0)
			continue;
		i = 0;
		/* Check if we already have the interface */
		iff = if_find(ctx->ifaces, ifp->name);
		if (iff) {
			logger(ctx, LOG_DEBUG, "%s: interface updated", iff->name);
			/* The flags and hwaddr could have changed */
			iff->flags = ifp->flags;
			iff->hwlen = ifp->hwlen;
			if (ifp->hwlen != 0)
				memcpy(iff->hwaddr, ifp->hwaddr, iff->hwlen);
		} else {
			logger(ctx, LOG_DEBUG, "%s: interface added", ifp->name);
			TAILQ_REMOVE(ifs, ifp, next);
			TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next);
			dhcpcd_initstate(ifp, 0);
			run_preinit(ifp);
			iff = ifp;
		}
		if (action > 0)
			dhcpcd_prestartinterface(iff);
	}

	/* Free our discovered list */
	while ((ifp = TAILQ_FIRST(ifs))) {
		TAILQ_REMOVE(ifs, ifp, next);
		if_free(ifp);
	}
	free(ifs);

	if (i == -1)
		errno = ENOENT;
	return i;
}

void
dhcpcd_handlehwaddr(struct dhcpcd_ctx *ctx, const char *ifname,
    const uint8_t *hwaddr, uint8_t hwlen)
{
	struct interface *ifp;
	char buf[sizeof(ifp->hwaddr) * 3];

	ifp = if_find(ctx->ifaces, ifname);
	if (ifp == NULL)
		return;

	if (hwlen > sizeof(ifp->hwaddr)) {
		errno = ENOBUFS;
		logger(ctx, LOG_ERR, "%s: %s: %m", ifp->name, __func__);
		return;
	}

	if (ifp->hwlen == hwlen && memcmp(ifp->hwaddr, hwaddr, hwlen) == 0)
		return;

	logger(ctx, LOG_INFO, "%s: new hardware address: %s", ifp->name,
	    hwaddr_ntoa(hwaddr, hwlen, buf, sizeof(buf)));
	ifp->hwlen = hwlen;
	memcpy(ifp->hwaddr, hwaddr, hwlen);
}

void
dhcpcd_start_interface(struct dhcpcd_ctx *ctx, const char *ifname)
{
	struct interface *ifp;
	ifp = if_find(ctx->ifaces, ifname);
	if (ifp == NULL)
	{
		logger(ctx, LOG_ERR, "start_interface: %s not found",
		       ifname);
		return;
	}
	dhcpcd_startinterface(ifp);
}

void
dhcpcd_stop_interface(struct dhcpcd_ctx *ctx, const char *ifname)
{
	struct interface *ifp;
	ifp = if_find(ctx->ifaces, ifname);
	if (ifp == NULL)
	{
		logger(ctx, LOG_ERR, "stop_interface: %s not found",
		       ifname);
		return;
	}
	stop_interface(ifp);
}

void
dhcpcd_stop_interfaces(struct dhcpcd_ctx *ctx)
{
	struct interface *ifp;
	TAILQ_FOREACH(ifp, ctx->ifaces, next) {
		stop_interface(ifp);
	}
}

void
dhcpcd_release_ipv4(struct dhcpcd_ctx *ctx, const char *ifname)
{
	struct interface *ifp;

	ifp = if_find(ctx->ifaces, ifname);
	if (ifp == NULL)
	{
		logger(ctx, LOG_ERR, "IPv4 release: %s not found",
		       ifname);
		return;
	}
	dhcp_drop(ifp, "RELEASE");
}

static void
if_reboot(struct interface *ifp, int argc, char **argv)
{
	unsigned long long oldopts;

	oldopts = ifp->options->options;
	script_runreason(ifp, "RECONFIGURE");
	dhcpcd_initstate1(ifp, argc, argv, 0);
	dhcp_reboot_newopts(ifp, oldopts);
	dhcp6_reboot(ifp);
	dhcpcd_prestartinterface(ifp);
}

static void
reload_config(struct dhcpcd_ctx *ctx)
{
	struct if_options *ifo;

	free_globals(ctx);
	ifo = read_config(ctx, NULL, NULL, NULL);
	add_options(ctx, NULL, ifo, ctx->argc, ctx->argv);
	/* We need to preserve these two options. */
	if (ctx->options & DHCPCD_MASTER)
		ifo->options |= DHCPCD_MASTER;
	if (ctx->options & DHCPCD_DAEMONISED)
		ifo->options |= DHCPCD_DAEMONISED;
	ctx->options = ifo->options;
	free_options(ifo);
}

static void
reconf_reboot(struct dhcpcd_ctx *ctx, int action, int argc, char **argv, int oi)
{
	struct if_head *ifs;
	struct interface *ifn, *ifp;

	ifs = if_discover(ctx, argc - oi, argv + oi);
	if (ifs == NULL) {
		logger(ctx, LOG_ERR, "%s: if_discover: %m", __func__);
		return;
	}

	while ((ifp = TAILQ_FIRST(ifs))) {
		TAILQ_REMOVE(ifs, ifp, next);
		ifn = if_find(ctx->ifaces, ifp->name);
		if (ifn) {
			if (action)
				if_reboot(ifn, argc, argv);
			else
				ipv4_applyaddr(ifn);
			if_free(ifp);
		} else {
			TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next);
			dhcpcd_initstate1(ifp, argc, argv, 0);
			run_preinit(ifp);
			dhcpcd_prestartinterface(ifp);
		}
	}
	free(ifs);
}

static void
stop_all_interfaces(struct dhcpcd_ctx *ctx, int do_release)
{
	struct interface *ifp;

	/* drop_dhcp could change the order, so we do it like this. */
	for (;;) {
		/* Be sane and drop the last config first,
		 * skipping any pseudo interfaces */
		TAILQ_FOREACH_REVERSE(ifp, ctx->ifaces, if_head, next) {
			if (!(ifp->options->options & DHCPCD_PFXDLGONLY))
				break;
		}
		if (ifp == NULL)
			break;
		if (do_release) {
			ifp->options->options |= DHCPCD_RELEASE;
			ifp->options->options &= ~DHCPCD_PERSISTENT;
		}
		ifp->options->options |= DHCPCD_EXITING;
		stop_interface(ifp);
	}
}

#ifdef USE_SIGNALS
struct dhcpcd_siginfo dhcpcd_siginfo;
#define sigmsg "received %s, %s"
void
dhcpcd_handle_signal(void *arg)
{
	struct dhcpcd_ctx *ctx;
	struct dhcpcd_siginfo *si;
	struct interface *ifp;
	int do_release, exit_code;;

	ctx = dhcpcd_ctx;
	si = arg;
	do_release = 0;
	exit_code = EXIT_FAILURE;
	switch (si->signo) {
	case SIGINT:
		logger(ctx, LOG_INFO, sigmsg, "SIGINT", "stopping");
		break;
	case SIGTERM:
		logger(ctx, LOG_INFO, sigmsg, "SIGTERM", "stopping");
		exit_code = EXIT_SUCCESS;
		break;
	case SIGALRM:
		logger(ctx, LOG_INFO, sigmsg, "SIGALRM", "releasing");
		do_release = 1;
		exit_code = EXIT_SUCCESS;
		break;
	case SIGHUP:
		logger(ctx, LOG_INFO, sigmsg, "SIGHUP", "rebinding");
		reload_config(ctx);
		/* Preserve any options passed on the commandline
		 * when we were started. */
		reconf_reboot(ctx, 1, ctx->argc, ctx->argv,
		    ctx->argc - ctx->ifc);
		return;
	case SIGUSR1:
		logger(ctx, LOG_INFO, sigmsg, "SIGUSR1", "reconfiguring");
		TAILQ_FOREACH(ifp, ctx->ifaces, next) {
			ipv4_applyaddr(ifp);
		}
		return;
	case SIGUSR2:
		logger_close(ctx);
		logger_open(ctx);
		logger(ctx, LOG_INFO, sigmsg, "SIGUSR2", "reopened logfile");
		return;
	case SIGPIPE:
		logger(ctx, LOG_WARNING, "received SIGPIPE");
		return;
	default:
		logger(ctx, LOG_ERR,
		    "received signal %d, "
		    "but don't know what to do with it",
		    si->signo);
		return;
	}

	if (!(ctx->options & DHCPCD_TEST))
		stop_all_interfaces(ctx, do_release);
	eloop_exit(ctx->eloop, exit_code);
}

#ifndef HAVE_KQUEUE
static void
handle_signal(int sig, __unused siginfo_t *siginfo, __unused void *context)
{

	/* So that we can operate safely under a signal we instruct
	 * eloop to pass a copy of the siginfo structure to handle_signal1
	 * as the very first thing to do. */
	dhcpcd_siginfo.signo = sig;
	eloop_timeout_add_now(dhcpcd_ctx->eloop,
	    dhcpcd_handle_signal, &dhcpcd_siginfo);
}
#endif

static int
signal_init(sigset_t *oldset)
{
	sigset_t newset;
#ifndef HAVE_KQUEUE
	int i;
	struct sigaction sa;
#endif

	sigfillset(&newset);
	if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1)
		return -1;

#ifndef HAVE_KQUEUE
	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = handle_signal;
	sa.sa_flags = SA_SIGINFO;
	sigemptyset(&sa.sa_mask);

	for (i = 0; dhcpcd_handlesigs[i]; i++) {
		if (sigaction(dhcpcd_handlesigs[i], &sa, NULL) == -1)
			return -1;
	}
#endif
	return 0;
}
#endif

static void
dhcpcd_getinterfaces(void *arg)
{
	struct fd_list *fd = arg;
	struct interface *ifp;
	size_t len;

	len = 0;
	TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) {
		len++;
		if (D_STATE_RUNNING(ifp))
			len++;
		if (RS_STATE_RUNNING(ifp))
			len++;
		if (D6_STATE_RUNNING(ifp))
			len++;
	}
	if (write(fd->fd, &len, sizeof(len)) != sizeof(len))
		return;
	eloop_event_delete(fd->ctx->eloop, fd->fd, 1);
	TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) {
		if (send_interface(fd, ifp) == -1)
			logger(ifp->ctx, LOG_ERR,
			    "send_interface %d: %m", fd->fd);
	}
}

int
dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd,
    int argc, char **argv)
{
	struct interface *ifp;
	int do_exit = 0, do_release = 0, do_reboot = 0;
	int opt, oi = 0;
	size_t len, l;
	char *tmp, *p;

	/* Special commands for our control socket
	 * as the other end should be blocking until it gets the
	 * expected reply we should be safely able just to change the
	 * write callback on the fd */
	if (strcmp(*argv, "--version") == 0) {
		return control_queue(fd, UNCONST(VERSION),
			strlen(VERSION) + 1, 0);
	} else if (strcmp(*argv, "--getconfigfile") == 0) {
		return control_queue(fd, UNCONST(fd->ctx->cffile),
			strlen(fd->ctx->cffile) + 1, 0);
	} else if (strcmp(*argv, "--getinterfaces") == 0) {
		eloop_event_add(fd->ctx->eloop, fd->fd, NULL, NULL,
			dhcpcd_getinterfaces, fd);
		return 0;
	} else if (strcmp(*argv, "--listen") == 0) {
		fd->flags |= FD_LISTEN;
		return 0;
	}

	/* Only priviledged users can control dhcpcd via the socket. */
	if (fd->flags & FD_UNPRIV) {
		errno = EPERM;
		return -1;
	}

	/* Log the command */
	len = 1;
	for (opt = 0; opt < argc; opt++)
		len += strlen(argv[opt]) + 1;
	tmp = malloc(len);
	if (tmp == NULL)
		return -1;
	p = tmp;
	for (opt = 0; opt < argc; opt++) {
		l = strlen(argv[opt]);
		strlcpy(p, argv[opt], len);
		len -= l + 1;
		p += l;
		*p++ = ' ';
	}
	*--p = '\0';
	logger(ctx, LOG_INFO, "control command: %s", tmp);
	free(tmp);

	optind = 0;
	while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
	{
		switch (opt) {
		case 'g':
			/* Assumed if below not set */
			break;
		case 'k':
			do_release = 1;
			break;
		case 'n':
			do_reboot = 1;
			break;
		case 'x':
			do_exit = 1;
			break;
		}
	}

	if (do_release || do_exit) {
		if (optind == argc) {
			stop_all_interfaces(ctx, do_release);
			eloop_exit(ctx->eloop, EXIT_SUCCESS);
			return 0;
		}
		for (oi = optind; oi < argc; oi++) {
			if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL)
				continue;
			if (do_release) {
				ifp->options->options |= DHCPCD_RELEASE;
				ifp->options->options &= ~DHCPCD_PERSISTENT;
			}
			ifp->options->options |= DHCPCD_EXITING;
			stop_interface(ifp);
		}
		return 0;
	}

	reload_config(ctx);
	/* XXX: Respect initial commandline options? */
	reconf_reboot(ctx, do_reboot, argc, argv, optind);
	return 0;
}

#if defined(__ANDROID__)
static void
switch_user(void)
{
	gid_t groups[] = { AID_DBUS, AID_INET, AID_SHELL };
	struct __user_cap_header_struct header;
	struct __user_cap_data_struct cap;

	setgroups(sizeof(groups)/sizeof(groups[0]), groups);

	prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);

	setgid(AID_DHCP);
	setuid(AID_DHCP);
	header.version = _LINUX_CAPABILITY_VERSION;
	header.pid = 0;
	cap.effective = cap.permitted =
		(1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) |
		(1 << CAP_NET_BROADCAST) | (1 << CAP_NET_BIND_SERVICE);
	cap.inheritable = 0;
	capset(&header, &cap);
}
#endif  /* __ANDROID__ */

int
main(int argc, char **argv)
{
	struct dhcpcd_ctx ctx;
	struct if_options *ifo;
	struct interface *ifp;
	uint16_t family = 0;
	int opt, oi = 0, i;
	time_t t;
	ssize_t len;
#if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK)
	pid_t pid;
#endif
#ifdef USE_SIGNALS
	int sig;
	const char *siga;
#endif
	char ifn[IF_NAMESIZE];

#if defined(__ANDROID__) && !defined(__BRILLO__)
	switch_user();
#endif  /* __ANDROID__ && !__BRILLO__ */

	/* Test for --help and --version */
	if (argc > 1) {
		if (strcmp(argv[1], "--help") == 0) {
			usage();
			return EXIT_SUCCESS;
		} else if (strcmp(argv[1], "--version") == 0) {
			printf(""PACKAGE" "VERSION"\n%s\n", dhcpcd_copyright);
			return EXIT_SUCCESS;
		}
	}

	memset(&ctx, 0, sizeof(ctx));
#ifdef USE_SIGNALS
	dhcpcd_ctx = &ctx;
	sig = 0;
	siga = NULL;
#endif
	closefrom(3);

	ctx.log_fd = -1;
	logger_open(&ctx);
	logger_mask(&ctx, LOG_UPTO(LOG_INFO));

	ifo = NULL;
	ctx.cffile = CONFIG;
	ctx.pid_fd = ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1;
	TAILQ_INIT(&ctx.control_fds);
#ifdef PLUGIN_DEV
	ctx.dev_fd = -1;
#endif
#ifdef INET
	ctx.udp_fd = -1;
#endif
	i = 0;
	while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
	{
		switch (opt) {
		case '4':
			family = AF_INET;
			break;
		case '6':
			family = AF_INET6;
			break;
		case 'f':
			ctx.cffile = optarg;
			break;
#ifdef USE_SIGNALS
		case 'g':
			sig = SIGUSR1;
			siga = "USR1";
			break;
		case 'j':
			ctx.logfile = strdup(optarg);
			logger_close(&ctx);
			logger_open(&ctx);
			break;
		case 'k':
			sig = SIGALRM;
			siga = "ARLM";
			break;
		case 'n':
			sig = SIGHUP;
			siga = "HUP";
			break;
		case 'x':
			sig = SIGTERM;
			siga = "TERM";;
			break;
#endif
		case 'T':
			i = 1;
			break;
		case 'U':
			if (i == 3)
				i = 4;
			else if (i != 4)
				i = 3;
			break;
		case 'V':
			i = 2;
			break;
		case '?':
			usage();
			goto exit_failure;
		}
	}

	ctx.argv = argv;
	ctx.argc = argc;
	ctx.ifc = argc - optind;
	ctx.ifv = argv + optind;

	ifo = read_config(&ctx, NULL, NULL, NULL);
	if (ifo == NULL)
		goto exit_failure;
	opt = add_options(&ctx, NULL, ifo, argc, argv);
	if (opt != 1) {
		if (opt == 0)
			usage();
		goto exit_failure;
	}
	if (i == 2) {
		printf("Interface options:\n");
		if (optind == argc - 1) {
			free_options(ifo);
			ifo = read_config(&ctx, argv[optind], NULL, NULL);
			if (ifo == NULL)
				goto exit_failure;
			add_options(&ctx, NULL, ifo, argc, argv);
		}
		if_printoptions();
#ifdef INET
		if (family == 0 || family == AF_INET) {
			printf("\nDHCPv4 options:\n");
			dhcp_printoptions(&ctx,
			    ifo->dhcp_override, ifo->dhcp_override_len);
		}
#endif
#ifdef INET6
		if (family == 0 || family == AF_INET6) {
			printf("\nDHCPv6 options:\n");
			dhcp6_printoptions(&ctx,
			    ifo->dhcp6_override, ifo->dhcp6_override_len);
		}
#endif
		goto exit_success;
	}
	ctx.options = ifo->options;
	if (i != 0) {
		if (i == 1)
			ctx.options |= DHCPCD_TEST;
		else
			ctx.options |= DHCPCD_DUMPLEASE;
		if (i == 4)
			ctx.options |= DHCPCD_PFXDLGONLY;
		ctx.options |= DHCPCD_PERSISTENT;
		ctx.options &= ~DHCPCD_DAEMONISE;
	}

#ifdef THERE_IS_NO_FORK
	ctx.options &= ~DHCPCD_DAEMONISE;
#endif

	if (ctx.options & DHCPCD_DEBUG)
		logger_mask(&ctx, LOG_UPTO(LOG_DEBUG));
	if (ctx.options & DHCPCD_QUIET) {
		i = open(_PATH_DEVNULL, O_RDWR);
		if (i == -1)
			logger(&ctx, LOG_ERR, "%s: open: %m", __func__);
		else {
			dup2(i, STDERR_FILENO);
			close(i);
		}
	}

	if (!(ctx.options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) {
		/* If we have any other args, we should run as a single dhcpcd
		 *  instance for that interface. */
		if (optind == argc - 1 && !(ctx.options & DHCPCD_MASTER)) {
			const char *per;
			int intf_len = strlen(argv[optind]);
			split_interface_lease(argv[optind], &intf_len, NULL);
			if (intf_len > IF_NAMESIZE) {
				logger(&ctx, LOG_ERR,
				    "%s: interface name too long",
				    argv[optind]);
				goto exit_failure;
			}
			strlcpy(ifn, argv[optind], intf_len + 1);
			/* Allow a dhcpcd interface per address family */
			switch(family) {
			case AF_INET:
				per = "-4";
				break;
			case AF_INET6:
				per = "-6";
				break;
			default:
				per = "";
			}
			snprintf(ctx.pidfile, sizeof(ctx.pidfile),
			    PIDFILE, "-", ifn, per);
		} else {
			snprintf(ctx.pidfile, sizeof(ctx.pidfile),
			    PIDFILE, "", "", "");
			ctx.options |= DHCPCD_MASTER;
		}
	}

	if (chdir("/") == -1)
		logger(&ctx, LOG_ERR, "chdir `/': %m");

	/* Freeing allocated addresses from dumping leases can trigger
	 * eloop removals as well, so init here. */
	ctx.eloop = eloop_init(&ctx);
	if (ctx.eloop == NULL) {
		logger(&ctx, LOG_ERR, "%s: eloop_init: %m", __func__);
		goto exit_failure;
	}

	if (ctx.options & DHCPCD_DUMPLEASE) {
		if (optind != argc - 1) {
			logger(&ctx, LOG_ERR,
			    "dumplease requires an interface");
			goto exit_failure;
		}
		i = 0;
		/* We need to try and find the interface so we can
		 * load the hardware address to compare automated IAID */
		ctx.ifaces = if_discover(&ctx, 1, argv + optind);
		if (ctx.ifaces == NULL) {
			logger(&ctx, LOG_ERR, "if_discover: %m");
			goto exit_failure;
		}
		ifp = TAILQ_FIRST(ctx.ifaces);
		if (ifp == NULL) {
			ifp = calloc(1, sizeof(*ifp));
			if (ifp == NULL) {
				logger(&ctx, LOG_ERR, "%s: %m", __func__);
				goto exit_failure;
			}
			strlcpy(ctx.pidfile, argv[optind], sizeof(ctx.pidfile));
			ifp->ctx = &ctx;
			TAILQ_INSERT_HEAD(ctx.ifaces, ifp, next);
			if (family == 0) {
				if (ctx.pidfile[strlen(ctx.pidfile) - 1] == '6')
					family = AF_INET6;
				else
					family = AF_INET;
			}
		}
		configure_interface(ifp, ctx.argc, ctx.argv, 0);
		if (ctx.options & DHCPCD_PFXDLGONLY)
			ifp->options->options |= DHCPCD_PFXDLGONLY;
		if (family == 0 || family == AF_INET) {
			if (dhcp_dump(ifp) == -1)
				i = 1;
		}
		if (family == 0 || family == AF_INET6) {
			if (dhcp6_dump(ifp) == -1)
				i = 1;
		}
		if (i == -1)
			goto exit_failure;
		goto exit_success;
	}

#ifdef USE_SIGNALS
	if (!(ctx.options & DHCPCD_TEST) &&
	    (sig == 0 || ctx.ifc != 0))
	{
#endif
		if (ctx.options & DHCPCD_MASTER)
			i = -1;
		else
			i = control_open(&ctx, argv[optind]);
		if (i == -1)
			i = control_open(&ctx, NULL);
		if (i != -1) {
			logger(&ctx, LOG_INFO,
			    "sending commands to master dhcpcd process");
			len = control_send(&ctx, argc, argv);
			control_close(&ctx);
			if (len > 0) {
				logger(&ctx, LOG_DEBUG, "send OK");
				goto exit_success;
			} else {
				logger(&ctx, LOG_ERR,
				    "failed to send commands");
				goto exit_failure;
			}
		} else {
			if (errno != ENOENT)
				logger(&ctx, LOG_ERR, "control_open: %m");
		}
#ifdef USE_SIGNALS
	}
#endif

	if (geteuid())
		logger(&ctx, LOG_NOTICE,
		    PACKAGE " is running with reduced privileges");

#ifdef USE_SIGNALS
	if (sig != 0) {
		pid = read_pid(ctx.pidfile);
		if (pid != 0)
			logger(&ctx, LOG_INFO, "sending signal %s to pid %d",
			    siga, pid);
		if (pid == 0 || kill(pid, sig) != 0) {
			if (sig != SIGHUP && errno != EPERM)
				logger(&ctx, LOG_ERR, ""PACKAGE" not running");
			if (pid != 0 && errno != ESRCH) {
				logger(&ctx, LOG_ERR, "kill: %m");
				goto exit_failure;
			}
			unlink(ctx.pidfile);
			if (sig != SIGHUP)
				goto exit_failure;
		} else {
			struct timespec ts;

			if (sig == SIGHUP || sig == SIGUSR1)
				goto exit_success;
			/* Spin until it exits */
			logger(&ctx, LOG_INFO,
			    "waiting for pid %d to exit", pid);
			ts.tv_sec = 0;
			ts.tv_nsec = 100000000; /* 10th of a second */
			for(i = 0; i < 100; i++) {
				nanosleep(&ts, NULL);
				if (read_pid(ctx.pidfile) == 0)
					goto exit_success;
			}
			logger(&ctx, LOG_ERR, "pid %d failed to exit", pid);
			goto exit_failure;
		}
	}

	if (!(ctx.options & DHCPCD_TEST)) {
		if ((pid = read_pid(ctx.pidfile)) > 0 &&
		    kill(pid, 0) == 0)
		{
			logger(&ctx, LOG_ERR, ""PACKAGE
			    " already running on pid %d (%s)",
			    pid, ctx.pidfile);
			goto exit_failure;
		}

#if !defined(__ANDROID__)
		/* Ensure we have the needed directories
		 * On Android, we assume that these directories have been created
		 * by calls to mkdir in an init.rc file. */
		if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST)
			logger(&ctx, LOG_ERR, "mkdir `%s': %m", RUNDIR);
		if (mkdir(DBDIR, 0755) == -1 && errno != EEXIST)
			logger(&ctx, LOG_ERR, "mkdir `%s': %m", DBDIR);
#endif /* __ANDROID__ */

		opt = O_WRONLY | O_CREAT | O_NONBLOCK;
#ifdef O_CLOEXEC
		opt |= O_CLOEXEC;
#endif
		ctx.pid_fd = open(ctx.pidfile, opt, 0664);
		if (ctx.pid_fd == -1)
			logger(&ctx, LOG_ERR, "open `%s': %m", ctx.pidfile);
		else {
#ifdef LOCK_EX
			/* Lock the file so that only one instance of dhcpcd
			 * runs on an interface */
			if (flock(ctx.pid_fd, LOCK_EX | LOCK_NB) == -1) {
				logger(&ctx, LOG_ERR, "flock `%s': %m", ctx.pidfile);
				close(ctx.pid_fd);
				ctx.pid_fd = -1;
				goto exit_failure;
			}
#endif
#ifndef O_CLOEXEC
			if (fcntl(ctx.pid_fd, F_GETFD, &opt) == -1 ||
			    fcntl(ctx.pid_fd, F_SETFD, opt | FD_CLOEXEC) == -1)
			{
				logger(&ctx, LOG_ERR, "fcntl: %m");
				close(ctx.pid_fd);
				ctx.pid_fd = -1;
				goto exit_failure;
			}
#endif
			write_pid(ctx.pid_fd, getpid());
		}
	}

	if (ctx.options & DHCPCD_MASTER) {
		if (control_start(&ctx, NULL) == -1)
			logger(&ctx, LOG_ERR, "control_start: %m");
	}
#else
	if (control_start(&ctx,
	    ctx.options & DHCPCD_MASTER ? NULL : argv[optind]) == -1)
	{
		logger(&ctx, LOG_ERR, "control_start: %m");
		goto exit_failure;
	}
#endif

	logger(&ctx, LOG_DEBUG, PACKAGE "-" VERSION " starting");
	ctx.options |= DHCPCD_STARTED;
#ifdef USE_SIGNALS
	/* Save signal mask, block and redirect signals to our handler */
	if (signal_init(&ctx.sigset) == -1) {
		logger(&ctx, LOG_ERR, "signal_setup: %m");
		goto exit_failure;
	}
#endif

	/* When running dhcpcd against a single interface, we need to retain
	 * the old behaviour of waiting for an IP address */
	if (ctx.ifc == 1 && !(ctx.options & DHCPCD_BACKGROUND))
		ctx.options |= DHCPCD_WAITIP;

	/* RTM_NEWADDR goes through the link socket as well which we
	 * need for IPv6 DAD, so we check for DHCPCD_LINK in
	 * dhcpcd_handlecarrier instead.
	 * We also need to open this before checking for interfaces below
	 * so that we pickup any new addresses during the discover phase. */
	ctx.link_fd = if_openlinksocket();
	if (ctx.link_fd == -1)
		logger(&ctx, LOG_ERR, "open_link_socket: %m");
	else
		eloop_event_add(ctx.eloop, ctx.link_fd,
		    handle_link, &ctx, NULL, NULL);

	/* Start any dev listening plugin which may want to
	 * change the interface name provided by the kernel */
	if ((ctx.options & (DHCPCD_MASTER | DHCPCD_DEV)) ==
	    (DHCPCD_MASTER | DHCPCD_DEV))
		dev_start(&ctx);

	if (rpc_init(&ctx) == -1) {
		/* NB: rpc_init generates a syslog msg */
		exit(EXIT_FAILURE);
	}
	rpc_signal_status("Init");

	ctx.ifaces = if_discover(&ctx, ctx.ifc, ctx.ifv);
	if (ctx.ifaces == NULL) {
		logger(&ctx, LOG_ERR, "if_discover: %m");
		goto exit_failure;
	}
	for (i = 0; i < ctx.ifc; i++) {
		int intf_len = strlen(ctx.ifv[i]);
		split_interface_lease(ctx.ifv[i], &intf_len, NULL);
		if (intf_len > IF_NAMESIZE) {
			logger(&ctx, LOG_ERR,
			    "%s: interface name too long",
			    ctx.ifv[i]);
			continue;
		}
		strlcpy(ifn, ctx.ifv[i], intf_len + 1);
		if (if_find(ctx.ifaces, ifn) == NULL)
			logger(&ctx, LOG_ERR,
			    "%s: interface not found or invalid",
			    ifn);
	}
	if (TAILQ_FIRST(ctx.ifaces) == NULL) {
		if (ctx.ifc == 0)
			logger(&ctx, LOG_ERR, "no valid interfaces found");
		else
			goto exit_failure;
		if (!(ctx.options & DHCPCD_LINK)) {
			logger(&ctx, LOG_ERR,
			    "aborting as link detection is disabled");
			goto exit_failure;
		}
	}

	TAILQ_FOREACH(ifp, ctx.ifaces, next) {
		dhcpcd_initstate1(ifp, argc, argv, 0);
	}

	if (ctx.options & DHCPCD_BACKGROUND && dhcpcd_daemonise(&ctx))
		goto exit_success;

	opt = 0;
	TAILQ_FOREACH(ifp, ctx.ifaces, next) {
		run_preinit(ifp);
		if (ifp->carrier != LINK_DOWN)
			opt = 1;
	}

	if (!(ctx.options & DHCPCD_BACKGROUND)) {
		if (ctx.options & DHCPCD_MASTER)
			t = ifo->timeout;
		else if ((ifp = TAILQ_FIRST(ctx.ifaces)))
			t = ifp->options->timeout;
		else
			t = 0;
		if (opt == 0 &&
		    ctx.options & DHCPCD_LINK &&
		    !(ctx.options & DHCPCD_WAITIP))
		{
			logger(&ctx, LOG_WARNING,
			    "no interfaces have a carrier");
			if (dhcpcd_daemonise(&ctx))
				goto exit_success;
		} else if (t > 0 &&
		    /* Test mode removes the daemonise bit, so check for both */
		    ctx.options & (DHCPCD_DAEMONISE | DHCPCD_TEST))
		{
			eloop_timeout_add_sec(ctx.eloop, t,
			    handle_exit_timeout, &ctx);
		}
	}
	free_options(ifo);
	ifo = NULL;

	if_sortinterfaces(&ctx);
	TAILQ_FOREACH(ifp, ctx.ifaces, next) {
		eloop_timeout_add_sec(ctx.eloop, 0,
		    dhcpcd_prestartinterface, ifp);
	}

	i = eloop_start(ctx.eloop);
	goto exit1;

exit_success:
	i = EXIT_SUCCESS;
	goto exit1;

exit_failure:
	i = EXIT_FAILURE;

exit1:
	/* Free memory and close fd's */
	if (ctx.ifaces) {
		while ((ifp = TAILQ_FIRST(ctx.ifaces))) {
			TAILQ_REMOVE(ctx.ifaces, ifp, next);
			if_free(ifp);
		}
		free(ctx.ifaces);
	}
	free(ctx.duid);
	if (ctx.link_fd != -1) {
		eloop_event_delete(ctx.eloop, ctx.link_fd, 0);
		close(ctx.link_fd);
	}

	free_options(ifo);
	free_globals(&ctx);
	ipv4_ctxfree(&ctx);
	ipv6_ctxfree(&ctx);
	dev_stop(&ctx);
	if (control_stop(&ctx) == -1)
		logger(&ctx, LOG_ERR, "control_stop: %m:");
	if (ctx.pid_fd != -1) {
		close(ctx.pid_fd);
		unlink(ctx.pidfile);
	}
	eloop_free(ctx.eloop);

	if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED))
		logger(&ctx, LOG_INFO, PACKAGE " exited");
	logger_close(&ctx);
	free(ctx.logfile);
	return i;
}