[BACK]Return to engine.c CVS log [TXT][DIR] Up to [local] / src / sbin / dhcpleased

File: [local] / src / sbin / dhcpleased / engine.c (download)

Revision 1.43, Tue Feb 13 12:53:05 2024 UTC (3 months, 2 weeks ago) by florian
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5
Changes since 1.42: +6 -4 lines

Only generate a new xid at the start of getting a new lease.

"RFC 2131 4.1 Constructing and sending DHCP messages" has this:

| Selecting a new 'xid' for each retransmission is an implementation
| decision.  A client may choose to reuse the same 'xid' or select a new
| 'xid' for each retransmitted message.

We used to change xid for each request / response cycle but this ran
into problems with slow dhcp servers where we would change the xid too
frequently and would ignore late coming replies from the server.

Andre S points out that table 5 in "4.4.1 Initialization and
allocation of network address" says for the xid field in "DHCPREQUEST"
messages:

| 'xid' from server DHCPOFFER message

This seems to suggest that we need to use the same xid for the whole
DHCPDISCOVER / DHCPOFFER / DHCPREQUEST / DHCPACK exchange of messages.

Nothing else in the RFC is saying this though.

But since there are DHCP servers out there that depend on this, we
only generate a new xid when entering the INIT, REBOOTING and RENEWING
state.

I do wonder if we should just go with a static value of 0x04, which
was chosen by a fair dice roll, so guaranteed to be random.

Issue reported, initial diff and fix tested by Andre S
deraadt likes this version
OK tb

/*	$OpenBSD: engine.c,v 1.43 2024/02/13 12:53:05 florian Exp $	*/

/*
 * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org>
 * Copyright (c) 2004, 2005 Claudio Jeker <claudio@openbsd.org>
 * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
 * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/uio.h>
#include <sys/mbuf.h>

#include <net/if.h>
#include <net/route.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/udp.h>

#include <errno.h>
#include <event.h>
#include <imsg.h>
#include <pwd.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <vis.h>

#include "checksum.h"
#include "log.h"
#include "dhcpleased.h"
#include "engine.h"

/*
 * RFC 2131 4.1 p23 has "SHOULD be 4 seconds", we are a bit more aggressive,
 * networks are faster these days.
 */
#define	START_EXP_BACKOFF	 1
#define	MAX_EXP_BACKOFF_SLOW	64 /* RFC 2131 4.1 p23 */
#define	MAX_EXP_BACKOFF_FAST	 2
#define	MINIMUM(a, b)		(((a) < (b)) ? (a) : (b))

enum if_state {
	IF_DOWN,
	IF_INIT,
	/* IF_SELECTING, */
	IF_REQUESTING,
	IF_BOUND,
	IF_RENEWING,
	IF_REBINDING,
	/* IF_INIT_REBOOT, */
	IF_REBOOTING,
	IF_IPV6_ONLY,
};

const char* if_state_name[] = {
	"Down",
	"Init",
	/* "Selecting", */
	"Requesting",
	"Bound",
	"Renewing",
	"Rebinding",
	/* "Init-Reboot", */
	"Rebooting",
	"IPv6 only",
};

struct dhcpleased_iface {
	LIST_ENTRY(dhcpleased_iface)	 entries;
	enum if_state			 state;
	struct event			 timer;
	struct timeval			 timo;
	uint32_t			 if_index;
	int				 rdomain;
	int				 running;
	struct ether_addr		 hw_address;
	int				 link_state;
	uint32_t			 cur_mtu;
	uint32_t			 xid;
	struct timespec			 request_time;
	struct in_addr			 server_identifier;
	struct in_addr			 dhcp_server; /* for unicast */
	struct in_addr			 requested_ip;
	struct in_addr			 mask;
	struct in_addr			 siaddr;
	char				 file[4 * DHCP_FILE_LEN + 1];
	char				 hostname[4 * 255 + 1];
	char				 domainname[4 * 255 + 1];
	struct dhcp_route		 prev_routes[MAX_DHCP_ROUTES];
	int				 prev_routes_len;
	struct dhcp_route		 routes[MAX_DHCP_ROUTES];
	int				 routes_len;
	struct in_addr			 nameservers[MAX_RDNS_COUNT];
	uint32_t			 lease_time;
	uint32_t			 renewal_time;
	uint32_t			 rebinding_time;
	uint32_t			 ipv6_only_time;
};

LIST_HEAD(, dhcpleased_iface) dhcpleased_interfaces;

__dead void		 engine_shutdown(void);
void			 engine_sig_handler(int sig, short, void *);
void			 engine_dispatch_frontend(int, short, void *);
void			 engine_dispatch_main(int, short, void *);
#ifndef	SMALL
void			 send_interface_info(struct dhcpleased_iface *, pid_t);
void			 engine_showinfo_ctl(struct imsg *, uint32_t);
#endif	/* SMALL */
void			 engine_update_iface(struct imsg_ifinfo *);
struct dhcpleased_iface	*get_dhcpleased_iface_by_id(uint32_t);
void			 remove_dhcpleased_iface(uint32_t);
void			 parse_dhcp(struct dhcpleased_iface *,
			     struct imsg_dhcp *);
void			 state_transition(struct dhcpleased_iface *, enum
			     if_state);
void			 iface_timeout(int, short, void *);
void			 request_dhcp_discover(struct dhcpleased_iface *);
void			 request_dhcp_request(struct dhcpleased_iface *);
void			 log_lease(struct dhcpleased_iface *, int);
void			 log_rdns(struct dhcpleased_iface *, int);
void			 send_configure_interface(struct dhcpleased_iface *);
void			 send_rdns_proposal(struct dhcpleased_iface *);
void			 send_deconfigure_interface(struct dhcpleased_iface *);
void			 send_rdns_withdraw(struct dhcpleased_iface *);
void			 send_routes_withdraw(struct dhcpleased_iface *);
void			 parse_lease(struct dhcpleased_iface *,
			     struct imsg_ifinfo *);
int			 engine_imsg_compose_main(int, pid_t, void *, uint16_t);
void			 log_dhcp_hdr(struct dhcp_hdr *);
const char		*dhcp_message_type2str(uint8_t);

#ifndef SMALL
struct dhcpleased_conf	*engine_conf;
#endif /* SMALL */

static struct imsgev	*iev_frontend;
static struct imsgev	*iev_main;
int64_t			 proposal_id;

void
engine_sig_handler(int sig, short event, void *arg)
{
	/*
	 * Normal signal handler rules don't apply because libevent
	 * decouples for us.
	 */

	switch (sig) {
	case SIGINT:
	case SIGTERM:
		engine_shutdown();
	default:
		fatalx("unexpected signal");
	}
}

void
engine(int debug, int verbose)
{
	struct event		 ev_sigint, ev_sigterm;
	struct passwd		*pw;

#ifndef SMALL
	engine_conf = config_new_empty();
#endif /* SMALL */

	log_init(debug, LOG_DAEMON);
	log_setverbose(verbose);

	if ((pw = getpwnam(DHCPLEASED_USER)) == NULL)
		fatal("getpwnam");

	if (chdir("/") == -1)
		fatal("chdir(\"/\")");

	if (unveil("/", "") == -1)
		fatal("unveil /");
	if (unveil(NULL, NULL) == -1)
		fatal("unveil");

	setproctitle("%s", "engine");
	log_procinit("engine");

	if (setgroups(1, &pw->pw_gid) ||
	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
		fatal("can't drop privileges");

	if (pledge("stdio recvfd", NULL) == -1)
		fatal("pledge");

	event_init();

	/* Setup signal handler(s). */
	signal_set(&ev_sigint, SIGINT, engine_sig_handler, NULL);
	signal_set(&ev_sigterm, SIGTERM, engine_sig_handler, NULL);
	signal_add(&ev_sigint, NULL);
	signal_add(&ev_sigterm, NULL);
	signal(SIGPIPE, SIG_IGN);
	signal(SIGHUP, SIG_IGN);

	/* Setup pipe and event handler to the main process. */
	if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
		fatal(NULL);

	imsg_init(&iev_main->ibuf, 3);
	iev_main->handler = engine_dispatch_main;

	/* Setup event handlers. */
	iev_main->events = EV_READ;
	event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
	    iev_main->handler, iev_main);
	event_add(&iev_main->ev, NULL);

	LIST_INIT(&dhcpleased_interfaces);

	event_dispatch();

	engine_shutdown();
}

__dead void
engine_shutdown(void)
{
	/* Close pipes. */
	msgbuf_clear(&iev_frontend->ibuf.w);
	close(iev_frontend->ibuf.fd);
	msgbuf_clear(&iev_main->ibuf.w);
	close(iev_main->ibuf.fd);

	free(iev_frontend);
	free(iev_main);

	log_info("engine exiting");
	exit(0);
}

int
engine_imsg_compose_frontend(int type, pid_t pid, void *data,
    uint16_t datalen)
{
	return (imsg_compose_event(iev_frontend, type, 0, pid, -1,
	    data, datalen));
}

int
engine_imsg_compose_main(int type, pid_t pid, void *data,
    uint16_t datalen)
{
	return (imsg_compose_event(iev_main, type, 0, pid, -1,
	    data, datalen));
}

void
engine_dispatch_frontend(int fd, short event, void *bula)
{
	struct imsgev			*iev = bula;
	struct imsgbuf			*ibuf = &iev->ibuf;
	struct imsg			 imsg;
	struct dhcpleased_iface		*iface;
	ssize_t				 n;
	int				 shut = 0;
#ifndef	SMALL
	int				 verbose;
#endif	/* SMALL */
	uint32_t			 if_index;

	if (event & EV_READ) {
		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
			fatal("imsg_read error");
		if (n == 0)	/* Connection closed. */
			shut = 1;
	}
	if (event & EV_WRITE) {
		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
			fatal("msgbuf_write");
		if (n == 0)	/* Connection closed. */
			shut = 1;
	}

	for (;;) {
		if ((n = imsg_get(ibuf, &imsg)) == -1)
			fatal("%s: imsg_get error", __func__);
		if (n == 0)	/* No more messages. */
			break;

		switch (imsg.hdr.type) {
#ifndef	SMALL
		case IMSG_CTL_LOG_VERBOSE:
			if (IMSG_DATA_SIZE(imsg) != sizeof(verbose))
				fatalx("%s: IMSG_CTL_LOG_VERBOSE wrong length: "
				    "%lu", __func__, IMSG_DATA_SIZE(imsg));
			memcpy(&verbose, imsg.data, sizeof(verbose));
			log_setverbose(verbose);
			break;
		case IMSG_CTL_SHOW_INTERFACE_INFO:
			if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
				fatalx("%s: IMSG_CTL_SHOW_INTERFACE_INFO wrong "
				    "length: %lu", __func__,
				    IMSG_DATA_SIZE(imsg));
			memcpy(&if_index, imsg.data, sizeof(if_index));
			engine_showinfo_ctl(&imsg, if_index);
			break;
		case IMSG_REQUEST_REBOOT:
			if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
				fatalx("%s: IMSG_CTL_SEND_DISCOVER wrong "
				    "length: %lu", __func__,
				    IMSG_DATA_SIZE(imsg));
			memcpy(&if_index, imsg.data, sizeof(if_index));
			iface = get_dhcpleased_iface_by_id(if_index);
			if (iface != NULL) {
				switch (iface->state) {
				case IF_DOWN:
					break;
				case IF_INIT:
				case IF_REQUESTING:
					state_transition(iface, iface->state);
					break;
				case IF_RENEWING:
				case IF_REBINDING:
				case IF_REBOOTING:
				case IF_BOUND:
				case IF_IPV6_ONLY:
					state_transition(iface, IF_REBOOTING);
					break;
				}
			}
			break;
#endif	/* SMALL */
		case IMSG_REMOVE_IF:
			if (IMSG_DATA_SIZE(imsg) != sizeof(if_index))
				fatalx("%s: IMSG_REMOVE_IF wrong length: %lu",
				    __func__, IMSG_DATA_SIZE(imsg));
			memcpy(&if_index, imsg.data, sizeof(if_index));
			remove_dhcpleased_iface(if_index);
			break;
		case IMSG_DHCP: {
			struct imsg_dhcp	imsg_dhcp;
			if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_dhcp))
				fatalx("%s: IMSG_DHCP wrong length: %lu",
				    __func__, IMSG_DATA_SIZE(imsg));
			memcpy(&imsg_dhcp, imsg.data, sizeof(imsg_dhcp));
			iface = get_dhcpleased_iface_by_id(imsg_dhcp.if_index);
			if (iface != NULL)
				parse_dhcp(iface, &imsg_dhcp);
			break;
		}
		case IMSG_REPROPOSE_RDNS:
			LIST_FOREACH (iface, &dhcpleased_interfaces, entries)
				send_rdns_proposal(iface);
			break;
		default:
			log_debug("%s: unexpected imsg %d", __func__,
			    imsg.hdr.type);
			break;
		}
		imsg_free(&imsg);
	}
	if (!shut)
		imsg_event_add(iev);
	else {
		/* This pipe is dead. Remove its event handler. */
		event_del(&iev->ev);
		event_loopexit(NULL);
	}
}

void
engine_dispatch_main(int fd, short event, void *bula)
{
#ifndef SMALL
	static struct dhcpleased_conf	*nconf;
	static struct iface_conf	*iface_conf;
#endif /* SMALL */
	struct imsg			 imsg;
	struct imsgev			*iev = bula;
	struct imsgbuf			*ibuf = &iev->ibuf;
	struct imsg_ifinfo		 imsg_ifinfo;
	ssize_t				 n;
	int				 shut = 0;

	if (event & EV_READ) {
		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
			fatal("imsg_read error");
		if (n == 0)	/* Connection closed. */
			shut = 1;
	}
	if (event & EV_WRITE) {
		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
			fatal("msgbuf_write");
		if (n == 0)	/* Connection closed. */
			shut = 1;
	}

	for (;;) {
		if ((n = imsg_get(ibuf, &imsg)) == -1)
			fatal("%s: imsg_get error", __func__);
		if (n == 0)	/* No more messages. */
			break;

		switch (imsg.hdr.type) {
		case IMSG_SOCKET_IPC:
			/*
			 * Setup pipe and event handler to the frontend
			 * process.
			 */
			if (iev_frontend)
				fatalx("%s: received unexpected imsg fd "
				    "to engine", __func__);

			if ((fd = imsg_get_fd(&imsg)) == -1)
				fatalx("%s: expected to receive imsg fd to "
				   "engine but didn't receive any", __func__);

			iev_frontend = malloc(sizeof(struct imsgev));
			if (iev_frontend == NULL)
				fatal(NULL);

			imsg_init(&iev_frontend->ibuf, fd);
			iev_frontend->handler = engine_dispatch_frontend;
			iev_frontend->events = EV_READ;

			event_set(&iev_frontend->ev, iev_frontend->ibuf.fd,
			iev_frontend->events, iev_frontend->handler,
			    iev_frontend);
			event_add(&iev_frontend->ev, NULL);

			if (pledge("stdio", NULL) == -1)
				fatal("pledge");

			break;
		case IMSG_UPDATE_IF:
			if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_ifinfo))
				fatalx("%s: IMSG_UPDATE_IF wrong length: %lu",
				    __func__, IMSG_DATA_SIZE(imsg));
			memcpy(&imsg_ifinfo, imsg.data, sizeof(imsg_ifinfo));
			if (imsg_ifinfo.lease[LEASE_SIZE - 1] != '\0')
				fatalx("Invalid lease");
			engine_update_iface(&imsg_ifinfo);
			break;
#ifndef SMALL
		case IMSG_RECONF_CONF:
			if (nconf != NULL)
				fatalx("%s: IMSG_RECONF_CONF already in "
				    "progress", __func__);
			if ((nconf = malloc(sizeof(struct dhcpleased_conf))) ==
			    NULL)
				fatal(NULL);
			SIMPLEQ_INIT(&nconf->iface_list);
			break;
		case IMSG_RECONF_IFACE:
			if (IMSG_DATA_SIZE(imsg) != sizeof(struct
			    iface_conf))
				fatalx("%s: IMSG_RECONF_IFACE wrong length: "
				    "%lu", __func__, IMSG_DATA_SIZE(imsg));
			if ((iface_conf = malloc(sizeof(struct iface_conf)))
			    == NULL)
				fatal(NULL);
			memcpy(iface_conf, imsg.data, sizeof(struct
			    iface_conf));
			iface_conf->vc_id = NULL;
			iface_conf->vc_id_len = 0;
			iface_conf->c_id = NULL;
			iface_conf->c_id_len = 0;
			iface_conf->h_name = NULL;
			SIMPLEQ_INSERT_TAIL(&nconf->iface_list,
			    iface_conf, entry);
			break;
		case IMSG_RECONF_VC_ID:
			if (iface_conf == NULL)
				fatal("IMSG_RECONF_VC_ID without "
				    "IMSG_RECONF_IFACE");
			if (IMSG_DATA_SIZE(imsg) > 255 + 2)
				fatalx("%s: IMSG_RECONF_VC_ID wrong length: "
				    "%lu", __func__, IMSG_DATA_SIZE(imsg));
			if ((iface_conf->vc_id = malloc(IMSG_DATA_SIZE(imsg)))
			    == NULL)
				fatal(NULL);
			memcpy(iface_conf->vc_id, imsg.data,
			    IMSG_DATA_SIZE(imsg));
			iface_conf->vc_id_len = IMSG_DATA_SIZE(imsg);
			break;
		case IMSG_RECONF_C_ID:
			if (iface_conf == NULL)
				fatal("IMSG_RECONF_C_ID without "
				    "IMSG_RECONF_IFACE");
			if (IMSG_DATA_SIZE(imsg) > 255 + 2)
				fatalx("%s: IMSG_RECONF_C_ID wrong length: "
				    "%lu", __func__, IMSG_DATA_SIZE(imsg));
			if ((iface_conf->c_id = malloc(IMSG_DATA_SIZE(imsg)))
			    == NULL)
				fatal(NULL);
			memcpy(iface_conf->c_id, imsg.data,
			    IMSG_DATA_SIZE(imsg));
			iface_conf->c_id_len = IMSG_DATA_SIZE(imsg);
			break;
		case IMSG_RECONF_H_NAME:
			if (iface_conf == NULL)
				fatal("IMSG_RECONF_H_NAME without "
				    "IMSG_RECONF_IFACE");
			if (((char *)imsg.data)[IMSG_DATA_SIZE(imsg) - 1] !=
			    '\0')
				fatalx("Invalid hostname");
			if (IMSG_DATA_SIZE(imsg) > 256)
				fatalx("Invalid hostname");
			if ((iface_conf->h_name = strdup(imsg.data)) == NULL)
				fatal(NULL);
			break;
		case IMSG_RECONF_END: {
			struct dhcpleased_iface	*iface;
			int			*ifaces;
			int			 i, if_index;
			char			*if_name;
			char			 ifnamebuf[IF_NAMESIZE];

			if (nconf == NULL)
				fatalx("%s: IMSG_RECONF_END without "
				    "IMSG_RECONF_CONF", __func__);
			ifaces = changed_ifaces(engine_conf, nconf);
			merge_config(engine_conf, nconf);
			nconf = NULL;
			for (i = 0; ifaces[i] != 0; i++) {
				if_index = ifaces[i];
				if_name = if_indextoname(if_index, ifnamebuf);
				iface = get_dhcpleased_iface_by_id(if_index);
				if (if_name == NULL || iface == NULL)
					continue;
				iface_conf = find_iface_conf(
				    &engine_conf->iface_list, if_name);
				if (iface_conf == NULL)
					continue;
				if (iface_conf->ignore & IGN_DNS)
					send_rdns_withdraw(iface);
				if (iface_conf->ignore & IGN_ROUTES)
					send_routes_withdraw(iface);
			}
			free(ifaces);
			break;
		}
#endif /* SMALL */
		default:
			log_debug("%s: unexpected imsg %d", __func__,
			    imsg.hdr.type);
			break;
		}
		imsg_free(&imsg);
	}
	if (!shut)
		imsg_event_add(iev);
	else {
		/* This pipe is dead. Remove its event handler. */
		event_del(&iev->ev);
		event_loopexit(NULL);
	}
}

#ifndef	SMALL
void
send_interface_info(struct dhcpleased_iface *iface, pid_t pid)
{
	struct ctl_engine_info	 cei;

	memset(&cei, 0, sizeof(cei));
	cei.if_index = iface->if_index;
	cei.running = iface->running;
	cei.link_state = iface->link_state;
	strlcpy(cei.state, if_state_name[iface->state], sizeof(cei.state));
	memcpy(&cei.request_time, &iface->request_time,
	    sizeof(cei.request_time));
	cei.server_identifier = iface->server_identifier;
	cei.dhcp_server = iface->dhcp_server;
	cei.requested_ip = iface->requested_ip;
	cei.mask = iface->mask;
	cei.routes_len = iface->routes_len;
	memcpy(cei.routes, iface->routes, sizeof(cei.routes));
	memcpy(cei.nameservers, iface->nameservers, sizeof(cei.nameservers));
	cei.lease_time = iface->lease_time;
	cei.renewal_time = iface->renewal_time;
	cei.rebinding_time = iface->rebinding_time;
	engine_imsg_compose_frontend(IMSG_CTL_SHOW_INTERFACE_INFO, pid, &cei,
	    sizeof(cei));
}

void
engine_showinfo_ctl(struct imsg *imsg, uint32_t if_index)
{
	struct dhcpleased_iface			*iface;

	switch (imsg->hdr.type) {
	case IMSG_CTL_SHOW_INTERFACE_INFO:
		if ((iface = get_dhcpleased_iface_by_id(if_index)) != NULL)
			send_interface_info(iface, imsg->hdr.pid);
		else
			engine_imsg_compose_frontend(IMSG_CTL_END,
			    imsg->hdr.pid, NULL, 0);
		break;
	default:
		log_debug("%s: error handling imsg", __func__);
		break;
	}
}
#endif	/* SMALL */

void
engine_update_iface(struct imsg_ifinfo *imsg_ifinfo)
{
	struct dhcpleased_iface	*iface;
	int			 need_refresh = 0;

	iface = get_dhcpleased_iface_by_id(imsg_ifinfo->if_index);

	if (iface == NULL) {
		if ((iface = calloc(1, sizeof(*iface))) == NULL)
			fatal("calloc");
		iface->state = IF_DOWN;
		iface->xid = arc4random();
		iface->timo.tv_usec = arc4random_uniform(1000000);
		evtimer_set(&iface->timer, iface_timeout, iface);
		iface->if_index = imsg_ifinfo->if_index;
		iface->rdomain = imsg_ifinfo->rdomain;
		iface->running = imsg_ifinfo->running;
		iface->link_state = imsg_ifinfo->link_state;
		iface->requested_ip.s_addr = INADDR_ANY;
		memcpy(&iface->hw_address, &imsg_ifinfo->hw_address,
		    sizeof(struct ether_addr));
		LIST_INSERT_HEAD(&dhcpleased_interfaces, iface, entries);
		need_refresh = 1;
	} else {
		if (memcmp(&iface->hw_address, &imsg_ifinfo->hw_address,
		    sizeof(struct ether_addr)) != 0) {
			memcpy(&iface->hw_address, &imsg_ifinfo->hw_address,
			    sizeof(struct ether_addr));
			need_refresh = 1;
		}
		if (imsg_ifinfo->rdomain != iface->rdomain) {
			iface->rdomain = imsg_ifinfo->rdomain;
			need_refresh = 1;
		}
		if (imsg_ifinfo->running != iface->running) {
			iface->running = imsg_ifinfo->running;
			need_refresh = 1;
		}

		if (imsg_ifinfo->link_state != iface->link_state) {
			iface->link_state = imsg_ifinfo->link_state;
			need_refresh = 1;
		}
	}

	if (!need_refresh)
		return;

	if (iface->running && LINK_STATE_IS_UP(iface->link_state)) {
		if (iface->requested_ip.s_addr == INADDR_ANY)
			parse_lease(iface, imsg_ifinfo);

		if (iface->requested_ip.s_addr == INADDR_ANY)
			state_transition(iface, IF_INIT);
		else
			state_transition(iface, IF_REBOOTING);
	} else
		state_transition(iface, IF_DOWN);
}
struct dhcpleased_iface*
get_dhcpleased_iface_by_id(uint32_t if_index)
{
	struct dhcpleased_iface	*iface;
	LIST_FOREACH (iface, &dhcpleased_interfaces, entries) {
		if (iface->if_index == if_index)
			return (iface);
	}

	return (NULL);
}

void
remove_dhcpleased_iface(uint32_t if_index)
{
	struct dhcpleased_iface	*iface;

	iface = get_dhcpleased_iface_by_id(if_index);

	if (iface == NULL)
		return;

	send_rdns_withdraw(iface);
	send_deconfigure_interface(iface);
	LIST_REMOVE(iface, entries);
	evtimer_del(&iface->timer);
	free(iface);
}

void
parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp)
{
	static uint8_t		 cookie[] = DHCP_COOKIE;
	static struct ether_addr bcast_mac;
#ifndef SMALL
	struct iface_conf	*iface_conf;
#endif /* SMALL */
	struct ether_header	*eh;
	struct ether_addr	 ether_src, ether_dst;
	struct ip		*ip;
	struct udphdr		*udp;
	struct dhcp_hdr		*dhcp_hdr;
	struct in_addr		 server_identifier, subnet_mask;
	struct in_addr		 nameservers[MAX_RDNS_COUNT];
	struct dhcp_route	 routes[MAX_DHCP_ROUTES];
	size_t			 rem, i;
	uint32_t		 sum, usum, lease_time = 0, renewal_time = 0;
	uint32_t		 rebinding_time = 0;
	uint32_t		 ipv6_only_time = 0;
	uint8_t			*p, dho = DHO_PAD, dho_len, slen;
	uint8_t			 dhcp_message_type = 0;
	int			 routes_len = 0, routers = 0, csr = 0;
	char			 from[sizeof("xx:xx:xx:xx:xx:xx")];
	char			 to[sizeof("xx:xx:xx:xx:xx:xx")];
	char			 hbuf_src[INET_ADDRSTRLEN];
	char			 hbuf_dst[INET_ADDRSTRLEN];
	char			 hbuf[INET_ADDRSTRLEN];
	char			 domainname[4 * 255 + 1];
	char			 hostname[4 * 255 + 1];
	char			 ifnamebuf[IF_NAMESIZE], *if_name;

	if (bcast_mac.ether_addr_octet[0] == 0)
		memset(bcast_mac.ether_addr_octet, 0xff, ETHER_ADDR_LEN);

	if_name = if_indextoname(iface->if_index, ifnamebuf);

#ifndef SMALL
	iface_conf = find_iface_conf(&engine_conf->iface_list, if_name);
#endif /* SMALL*/

	memset(hbuf_src, 0, sizeof(hbuf_src));
	memset(hbuf_dst, 0, sizeof(hbuf_dst));

	p = dhcp->packet;
	rem = dhcp->len;

	if (rem < sizeof(*eh)) {
		log_warnx("%s: message too short", __func__);
		return;
	}
	eh = (struct ether_header *)p;
	memcpy(ether_src.ether_addr_octet, eh->ether_shost,
	    sizeof(ether_src.ether_addr_octet));
	strlcpy(from, ether_ntoa(&ether_src), sizeof(from));
	memcpy(ether_dst.ether_addr_octet, eh->ether_dhost,
	    sizeof(ether_dst.ether_addr_octet));
	strlcpy(to, ether_ntoa(&ether_dst), sizeof(to));
	p += sizeof(*eh);
	rem -= sizeof(*eh);

	if (memcmp(&ether_dst, &iface->hw_address, sizeof(ether_dst)) != 0 &&
	    memcmp(&ether_dst, &bcast_mac, sizeof(ether_dst)) != 0)
		return ; /* silently ignore packet not for us */

	if (rem < sizeof(*ip))
		goto too_short;

	if (log_getverbose() > 1)
		log_debug("%s, from: %s, to: %s", __func__, from, to);

	ip = (struct ip *)p;

	if (rem < (size_t)ip->ip_hl << 2)
		goto too_short;

	if ((dhcp->csumflags & M_IPV4_CSUM_IN_OK) == 0 &&
	    wrapsum(checksum((uint8_t *)ip, ip->ip_hl << 2, 0)) != 0) {
		log_warnx("%s: bad IP checksum", __func__);
		return;
	}
	if (rem < ntohs(ip->ip_len))
		goto too_short;

	p += ip->ip_hl << 2;
	rem -= ip->ip_hl << 2;

	if (inet_ntop(AF_INET, &ip->ip_src, hbuf_src, sizeof(hbuf_src)) == NULL)
		hbuf_src[0] = '\0';
	if (inet_ntop(AF_INET, &ip->ip_dst, hbuf_dst, sizeof(hbuf_dst)) == NULL)
		hbuf_dst[0] = '\0';

#ifndef SMALL
	if (iface_conf != NULL) {
		for (i = 0; (int)i < iface_conf->ignore_servers_len; i++) {
			if (iface_conf->ignore_servers[i].s_addr ==
			    ip->ip_src.s_addr) {
				log_debug("ignoring server %s", hbuf_src);
				return;
			}
		}
	}
#endif /* SMALL */

	if (rem < sizeof(*udp))
		goto too_short;

	udp = (struct udphdr *)p;
	if (rem < ntohs(udp->uh_ulen))
		goto too_short;

	if (rem > ntohs(udp->uh_ulen)) {
		if (log_getverbose() > 1) {
			log_debug("%s: accepting packet with %lu bytes of data"
			    " after udp payload", __func__, rem -
			    ntohs(udp->uh_ulen));
		}
		rem = ntohs(udp->uh_ulen);
	}

	p += sizeof(*udp);
	rem -= sizeof(*udp);

	if ((dhcp->csumflags & M_UDP_CSUM_IN_OK) == 0) {
		usum = udp->uh_sum;
		udp->uh_sum = 0;

		sum = wrapsum(checksum((uint8_t *)udp, sizeof(*udp),
		    checksum(p, rem,
		    checksum((uint8_t *)&ip->ip_src, 2 * sizeof(ip->ip_src),
		    IPPROTO_UDP + ntohs(udp->uh_ulen)))));

		if (usum != 0 && usum != sum) {
			log_warnx("%s: bad UDP checksum", __func__);
			return;
		}
	}

	if (log_getverbose() > 1) {
		log_debug("%s: %s:%d -> %s:%d", __func__, hbuf_src,
		    ntohs(udp->uh_sport), hbuf_dst, ntohs(udp->uh_dport));
	}

	if (rem < sizeof(*dhcp_hdr))
		goto too_short;

	dhcp_hdr = (struct dhcp_hdr *)p;
	p += sizeof(*dhcp_hdr);
	rem -= sizeof(*dhcp_hdr);

	dhcp_hdr->sname[DHCP_SNAME_LEN -1 ] = '\0'; /* ensure it's a string */
	dhcp_hdr->file[DHCP_FILE_LEN -1 ] = '\0'; /* ensure it's a string */

	if (log_getverbose() > 1)
		log_dhcp_hdr(dhcp_hdr);

	if (dhcp_hdr->op != DHCP_BOOTREPLY) {
		log_warnx("%s: ignoring non-reply packet", __func__);
		return;
	}

	if (ntohl(dhcp_hdr->xid) != iface->xid)
		return; /* silently ignore wrong xid */

	if (rem < sizeof(cookie))
		goto too_short;

	if (memcmp(p, cookie, sizeof(cookie)) != 0) {
		log_warnx("%s: no dhcp cookie in packet from %s", __func__,
		    from);
		return;
	}
	p += sizeof(cookie);
	rem -= sizeof(cookie);

	memset(&server_identifier, 0, sizeof(server_identifier));
	memset(&subnet_mask, 0, sizeof(subnet_mask));
	memset(&routes, 0, sizeof(routes));
	memset(&nameservers, 0, sizeof(nameservers));
	memset(hostname, 0, sizeof(hostname));
	memset(domainname, 0, sizeof(domainname));

	while (rem > 0 && dho != DHO_END) {
		dho = *p;
		p += 1;
		rem -= 1;
		/* only DHO_END and DHO_PAD are 1 byte long without length */
		if (dho == DHO_PAD || dho == DHO_END)
			dho_len = 0;
		else {
			if (rem == 0)
				goto too_short; /* missing option length */
			dho_len = *p;
			p += 1;
			rem -= 1;
			if (rem < dho_len)
				goto too_short;
		}

		switch (dho) {
		case DHO_PAD:
			if (log_getverbose() > 1)
				log_debug("DHO_PAD");
			break;
		case DHO_END:
			if (log_getverbose() > 1)
				log_debug("DHO_END");
			break;
		case DHO_DHCP_MESSAGE_TYPE:
			if (dho_len != 1)
				goto wrong_length;
			dhcp_message_type = *p;
			if (log_getverbose() > 1) {
				log_debug("DHO_DHCP_MESSAGE_TYPE: %s",
				    dhcp_message_type2str(dhcp_message_type));
			}
			p += dho_len;
			rem -= dho_len;
			break;
		case DHO_DHCP_SERVER_IDENTIFIER:
			if (dho_len != sizeof(server_identifier))
				goto wrong_length;
			memcpy(&server_identifier, p,
			    sizeof(server_identifier));
			if (log_getverbose() > 1) {
				log_debug("DHO_DHCP_SERVER_IDENTIFIER: %s",
				    inet_ntop(AF_INET, &server_identifier,
				    hbuf, sizeof(hbuf)));
			}
			p += dho_len;
			rem -= dho_len;
			break;
		case DHO_DHCP_LEASE_TIME:
			if (dho_len != sizeof(lease_time))
				goto wrong_length;
			memcpy(&lease_time, p, sizeof(lease_time));
			lease_time = ntohl(lease_time);
			if (log_getverbose() > 1) {
				log_debug("DHO_DHCP_LEASE_TIME %us",
				    lease_time);
			}
			p += dho_len;
			rem -= dho_len;
			break;
		case DHO_SUBNET_MASK:
			if (dho_len != sizeof(subnet_mask))
				goto wrong_length;
			memcpy(&subnet_mask, p, sizeof(subnet_mask));
			if (log_getverbose() > 1) {
				log_debug("DHO_SUBNET_MASK: %s",
				    inet_ntop(AF_INET, &subnet_mask, hbuf,
				    sizeof(hbuf)));
			}
			p += dho_len;
			rem -= dho_len;
			break;
		case DHO_ROUTERS:
			if (dho_len < sizeof(routes[routes_len].gw))
				goto wrong_length;
			if (dho_len % sizeof(routes[routes_len].gw) != 0)
				goto wrong_length;

			/*
			 * Ignore routers option if classless static routes
			 * are present (RFC3442).
			 */
			if (!csr) {
				routers = 1;
				while (routes_len < MAX_DHCP_ROUTES &&
				    dho_len > 0) {
					memcpy(&routes[routes_len].gw, p,
					    sizeof(routes[routes_len].gw));
					if (log_getverbose() > 1) {
						log_debug("DHO_ROUTER: %s",
						    inet_ntop(AF_INET,
						    &routes[routes_len].gw,
						    hbuf, sizeof(hbuf)));
					}
					p += sizeof(routes[routes_len].gw);
					rem -= sizeof(routes[routes_len].gw);
					dho_len -=
					    sizeof(routes[routes_len].gw);
					routes_len++;
				}
			}
			if (dho_len != 0) {
				/* ignore > MAX_DHCP_ROUTES routes */
				p += dho_len;
				rem -= dho_len;
			}
			break;
		case DHO_DOMAIN_NAME_SERVERS:
			if (dho_len < sizeof(nameservers[0]))
				goto wrong_length;
			if (dho_len % sizeof(nameservers[0]) != 0)
				goto wrong_length;
			/* we limit ourself to 8 nameservers for proposals */
			memcpy(&nameservers, p, MINIMUM(sizeof(nameservers),
			    dho_len));
			if (log_getverbose() > 1) {
				for (i = 0; i < MINIMUM(sizeof(nameservers),
				    dho_len / sizeof(nameservers[0])); i++) {
					log_debug("DHO_DOMAIN_NAME_SERVERS: %s "
					    "(%lu/%lu)", inet_ntop(AF_INET,
					    &nameservers[i], hbuf,
					    sizeof(hbuf)), i + 1,
					    dho_len / sizeof(nameservers[0]));
				}
			}
			p += dho_len;
			rem -= dho_len;
			break;
		case DHO_HOST_NAME:
			if (dho_len < 1) {
				/*
				 * Protocol violation: minimum length is 1;
				 * pretend the option is not there
				 */
				break;
			}
			/* MUST delete trailing NUL, per RFC 2132 */
			slen = dho_len;
			while (slen > 0 && p[slen - 1] == '\0')
				slen--;
			/* slen might be 0 here, pretend option is not there. */
			strvisx(hostname, p, slen, VIS_SAFE);
			if (log_getverbose() > 1)
				log_debug("DHO_HOST_NAME: %s", hostname);
			p += dho_len;
			rem -= dho_len;
			break;
		case DHO_DOMAIN_NAME:
			if (dho_len < 1) {
				/*
				 * Protocol violation: minimum length is 1;
				 * pretend the option is not there
				 */
				break;
			}
			/* MUST delete trailing NUL, per RFC 2132 */
			slen = dho_len;
			while (slen > 0 && p[slen - 1] == '\0')
				slen--;
			/* slen might be 0 here, pretend option is not there. */
			strvisx(domainname, p, slen, VIS_SAFE);
			if (log_getverbose() > 1)
				log_debug("DHO_DOMAIN_NAME: %s", domainname);
			p += dho_len;
			rem -= dho_len;
			break;
		case DHO_DHCP_RENEWAL_TIME:
			if (dho_len != sizeof(renewal_time))
				goto wrong_length;
			memcpy(&renewal_time, p, sizeof(renewal_time));
			renewal_time = ntohl(renewal_time);
			if (log_getverbose() > 1) {
				log_debug("DHO_DHCP_RENEWAL_TIME %us",
				    renewal_time);
			}
			p += dho_len;
			rem -= dho_len;
			break;
		case DHO_DHCP_REBINDING_TIME:
			if (dho_len != sizeof(rebinding_time))
				goto wrong_length;
			memcpy(&rebinding_time, p, sizeof(rebinding_time));
			rebinding_time = ntohl(rebinding_time);
			if (log_getverbose() > 1) {
				log_debug("DHO_DHCP_REBINDING_TIME %us",
				    rebinding_time);
			}
			p += dho_len;
			rem -= dho_len;
			break;
		case DHO_DHCP_CLIENT_IDENTIFIER:
			/* the server is supposed to echo this back to us */
#ifndef SMALL
			if (iface_conf != NULL && iface_conf->c_id_len > 0) {
				if (dho_len != iface_conf->c_id[1]) {
					log_warnx("wrong "
					    "DHO_DHCP_CLIENT_IDENTIFIER");
					return;
				}
				if (memcmp(p, &iface_conf->c_id[2], dho_len) !=
				    0) {
					log_warnx("wrong "
					    "DHO_DHCP_CLIENT_IDENTIFIER");
					return;
				}
			} else
#endif /* SMALL */
			{
				if (dho_len != 1 + sizeof(iface->hw_address))
					goto wrong_length;
				if (*p != HTYPE_ETHER) {
					log_warnx("DHO_DHCP_CLIENT_IDENTIFIER: "
					    "wrong type");
					return;
				}
				if (memcmp(p + 1, &iface->hw_address,
				    sizeof(iface->hw_address)) != 0) {
					log_warnx("wrong "
					    "DHO_DHCP_CLIENT_IDENTIFIER");
					return;
				}
			}
			p += dho_len;
			rem -= dho_len;
			break;
		case DHO_CLASSLESS_STATIC_ROUTES: {
			int	prefixlen, compressed_prefixlen;

			csr = 1;
			if (routers) {
				/*
				 * Ignore routers option if classless static
				 * routes are present (RFC3442).
				 */
				routers = 0;
				routes_len = 0;
			}
			while (routes_len < MAX_DHCP_ROUTES && dho_len > 0) {
				prefixlen = *p;
				p += 1;
				rem -= 1;
				dho_len -= 1;

				if (prefixlen < 0 || prefixlen > 32) {
					log_warnx("%s: invalid prefixlen: %d",
					    __func__, prefixlen);
					return;
				}

				if (prefixlen > 0)
					routes[routes_len].mask.s_addr =
					    htonl(0xffffffff << (32 -
						prefixlen));
				else
					routes[routes_len].mask.s_addr =
					    INADDR_ANY;

				compressed_prefixlen = (prefixlen + 7) / 8;
				if (dho_len < compressed_prefixlen)
					goto wrong_length;

				memcpy(&routes[routes_len].dst, p,
				    compressed_prefixlen);
				p += compressed_prefixlen;
				rem -= compressed_prefixlen;
				dho_len -= compressed_prefixlen;

				if (dho_len < sizeof(routes[routes_len].gw))
					goto wrong_length;

				memcpy(&routes[routes_len].gw, p,
				    sizeof(routes[routes_len].gw));
				p += sizeof(routes[routes_len].gw);
				rem -= sizeof(routes[routes_len].gw);
				dho_len -= sizeof(routes[routes_len].gw);

				routes_len++;
			}

			if (dho_len != 0) {
				/* ignore > MAX_DHCP_ROUTES routes */
				p += dho_len;
				rem -= dho_len;
			}
			break;
		}
		case DHO_IPV6_ONLY_PREFERRED:
			if (dho_len != sizeof(ipv6_only_time))
				goto wrong_length;
			memcpy(&ipv6_only_time, p, sizeof(ipv6_only_time));
			ipv6_only_time = ntohl(ipv6_only_time);
			if (log_getverbose() > 1) {
				log_debug("DHO_IPV6_ONLY_PREFERRED %us",
				    ipv6_only_time);
			}
			p += dho_len;
			rem -= dho_len;
			break;
		default:
			if (log_getverbose() > 1)
				log_debug("DHO_%u, len: %u", dho, dho_len);
			p += dho_len;
			rem -= dho_len;
		}

	}
	while (rem != 0) {
		if (*p != DHO_PAD)
			break;
		p++;
		rem--;
	}
	if (rem != 0)
		log_debug("%s: %lu bytes garbage data from %s", __func__, rem,
		    from);

	log_debug("%s on %s from %s/%s to %s/%s",
	    dhcp_message_type2str(dhcp_message_type), if_name == NULL ? "?" :
	    if_name, from, hbuf_src, to, hbuf_dst);

	switch (dhcp_message_type) {
	case DHCPOFFER:
		if (iface->state != IF_INIT) {
			log_debug("ignoring unexpected DHCPOFFER");
			return;
		}
		if (server_identifier.s_addr == INADDR_ANY &&
		    dhcp_hdr->yiaddr.s_addr == INADDR_ANY) {
			log_warnx("%s: did not receive server identifier or "
			    "offered IP address", __func__);
			return;
		}
#ifndef SMALL
		if (iface_conf != NULL && iface_conf->prefer_ipv6 &&
		    ipv6_only_time > 0) {
			iface->ipv6_only_time = ipv6_only_time;
			state_transition(iface, IF_IPV6_ONLY);
			break;
		}
#endif
		iface->server_identifier = server_identifier;
		iface->dhcp_server = server_identifier;
		iface->requested_ip = dhcp_hdr->yiaddr;
		state_transition(iface, IF_REQUESTING);
		break;
	case DHCPACK:
		switch (iface->state) {
		case IF_REQUESTING:
		case IF_RENEWING:
		case IF_REBINDING:
		case IF_REBOOTING:
			break;
		default:
			log_debug("ignoring unexpected DHCPACK");
			return;
		}
		if (server_identifier.s_addr == INADDR_ANY &&
		    dhcp_hdr->yiaddr.s_addr == INADDR_ANY) {
			log_warnx("%s: did not receive server identifier or "
			    "offered IP address", __func__);
			return;
		}
		if (lease_time == 0) {
			log_warnx("%s no lease time from %s", __func__, from);
			return;
		}
		if (subnet_mask.s_addr == INADDR_ANY) {
			log_warnx("%s: no subnetmask received from %s",
			    __func__, from);
			return;
		}

		/* Defaults if we didn't receive renewal or rebinding time. */
		if (renewal_time == 0)
			renewal_time = lease_time / 2;
		if (rebinding_time == 0)
			rebinding_time = lease_time - (lease_time / 8);

		/* RFC 2131 4.4.5 */
		/* Ignore invalid T1/T2 options */
		if (renewal_time >= rebinding_time) {
			log_warnx("%s: renewal_time(%u) >= rebinding_time(%u) "
			    "from %s: using defaults",
			    __func__, renewal_time, rebinding_time, from);
			renewal_time = rebinding_time = 0;
		} else if (rebinding_time >= lease_time) {
			log_warnx("%s: rebinding_time(%u) >= lease_time(%u) "
			    "from %s: using defaults",
			    __func__, rebinding_time, lease_time, from);
			renewal_time = rebinding_time = 0;
		}

		/* Defaults if we received wrong renewal or rebinding time. */
		if (renewal_time == 0)
			renewal_time = lease_time / 2;
		if (rebinding_time == 0)
			rebinding_time = lease_time - (lease_time / 8);

		clock_gettime(CLOCK_MONOTONIC, &iface->request_time);
		iface->server_identifier = server_identifier;
		iface->dhcp_server = server_identifier;
		iface->requested_ip = dhcp_hdr->yiaddr;
		iface->mask = subnet_mask;
#ifndef SMALL
		if (iface_conf != NULL && iface_conf->ignore & IGN_ROUTES) {
			iface->routes_len = 0;
			memset(iface->routes, 0, sizeof(iface->routes));
		} else
#endif /* SMALL */
		{
			iface->prev_routes_len = iface->routes_len;
			memcpy(iface->prev_routes, iface->routes,
			    sizeof(iface->prev_routes));
			iface->routes_len = routes_len;
			memcpy(iface->routes, routes, sizeof(iface->routes));
		}
		iface->lease_time = lease_time;
		iface->renewal_time = renewal_time;
		iface->rebinding_time = rebinding_time;

#ifndef SMALL
		if (iface_conf != NULL && iface_conf->ignore & IGN_DNS) {
			memset(iface->nameservers, 0,
			    sizeof(iface->nameservers));
		} else
#endif /* SMALL */
		{
			memcpy(iface->nameservers, nameservers,
			    sizeof(iface->nameservers));
		}

		iface->siaddr = dhcp_hdr->siaddr;

		/* we made sure this is a string futher up */
		strnvis(iface->file, dhcp_hdr->file, sizeof(iface->file),
		    VIS_SAFE);

		strlcpy(iface->domainname, domainname,
		    sizeof(iface->domainname));
		strlcpy(iface->hostname, hostname, sizeof(iface->hostname));
#ifndef SMALL
		if (iface_conf != NULL && iface_conf->prefer_ipv6 &&
		    ipv6_only_time > 0) {
			iface->ipv6_only_time = ipv6_only_time;
			state_transition(iface, IF_IPV6_ONLY);
			break;
		}
#endif
		state_transition(iface, IF_BOUND);
		break;
	case DHCPNAK:
		switch (iface->state) {
		case IF_REQUESTING:
		case IF_RENEWING:
		case IF_REBINDING:
		case IF_REBOOTING:
			break;
		default:
			log_debug("ignoring unexpected DHCPNAK");
			return;
		}

		state_transition(iface, IF_INIT);
		break;
	default:
		log_warnx("%s: unimplemented message type %d", __func__,
		    dhcp_message_type);
		break;
	}
	return;
 too_short:
	log_warnx("%s: message from %s too short", __func__, from);
	return;
 wrong_length:
	log_warnx("%s: received option %d with wrong length: %d", __func__,
	    dho, dho_len);
	return;
}

/* XXX check valid transitions */
void
state_transition(struct dhcpleased_iface *iface, enum if_state new_state)
{
	enum if_state	 old_state = iface->state;
	struct timespec	 now, res;
	char		 ifnamebuf[IF_NAMESIZE], *if_name;

	iface->state = new_state;

	switch (new_state) {
	case IF_DOWN:
		if (iface->requested_ip.s_addr == INADDR_ANY) {
			/* nothing to do until iface comes up */
			iface->timo.tv_sec = -1;
			break;
		}
		if (old_state == IF_DOWN) {
			/* nameservers already withdrawn when if went down */
			send_deconfigure_interface(iface);
			/* nothing more to do until iface comes back */
			iface->timo.tv_sec = -1;
		} else {
			send_rdns_withdraw(iface);
			clock_gettime(CLOCK_MONOTONIC, &now);
			timespecsub(&now, &iface->request_time, &res);
			iface->timo.tv_sec = iface->lease_time - res.tv_sec;
			if (iface->timo.tv_sec < 0)
				iface->timo.tv_sec = 0; /* deconfigure now */
		}
		break;
	case IF_INIT:
		switch (old_state) {
		case IF_INIT:
			if (iface->timo.tv_sec < MAX_EXP_BACKOFF_SLOW)
				iface->timo.tv_sec *= 2;
			break;
		case IF_REQUESTING:
		case IF_RENEWING:
		case IF_REBINDING:
		case IF_REBOOTING:
			/* lease expired, got DHCPNAK or timeout: delete IP */
			send_rdns_withdraw(iface);
			send_deconfigure_interface(iface);
			/* fall through */
		case IF_DOWN:
		case IF_IPV6_ONLY:
			iface->timo.tv_sec = START_EXP_BACKOFF;
			iface->xid = arc4random();
			break;
		case IF_BOUND:
			fatal("invalid transition Bound -> Init");
			break;
		}
		request_dhcp_discover(iface);
		break;
	case IF_REBOOTING:
		if (old_state == IF_REBOOTING)
			iface->timo.tv_sec *= 2;
		else {
			iface->timo.tv_sec = START_EXP_BACKOFF;
			iface->xid = arc4random();
		}
		request_dhcp_request(iface);
		break;
	case IF_REQUESTING:
		if (old_state == IF_REQUESTING)
			iface->timo.tv_sec *= 2;
		else
			iface->timo.tv_sec = START_EXP_BACKOFF;
		request_dhcp_request(iface);
		break;
	case IF_BOUND:
		iface->timo.tv_sec = iface->renewal_time;
		if (old_state == IF_REQUESTING || old_state == IF_REBOOTING) {
			send_configure_interface(iface);
			send_rdns_proposal(iface);
		}
		break;
	case IF_RENEWING:
		if (old_state == IF_BOUND) {
			iface->timo.tv_sec = (iface->rebinding_time -
			    iface->renewal_time) / 2; /* RFC 2131 4.4.5 */
			iface->xid = arc4random();
		} else
			iface->timo.tv_sec /= 2;

		if (iface->timo.tv_sec < 60)
			iface->timo.tv_sec = 60;
		request_dhcp_request(iface);
		break;
	case IF_REBINDING:
		if (old_state == IF_RENEWING) {
			iface->timo.tv_sec = (iface->lease_time -
			    iface->rebinding_time) / 2; /* RFC 2131 4.4.5 */
		} else
			iface->timo.tv_sec /= 2;
		request_dhcp_request(iface);
		break;
	case IF_IPV6_ONLY:
		switch (old_state) {
		case IF_REQUESTING:
		case IF_RENEWING:
		case IF_REBINDING:
		case IF_REBOOTING:
			/* going IPv6 only: delete legacy IP */
			send_rdns_withdraw(iface);
			send_deconfigure_interface(iface);
			/* fall through */
		case IF_INIT:
		case IF_DOWN:
		case IF_IPV6_ONLY:
			iface->timo.tv_sec = iface->ipv6_only_time;
			break;
		case IF_BOUND:
			fatal("invalid transition Bound -> IPv6 only");
			break;
		}
	}

	if_name = if_indextoname(iface->if_index, ifnamebuf);
	log_debug("%s[%s] %s -> %s, timo: %lld", __func__, if_name == NULL ?
	    "?" : if_name, if_state_name[old_state], if_state_name[new_state],
	    iface->timo.tv_sec);

	if (iface->timo.tv_sec == -1) {
		if (evtimer_pending(&iface->timer, NULL))
			evtimer_del(&iface->timer);
	} else
		evtimer_add(&iface->timer, &iface->timo);
}

void
iface_timeout(int fd, short events, void *arg)
{
	struct dhcpleased_iface	*iface = (struct dhcpleased_iface *)arg;
	struct timespec		 now, res;

	log_debug("%s[%d]: %s", __func__, iface->if_index,
	    if_state_name[iface->state]);

	switch (iface->state) {
	case IF_DOWN:
		state_transition(iface, IF_DOWN);
		break;
	case IF_INIT:
		state_transition(iface, IF_INIT);
		break;
	case IF_REBOOTING:
		if (iface->timo.tv_sec >= MAX_EXP_BACKOFF_FAST)
			state_transition(iface, IF_INIT);
		else
			state_transition(iface, IF_REBOOTING);
		break;
	case IF_REQUESTING:
		if (iface->timo.tv_sec >= MAX_EXP_BACKOFF_SLOW)
			state_transition(iface, IF_INIT);
		else
			state_transition(iface, IF_REQUESTING);
		break;
	case IF_BOUND:
		state_transition(iface, IF_RENEWING);
		break;
	case IF_RENEWING:
		clock_gettime(CLOCK_MONOTONIC, &now);
		timespecsub(&now, &iface->request_time, &res);
		log_debug("%s: res.tv_sec: %lld, rebinding_time: %u", __func__,
		    res.tv_sec, iface->rebinding_time);
		if (res.tv_sec > iface->rebinding_time)
			state_transition(iface, IF_REBINDING);
		else
			state_transition(iface, IF_RENEWING);
		break;
	case IF_REBINDING:
		clock_gettime(CLOCK_MONOTONIC, &now);
		timespecsub(&now, &iface->request_time, &res);
		log_debug("%s: res.tv_sec: %lld, lease_time: %u", __func__,
		    res.tv_sec, iface->lease_time);
		if (res.tv_sec > iface->lease_time)
			state_transition(iface, IF_INIT);
		else
			state_transition(iface, IF_REBINDING);
		break;
	case IF_IPV6_ONLY:
		state_transition(iface, IF_REQUESTING);
		break;
	}
}

void
request_dhcp_discover(struct dhcpleased_iface *iface)
{
	struct imsg_req_dhcp	 imsg;

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

	imsg.if_index = iface->if_index;
	imsg.xid = iface->xid;

	/*
	 * similar to RFC 2131 4.3.6, Table 4 for DHCPDISCOVER
	 * ------------------------------
	 * |              | INIT         |
	 * ------------------------------
	 * |broad/unicast | broadcast    |
	 * |server-ip     | MUST NOT     |
	 * |requested-ip  | MAY          |
	 * |ciaddr        | zero         |
	 * ------------------------------
	 *
	 * Leaving everything at 0 from the memset results in this table with
	 * requested-ip not set.
	*/

	engine_imsg_compose_frontend(IMSG_SEND_DISCOVER, 0, &imsg, sizeof(imsg));
}

void
request_dhcp_request(struct dhcpleased_iface *iface)
{
	struct imsg_req_dhcp	 imsg;

	imsg.if_index = iface->if_index;
	imsg.xid = iface->xid;

	/*
	 * RFC 2131 4.3.6, Table 4
	 * ---------------------------------------------------------------------
	 * |              |REBOOTING    |REQUESTING   |RENEWING     |REBINDING |
	 * ---------------------------------------------------------------------
	 * |broad/unicast |broadcast    |broadcast    |unicast      |broadcast |
	 * |server-ip     |MUST NOT     |MUST         |MUST NOT     |MUST NOT  |
	 * |requested-ip  |MUST         |MUST         |MUST NOT     |MUST NOT  |
	 * |ciaddr        |zero         |zero         |IP address   |IP address|
	 * ---------------------------------------------------------------------
	*/
	switch (iface->state) {
	case IF_DOWN:
		fatalx("invalid state IF_DOWN in %s", __func__);
		break;
	case IF_INIT:
		fatalx("invalid state IF_INIT in %s", __func__);
		break;
	case IF_BOUND:
		fatalx("invalid state IF_BOUND in %s", __func__);
		break;
	case IF_REBOOTING:
		imsg.dhcp_server.s_addr = INADDR_ANY;		/* broadcast */
		imsg.server_identifier.s_addr = INADDR_ANY;	/* MUST NOT */
		imsg.requested_ip = iface->requested_ip;	/* MUST */
		imsg.ciaddr.s_addr = INADDR_ANY;		/* zero */
		break;
	case IF_REQUESTING:
		imsg.dhcp_server.s_addr = INADDR_ANY;		/* broadcast */
		imsg.server_identifier =
		    iface->server_identifier;			/* MUST */
		imsg.requested_ip = iface->requested_ip;	/* MUST */
		imsg.ciaddr.s_addr = INADDR_ANY;		/* zero */
		break;
	case IF_RENEWING:
		imsg.dhcp_server = iface->dhcp_server;		/* unicast */
		imsg.server_identifier.s_addr = INADDR_ANY;	/* MUST NOT */
		imsg.requested_ip.s_addr = INADDR_ANY;		/* MUST NOT */
		imsg.ciaddr = iface->requested_ip;		/* IP address */
		break;
	case IF_REBINDING:
		imsg.dhcp_server.s_addr = INADDR_ANY;		/* broadcast */
		imsg.server_identifier.s_addr = INADDR_ANY;	/* MUST NOT */
		imsg.requested_ip.s_addr = INADDR_ANY;		/* MUST NOT */
		imsg.ciaddr = iface->requested_ip;		/* IP address */
		break;
	case IF_IPV6_ONLY:
		fatalx("invalid state IF_IPV6_ONLY in %s", __func__);
		break;
	}

	engine_imsg_compose_frontend(IMSG_SEND_REQUEST, 0, &imsg, sizeof(imsg));
}

void
log_lease(struct dhcpleased_iface *iface, int deconfigure)
{
	char	 hbuf_lease[INET_ADDRSTRLEN], hbuf_server[INET_ADDRSTRLEN];
	char	 ifnamebuf[IF_NAMESIZE], *if_name;

	if_name = if_indextoname(iface->if_index, ifnamebuf);
	inet_ntop(AF_INET, &iface->requested_ip, hbuf_lease,
	    sizeof(hbuf_lease));
	inet_ntop(AF_INET, &iface->server_identifier, hbuf_server,
	    sizeof(hbuf_server));


	if (deconfigure)
		log_info("deleting %s from %s (lease from %s)", hbuf_lease,
		    if_name == NULL ? "?" : if_name, hbuf_server);
	else
		log_info("adding %s to %s (lease from %s)", hbuf_lease,
		    if_name == NULL ? "?" : if_name, hbuf_server);
}

void
send_configure_interface(struct dhcpleased_iface *iface)
{
	struct imsg_configure_interface	 imsg;
	int				 i, j, found;

	log_lease(iface, 0);

	memset(&imsg, 0, sizeof(imsg));
	imsg.if_index = iface->if_index;
	imsg.rdomain = iface->rdomain;
	imsg.addr = iface->requested_ip;
	imsg.mask = iface->mask;
	imsg.siaddr = iface->siaddr;
	strlcpy(imsg.file, iface->file, sizeof(imsg.file));
	strlcpy(imsg.domainname, iface->domainname, sizeof(imsg.domainname));
	strlcpy(imsg.hostname, iface->hostname, sizeof(imsg.hostname));
	for (i = 0; i < iface->prev_routes_len; i++) {
		found = 0;
		for (j = 0; j < iface->routes_len; j++) {
			if (memcmp(&iface->prev_routes[i], &iface->routes[j],
			    sizeof(struct dhcp_route)) == 0) {
				found = 1;
				break;
			}
		}
		if (!found)
			imsg.routes[imsg.routes_len++] = iface->prev_routes[i];
	}
	if (imsg.routes_len > 0)
		engine_imsg_compose_main(IMSG_WITHDRAW_ROUTES, 0, &imsg,
		    sizeof(imsg));
	imsg.routes_len = iface->routes_len;
	memcpy(imsg.routes, iface->routes, sizeof(imsg.routes));
	engine_imsg_compose_main(IMSG_CONFIGURE_INTERFACE, 0, &imsg,
	    sizeof(imsg));
}

void
send_deconfigure_interface(struct dhcpleased_iface *iface)
{
	struct imsg_configure_interface	 imsg;

	if (iface->requested_ip.s_addr == INADDR_ANY)
		return;

	log_lease(iface, 1);

	imsg.if_index = iface->if_index;
	imsg.rdomain = iface->rdomain;
	imsg.addr = iface->requested_ip;
	imsg.mask = iface->mask;
	imsg.siaddr = iface->siaddr;
	strlcpy(imsg.file, iface->file, sizeof(imsg.file));
	strlcpy(imsg.domainname, iface->domainname, sizeof(imsg.domainname));
	strlcpy(imsg.hostname, iface->hostname, sizeof(imsg.hostname));
	imsg.routes_len = iface->routes_len;
	memcpy(imsg.routes, iface->routes, sizeof(imsg.routes));
	engine_imsg_compose_main(IMSG_DECONFIGURE_INTERFACE, 0, &imsg,
	    sizeof(imsg));

	iface->server_identifier.s_addr = INADDR_ANY;
	iface->dhcp_server.s_addr = INADDR_ANY;
	iface->requested_ip.s_addr = INADDR_ANY;
	iface->mask.s_addr = INADDR_ANY;
	iface->routes_len = 0;
	memset(iface->routes, 0, sizeof(iface->routes));
}

void
send_routes_withdraw(struct dhcpleased_iface *iface)
{
	struct imsg_configure_interface	 imsg;

	if (iface->requested_ip.s_addr == INADDR_ANY || iface->routes_len == 0)
		return;

	imsg.if_index = iface->if_index;
	imsg.rdomain = iface->rdomain;
	imsg.addr = iface->requested_ip;
	imsg.mask = iface->mask;
	imsg.siaddr = iface->siaddr;
	strlcpy(imsg.file, iface->file, sizeof(imsg.file));
	strlcpy(imsg.domainname, iface->domainname, sizeof(imsg.domainname));
	strlcpy(imsg.hostname, iface->hostname, sizeof(imsg.hostname));
	imsg.routes_len = iface->routes_len;
	memcpy(imsg.routes, iface->routes, sizeof(imsg.routes));
	engine_imsg_compose_main(IMSG_WITHDRAW_ROUTES, 0, &imsg,
	    sizeof(imsg));
}

void
log_rdns(struct dhcpleased_iface *iface, int withdraw)
{
	int	 i;
	char	 hbuf_rdns[INET_ADDRSTRLEN], hbuf_server[INET_ADDRSTRLEN];
	char	 ifnamebuf[IF_NAMESIZE], *if_name, *rdns_buf = NULL, *tmp_buf;

	if_name = if_indextoname(iface->if_index, ifnamebuf);

	inet_ntop(AF_INET, &iface->server_identifier, hbuf_server,
	    sizeof(hbuf_server));

	for (i = 0; i < MAX_RDNS_COUNT && iface->nameservers[i].s_addr !=
		 INADDR_ANY; i++) {
		inet_ntop(AF_INET, &iface->nameservers[i], hbuf_rdns,
		    sizeof(hbuf_rdns));
		tmp_buf = rdns_buf;
		if (asprintf(&rdns_buf, "%s %s", tmp_buf ? tmp_buf : "",
		    hbuf_rdns) < 0) {
			rdns_buf = NULL;
			break;
		}
		free(tmp_buf);
	}

	if (rdns_buf != NULL) {
		if (withdraw) {
			log_info("deleting nameservers%s (lease from %s on %s)",
			    rdns_buf, hbuf_server, if_name == NULL ? "?" :
			    if_name);
		} else {
			log_info("adding nameservers%s (lease from %s on %s)",
			    rdns_buf, hbuf_server, if_name == NULL ? "?" :
			    if_name);
		}
		free(rdns_buf);
	}
}

void
send_rdns_proposal(struct dhcpleased_iface *iface)
{
	struct imsg_propose_rdns	 imsg;

	log_rdns(iface, 0);

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

	imsg.if_index = iface->if_index;
	imsg.rdomain = iface->rdomain;
	for (imsg.rdns_count = 0; imsg.rdns_count < MAX_RDNS_COUNT &&
		 iface->nameservers[imsg.rdns_count].s_addr != INADDR_ANY;
	     imsg.rdns_count++)
		;
	memcpy(imsg.rdns, iface->nameservers, sizeof(imsg.rdns));
	engine_imsg_compose_main(IMSG_PROPOSE_RDNS, 0, &imsg, sizeof(imsg));
}

void
send_rdns_withdraw(struct dhcpleased_iface *iface)
{
	struct imsg_propose_rdns	 imsg;

	log_rdns(iface, 1);

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

	imsg.if_index = iface->if_index;
	imsg.rdomain = iface->rdomain;
	engine_imsg_compose_main(IMSG_WITHDRAW_RDNS, 0, &imsg, sizeof(imsg));
	memset(iface->nameservers, 0, sizeof(iface->nameservers));
}

void
parse_lease(struct dhcpleased_iface *iface, struct imsg_ifinfo *imsg_ifinfo)
{
	char	*p, *p1;

	iface->requested_ip.s_addr = INADDR_ANY;

	if ((p = strstr(imsg_ifinfo->lease, LEASE_IP_PREFIX)) == NULL)
		return;

	p += sizeof(LEASE_IP_PREFIX) - 1;
	if ((p1 = strchr(p, '\n')) == NULL)
		return;
	*p1 = '\0';

	if (inet_pton(AF_INET, p, &iface->requested_ip) != 1)
		iface->requested_ip.s_addr = INADDR_ANY;
}

void
log_dhcp_hdr(struct dhcp_hdr *dhcp_hdr)
{
#ifndef	SMALL
	char	 hbuf[INET_ADDRSTRLEN];

	log_debug("dhcp_hdr op: %s (%d)", dhcp_hdr->op == DHCP_BOOTREQUEST ?
	    "Boot Request" : dhcp_hdr->op == DHCP_BOOTREPLY ? "Boot Reply" :
	    "Unknown", dhcp_hdr->op);
	log_debug("dhcp_hdr htype: %s (%d)", dhcp_hdr->htype == 1 ? "Ethernet":
	    "Unknown", dhcp_hdr->htype);
	log_debug("dhcp_hdr hlen: %d", dhcp_hdr->hlen);
	log_debug("dhcp_hdr hops: %d", dhcp_hdr->hops);
	log_debug("dhcp_hdr xid: 0x%x", ntohl(dhcp_hdr->xid));
	log_debug("dhcp_hdr secs: %u", dhcp_hdr->secs);
	log_debug("dhcp_hdr flags: 0x%x", dhcp_hdr->flags);
	log_debug("dhcp_hdr ciaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->ciaddr,
	    hbuf, sizeof(hbuf)));
	log_debug("dhcp_hdr yiaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->yiaddr,
	    hbuf, sizeof(hbuf)));
	log_debug("dhcp_hdr siaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->siaddr,
	    hbuf, sizeof(hbuf)));
	log_debug("dhcp_hdr giaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->giaddr,
	    hbuf, sizeof(hbuf)));
	log_debug("dhcp_hdr chaddr: %02x:%02x:%02x:%02x:%02x:%02x "
	    "(%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x)",
	    dhcp_hdr->chaddr[0], dhcp_hdr->chaddr[1], dhcp_hdr->chaddr[2],
	    dhcp_hdr->chaddr[3], dhcp_hdr->chaddr[4], dhcp_hdr->chaddr[5],
	    dhcp_hdr->chaddr[6], dhcp_hdr->chaddr[7], dhcp_hdr->chaddr[8],
	    dhcp_hdr->chaddr[9], dhcp_hdr->chaddr[10], dhcp_hdr->chaddr[11],
	    dhcp_hdr->chaddr[12], dhcp_hdr->chaddr[13], dhcp_hdr->chaddr[14],
	    dhcp_hdr->chaddr[15]);
	/* ignore sname and file, if we ever print it use strvis(3) */
#endif
}

const char *
dhcp_message_type2str(uint8_t dhcp_message_type)
{
	switch (dhcp_message_type) {
	case DHCPDISCOVER:
		return "DHCPDISCOVER";
	case DHCPOFFER:
		return "DHCPOFFER";
	case DHCPREQUEST:
		return "DHCPREQUEST";
	case DHCPDECLINE:
		return "DHCPDECLINE";
	case DHCPACK:
		return "DHCPACK";
	case DHCPNAK:
		return "DHCPNAK";
	case DHCPRELEASE:
		return "DHCPRELEASE";
	case DHCPINFORM:
		return "DHCPINFORM";
	default:
		return "Unknown";
	}
}