Edit

IABSD.fr/src/sbin/slaacd/engine.c

Branch :

  • Show log

    Commit

  • Author : florian
    Date : 2026-04-16 16:59:07
    Hash : bd186633
    Message : Prevent buffer overflow by checking the correct counter. An attacker on the same layer 2 network can send rogue router advertisements, potentially crashing slaacd. From Maurice Hieronymus (mhi AT mailbox.org), thanks! OK deraadt

  • sbin/slaacd/engine.c
  • /*	$OpenBSD: engine.c,v 1.101 2026/04/16 16:59:07 florian Exp $	*/
    
    /*
     * Copyright (c) 2017 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.
     */
    
    /*
     * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
     * 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.
     * 3. Neither the name of the project nor the names of its contributors
     *    may be used to endorse or promote products derived from this software
     *    without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT OR CONTRIBUTORS BE LIABLE
     * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     * SUCH DAMAGE.
     */
    
    #include <sys/types.h>
    #include <sys/queue.h>
    #include <sys/socket.h>
    #include <sys/syslog.h>
    #include <sys/uio.h>
    
    #include <net/if.h>
    #include <net/route.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <netinet/if_ether.h>
    #include <netinet/ip6.h>
    #include <netinet6/nd6.h>
    #include <netinet/icmp6.h>
    
    #include <crypto/sha2.h>
    
    #include <errno.h>
    #include <event.h>
    #include <imsg.h>
    #include <pwd.h>
    #include <signal.h>
    #include <stddef.h>
    #include <stdlib.h>
    #include <string.h>
    #include <time.h>
    #include <unistd.h>
    
    #include "log.h"
    #include "slaacd.h"
    #include "engine.h"
    
    #define	MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
    
    #define	MAX_RTR_SOLICITATION_DELAY	1
    #define	MAX_RTR_SOLICITATION_DELAY_USEC	MAX_RTR_SOLICITATION_DELAY * 1000000
    #define	RTR_SOLICITATION_INTERVAL	4
    #define	MAX_RTR_SOLICITATIONS		3
    
    /*
     * Constants for RFC 8981 temporary address extensions
     *
     * PRIV_PREFERRED_LIFETIME > (PRIV_MAX_DESYNC_FACTOR + PRIV_REGEN_ADVANCE)
     */
    #define PRIV_VALID_LIFETIME	172800	/* 2 days */
    #define PRIV_PREFERRED_LIFETIME	86400	/* 1 day */
    #define PRIV_MAX_DESYNC_FACTOR	34560	/* PRIV_PREFERRED_LIFETIME * 0.4 */
    #define	PRIV_REGEN_ADVANCE	5	/* 5 seconds */
    
    enum if_state {
    	IF_DOWN,
    	IF_INIT,
    	IF_BOUND,
    };
    
    enum proposal_state {
    	PROPOSAL_IF_DOWN,
    	PROPOSAL_NOT_CONFIGURED,
    	PROPOSAL_CONFIGURED,
    	PROPOSAL_NEARLY_EXPIRED,
    	PROPOSAL_WITHDRAWN,
    	PROPOSAL_DUPLICATED,
    	PROPOSAL_STALE,
    };
    
    const char* rpref_name[] = {
    	"Low",
    	"Medium",
    	"High",
    };
    
    struct radv_prefix {
    	LIST_ENTRY(radv_prefix)	entries;
    	struct in6_addr		prefix;
    	uint8_t			prefix_len; /*XXX int */
    	int			onlink;
    	int			autonomous;
    	uint32_t		vltime;
    	uint32_t		pltime;
    	int			dad_counter;
    };
    
    struct radv_rdns {
    	LIST_ENTRY(radv_rdns)	entries;
    	struct in6_addr		rdns;
    };
    
    struct radv {
    	LIST_ENTRY(radv)		 entries;
    	struct sockaddr_in6		 from;
    	struct timespec			 when;
    	struct timespec			 uptime;
    	struct event			 timer;
    	uint32_t			 min_lifetime;
    	uint8_t				 curhoplimit;
    	int				 managed;
    	int				 other;
    	enum rpref			 rpref;
    	uint16_t			 router_lifetime; /* in seconds */
    	uint32_t			 reachable_time; /* in milliseconds */
    	uint32_t			 retrans_time; /* in milliseconds */
    	LIST_HEAD(, radv_prefix)	 prefixes;
    	uint32_t			 rdns_lifetime;
    	LIST_HEAD(, radv_rdns)		 rdns_servers;
    	uint32_t			 mtu;
    };
    
    struct address_proposal {
    	LIST_ENTRY(address_proposal)	 entries;
    	struct event			 timer;
    	int64_t				 id;
    	enum proposal_state		 state;
    	struct timeval			 timo;
    	struct timespec			 created;
    	struct timespec			 when;
    	struct timespec			 uptime;
    	uint32_t			 if_index;
    	struct ether_addr		 hw_address;
    	struct sockaddr_in6		 from;
    	struct sockaddr_in6		 addr;
    	struct in6_addr			 mask;
    	struct in6_addr			 prefix;
    	int				 temporary;
    	uint8_t				 prefix_len;
    	uint32_t			 vltime;
    	uint32_t			 pltime;
    	uint32_t			 desync_factor;
    	uint8_t				 soiikey[SLAACD_SOIIKEY_LEN];
    	uint32_t			 mtu;
    };
    
    struct dfr_proposal {
    	LIST_ENTRY(dfr_proposal)	 entries;
    	struct event			 timer;
    	int64_t				 id;
    	enum proposal_state		 state;
    	struct timeval			 timo;
    	struct timespec			 when;
    	struct timespec			 uptime;
    	uint32_t			 if_index;
    	int				 rdomain;
    	struct sockaddr_in6		 addr;
    	uint32_t			 router_lifetime;
    	enum rpref			 rpref;
    };
    
    struct rdns_proposal {
    	LIST_ENTRY(rdns_proposal)	 entries;
    	struct event			 timer;
    	int64_t				 id;
    	enum proposal_state		 state;
    	struct timeval			 timo;
    	struct timespec			 when;
    	struct timespec			 uptime;
    	uint32_t			 if_index;
    	int				 rdomain;
    	struct sockaddr_in6		 from;
    	int				 rdns_count;
    	struct in6_addr			 rdns[MAX_RDNS_COUNT];
    	uint32_t			 rdns_lifetime;
    };
    
    struct slaacd_iface {
    	LIST_ENTRY(slaacd_iface)	 entries;
    	enum if_state			 state;
    	struct event			 timer;
    	struct timeval			 timo;
    	struct timespec			 last_sol;
    	int				 probes;
    	uint32_t			 if_index;
    	uint32_t			 rdomain;
    	int				 running;
    	int				 autoconf;
    	int				 temporary;
    	int				 soii;
    	struct ether_addr		 hw_address;
    	struct sockaddr_in6		 ll_address;
    	uint8_t				 soiikey[SLAACD_SOIIKEY_LEN];
    	int				 link_state;
    	uint32_t			 cur_mtu;
    	LIST_HEAD(, radv)		 radvs;
    	LIST_HEAD(, address_proposal)	 addr_proposals;
    	LIST_HEAD(, dfr_proposal)	 dfr_proposals;
    	LIST_HEAD(, rdns_proposal)	 rdns_proposals;
    };
    
    LIST_HEAD(, slaacd_iface) slaacd_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 slaacd_iface *, pid_t);
    void			 engine_showinfo_ctl(pid_t, uint32_t);
    void			 debug_log_ra(struct imsg_ra *);
    int			 in6_mask2prefixlen(struct in6_addr *);
    #endif	/* SMALL */
    struct slaacd_iface	*get_slaacd_iface_by_id(uint32_t);
    void			 remove_slaacd_iface(uint32_t);
    void			 free_ra(struct radv *);
    void			 iface_state_transition(struct slaacd_iface *, enum
    			     if_state);
    void			 addr_proposal_state_transition(struct
    			     address_proposal *, enum proposal_state);
    void			 dfr_proposal_state_transition(struct dfr_proposal *,
    			     enum proposal_state);
    void			 rdns_proposal_state_transition(struct rdns_proposal *,
    			     enum proposal_state);
    void			 engine_update_iface(struct imsg_ifinfo *);
    void			 request_solicitation(struct slaacd_iface *);
    void			 parse_ra(struct slaacd_iface *, struct imsg_ra *);
    void			 gen_addr(struct slaacd_iface *, struct radv_prefix *,
    			     struct address_proposal *, int);
    void			 gen_address_proposal(struct slaacd_iface *, struct
    			     radv *, struct radv_prefix *, int);
    void			 free_address_proposal(struct address_proposal *);
    void			 withdraw_addr(struct address_proposal *);
    void			 configure_address(struct address_proposal *);
    void			 in6_prefixlen2mask(struct in6_addr *, int len);
    void			 gen_dfr_proposal(struct slaacd_iface *, struct
    			     radv *);
    void			 configure_dfr(struct dfr_proposal *);
    void			 free_dfr_proposal(struct dfr_proposal *);
    void			 withdraw_dfr(struct dfr_proposal *);
    void			 update_iface_ra_rdns(struct slaacd_iface *,
    			     struct radv *);
    void			 gen_rdns_proposal(struct slaacd_iface *, struct
    			     radv *);
    void			 free_rdns_proposal(struct rdns_proposal *);
    void			 withdraw_rdns(struct rdns_proposal *);
    void			 compose_rdns_proposal(uint32_t, int);
    void			 update_iface_ra(struct slaacd_iface *, struct radv *);
    void			 update_iface_ra_dfr(struct slaacd_iface *,
        			     struct radv *);
    void			 update_iface_ra_prefix(struct slaacd_iface *,
    			     struct radv *, struct radv_prefix *prefix);
    void			 address_proposal_timeout(int, short, void *);
    void			 dfr_proposal_timeout(int, short, void *);
    void			 rdns_proposal_timeout(int, short, void *);
    void			 iface_timeout(int, short, void *);
    struct radv		*find_ra(struct slaacd_iface *, struct sockaddr_in6 *);
    struct address_proposal	*find_address_proposal_by_addr(struct slaacd_iface *,
    			     struct sockaddr_in6 *);
    struct dfr_proposal	*find_dfr_proposal_by_gw(struct slaacd_iface *,
    			     struct sockaddr_in6 *);
    struct rdns_proposal	*find_rdns_proposal_by_gw(struct slaacd_iface *,
    			     struct sockaddr_in6 *);
    struct radv_prefix	*find_prefix(struct radv *, struct in6_addr *, uint8_t);
    int			 engine_imsg_compose_main(int, pid_t, void *, uint16_t);
    uint32_t		 real_lifetime(struct timespec *, uint32_t);
    void			 merge_dad_couters(struct radv *, struct radv *);
    
    static struct imsgev	*iev_frontend;
    static struct imsgev	*iev_main;
    int64_t			 proposal_id;
    
    
    #define	CASE(x) case x : return #x
    
    #ifndef SMALL
    static const char*
    if_state_name(enum if_state ifs)
    {
    	switch (ifs) {
    	CASE(IF_DOWN);
    	CASE(IF_INIT);
    	CASE(IF_BOUND);
    	}
    }
    
    static const char*
    proposal_state_name(enum proposal_state ps)
    {
    	switch (ps) {
    	CASE(PROPOSAL_IF_DOWN);
    	CASE(PROPOSAL_NOT_CONFIGURED);
    	CASE(PROPOSAL_CONFIGURED);
    	CASE(PROPOSAL_NEARLY_EXPIRED);
    	CASE(PROPOSAL_WITHDRAWN);
    	CASE(PROPOSAL_DUPLICATED);
    	CASE(PROPOSAL_STALE);
    	}
    }
    #endif
    
    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;
    
    	log_init(debug, LOG_DAEMON);
    	log_setverbose(verbose);
    
    	if ((pw = getpwnam(SLAACD_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);
    
    	if (imsgbuf_init(&iev_main->ibuf, 3) == -1)
    		fatal(NULL);
    	imsgbuf_allow_fdpass(&iev_main->ibuf);
    	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(&slaacd_interfaces);
    
    	event_dispatch();
    
    	engine_shutdown();
    }
    
    __dead void
    engine_shutdown(void)
    {
    	/* Close pipes. */
    	imsgbuf_clear(&iev_frontend->ibuf);
    	close(iev_frontend->ibuf.fd);
    	imsgbuf_clear(&iev_main->ibuf);
    	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 slaacd_iface		*iface;
    	struct imsg_ra			 ra;
    	struct address_proposal		*addr_proposal = NULL;
    	struct dfr_proposal		*dfr_proposal = NULL;
    	struct imsg_del_addr		 del_addr;
    	struct imsg_del_route		 del_route;
    	struct imsg_dup_addr		 dup_addr;
    	ssize_t				 n;
    	int				 shut = 0;
    #ifndef	SMALL
    	int				 verbose;
    #endif	/* SMALL */
    	uint32_t			 if_index, type;
    
    	if (event & EV_READ) {
    		if ((n = imsgbuf_read(ibuf)) == -1)
    			fatal("imsgbuf_read error");
    		if (n == 0)	/* Connection closed. */
    			shut = 1;
    	}
    	if (event & EV_WRITE) {
    		if (imsgbuf_write(ibuf) == -1) {
    			if (errno == EPIPE)	/* Connection closed. */
    				shut = 1;
    			else
    				fatal("imsgbuf_write");
    		}
    	}
    
    	for (;;) {
    		if ((n = imsg_get(ibuf, &imsg)) == -1)
    			fatal("%s: imsg_get error", __func__);
    		if (n == 0)	/* No more messages. */
    			break;
    
    		type = imsg_get_type(&imsg);
    
    		switch (type) {
    #ifndef	SMALL
    		case IMSG_CTL_LOG_VERBOSE:
    			if (imsg_get_data(&imsg, &verbose,
    			    sizeof(verbose)) == -1)
    				fatalx("%s: invalid %s", __func__, i2s(type));
    
    			log_setverbose(verbose);
    			break;
    		case IMSG_CTL_SHOW_INTERFACE_INFO:
    			if (imsg_get_data(&imsg, &if_index,
    			    sizeof(if_index)) == -1)
    				fatalx("%s: invalid %s", __func__, i2s(type));
    
    			engine_showinfo_ctl(imsg_get_pid(&imsg), if_index);
    			break;
    #endif	/* SMALL */
    		case IMSG_REMOVE_IF:
    			if (imsg_get_data(&imsg, &if_index,
    			    sizeof(if_index)) == -1)
    				fatalx("%s: invalid %s", __func__, i2s(type));
    
    			remove_slaacd_iface(if_index);
    			break;
    		case IMSG_RA:
    			if (imsg_get_data(&imsg, &ra, sizeof(ra)) == -1)
    				fatalx("%s: invalid %s", __func__, i2s(type));
    
    			iface = get_slaacd_iface_by_id(ra.if_index);
    
    			/*
    			 * Ignore unsolicitated router advertisements
    			 * if we think the interface is still down.
    			 * Otherwise we confuse the state machine.
    			 */
    			if (iface != NULL && iface->state != IF_DOWN)
    				parse_ra(iface, &ra);
    			break;
    		case IMSG_CTL_SEND_SOLICITATION:
    			if (imsg_get_data(&imsg, &if_index,
    			    sizeof(if_index)) == -1)
    				fatalx("%s: invalid %s", __func__, i2s(type));
    
    			iface = get_slaacd_iface_by_id(if_index);
    			if (iface == NULL)
    				log_warnx("requested to send solicitation on "
    				    "non-autoconf interface: %u", if_index);
    			else {
    				iface->last_sol.tv_sec = 0; /* no rate limit */
    				request_solicitation(iface);
    			}
    			break;
    		case IMSG_DEL_ADDRESS:
    			if (imsg_get_data(&imsg, &del_addr,
    			    sizeof(del_addr)) == -1)
    				fatalx("%s: invalid %s", __func__, i2s(type));
    
    			iface = get_slaacd_iface_by_id(del_addr.if_index);
    			if (iface == NULL) {
    				log_debug("IMSG_DEL_ADDRESS: unknown interface"
    				    ", ignoring");
    				break;
    			}
    
    			addr_proposal = find_address_proposal_by_addr(iface,
    			    &del_addr.addr);
    			/*
    			 * If it's in state PROPOSAL_WITHDRAWN we just
    			 * deleted it ourself but want to keep it around
    			 * so we can renew it
    			 */
    			if (addr_proposal && addr_proposal->state !=
    			    PROPOSAL_WITHDRAWN)
    				free_address_proposal(addr_proposal);
    			break;
    		case IMSG_DEL_ROUTE:
    			if (imsg_get_data(&imsg, &del_route,
    			    sizeof(del_route)) == -1)
    				fatalx("%s: invalid %s", __func__, i2s(type));
    
    			iface = get_slaacd_iface_by_id(del_route.if_index);
    			if (iface == NULL) {
    				log_debug("IMSG_DEL_ROUTE: unknown interface"
    				    ", ignoring");
    				break;
    			}
    
    			dfr_proposal = find_dfr_proposal_by_gw(iface,
    			    &del_route.gw);
    
    			if (dfr_proposal) {
    				dfr_proposal->state = PROPOSAL_WITHDRAWN;
    				free_dfr_proposal(dfr_proposal);
    			}
    			break;
    		case IMSG_DUP_ADDRESS:
    			if (imsg_get_data(&imsg, &dup_addr,
    			    sizeof(dup_addr)) == -1)
    				fatalx("%s: invalid %s", __func__, i2s(type));
    
    			iface = get_slaacd_iface_by_id(dup_addr.if_index);
    			if (iface == NULL) {
    				log_debug("IMSG_DUP_ADDRESS: unknown interface"
    				    ", ignoring");
    				break;
    			}
    
    			addr_proposal = find_address_proposal_by_addr(iface,
    			    &dup_addr.addr);
    
    			if (addr_proposal)
    				addr_proposal_state_transition(addr_proposal,
    				    PROPOSAL_DUPLICATED);
    			break;
    		case IMSG_REPROPOSE_RDNS:
    			LIST_FOREACH (iface, &slaacd_interfaces, entries)
    				compose_rdns_proposal(iface->if_index,
    				    iface->rdomain);
    			break;
    		default:
    			log_debug("%s: unexpected imsg %d", __func__, 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)
    {
    	struct imsg		 imsg;
    	struct imsgev		*iev = bula;
    	struct imsgbuf		*ibuf = &iev->ibuf;
    	struct imsg_ifinfo	 imsg_ifinfo;
    	ssize_t			 n;
    	uint32_t		 type;
    	int			 shut = 0;
    
    	if (event & EV_READ) {
    		if ((n = imsgbuf_read(ibuf)) == -1)
    			fatal("imsgbuf_read error");
    		if (n == 0)	/* Connection closed. */
    			shut = 1;
    	}
    	if (event & EV_WRITE) {
    		if (imsgbuf_write(ibuf) == -1) {
    			if (errno == EPIPE)	/* Connection closed. */
    				shut = 1;
    			else
    				fatal("imsgbuf_write");
    		}
    	}
    
    	for (;;) {
    		if ((n = imsg_get(ibuf, &imsg)) == -1)
    			fatal("%s: imsg_get error", __func__);
    		if (n == 0)	/* No more messages. */
    			break;
    
    		type = imsg_get_type(&imsg);
    
    		switch (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);
    
    			if (imsgbuf_init(&iev_frontend->ibuf, fd) == -1)
    				fatal(NULL);
    			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_get_data(&imsg, &imsg_ifinfo,
    			    sizeof(imsg_ifinfo)) == -1)
    				fatalx("%s: invalid %s", __func__, i2s(type));
    
    			engine_update_iface(&imsg_ifinfo);
    			break;
    		default:
    			log_debug("%s: unexpected imsg %d", __func__, 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 slaacd_iface *iface, pid_t pid)
    {
    	struct ctl_engine_info			 cei;
    	struct ctl_engine_info_ra		 cei_ra;
    	struct ctl_engine_info_ra_prefix	 cei_ra_prefix;
    	struct ctl_engine_info_ra_rdns		 cei_ra_rdns;
    	struct ctl_engine_info_address_proposal	 cei_addr_proposal;
    	struct ctl_engine_info_dfr_proposal	 cei_dfr_proposal;
    	struct ctl_engine_info_rdns_proposal	 cei_rdns_proposal;
    	struct radv				*ra;
    	struct radv_prefix			*prefix;
    	struct radv_rdns			*rdns;
    	struct address_proposal			*addr_proposal;
    	struct dfr_proposal			*dfr_proposal;
    	struct rdns_proposal			*rdns_proposal;
    
    	memset(&cei, 0, sizeof(cei));
    	cei.if_index = iface->if_index;
    	cei.running = iface->running;
    	cei.autoconf = iface->autoconf;
    	cei.temporary = iface->temporary;
    	cei.soii = iface->soii;
    	memcpy(&cei.hw_address, &iface->hw_address, sizeof(struct ether_addr));
    	memcpy(&cei.ll_address, &iface->ll_address,
    	    sizeof(struct sockaddr_in6));
    	engine_imsg_compose_frontend(IMSG_CTL_SHOW_INTERFACE_INFO, pid, &cei,
    	    sizeof(cei));
    	LIST_FOREACH(ra, &iface->radvs, entries) {
    		memset(&cei_ra, 0, sizeof(cei_ra));
    		memcpy(&cei_ra.from, &ra->from, sizeof(cei_ra.from));
    		memcpy(&cei_ra.when, &ra->when, sizeof(cei_ra.when));
    		memcpy(&cei_ra.uptime, &ra->uptime, sizeof(cei_ra.uptime));
    		cei_ra.curhoplimit = ra->curhoplimit;
    		cei_ra.managed = ra->managed;
    		cei_ra.other = ra->other;
    		if (strlcpy(cei_ra.rpref, rpref_name[ra->rpref], sizeof(
    		    cei_ra.rpref)) >= sizeof(cei_ra.rpref))
    			log_warnx("truncated router preference");
    		cei_ra.router_lifetime = ra->router_lifetime;
    		cei_ra.reachable_time = ra->reachable_time;
    		cei_ra.retrans_time = ra->retrans_time;
    		cei_ra.mtu = ra->mtu;
    		engine_imsg_compose_frontend(IMSG_CTL_SHOW_INTERFACE_INFO_RA,
    		    pid, &cei_ra, sizeof(cei_ra));
    
    		LIST_FOREACH(prefix, &ra->prefixes, entries) {
    			memset(&cei_ra_prefix, 0, sizeof(cei_ra_prefix));
    
    			cei_ra_prefix.prefix = prefix->prefix;
    			cei_ra_prefix.prefix_len = prefix->prefix_len;
    			cei_ra_prefix.onlink = prefix->onlink;
    			cei_ra_prefix.autonomous = prefix->autonomous;
    			cei_ra_prefix.vltime = prefix->vltime;
    			cei_ra_prefix.pltime = prefix->pltime;
    			engine_imsg_compose_frontend(
    			    IMSG_CTL_SHOW_INTERFACE_INFO_RA_PREFIX, pid,
    			    &cei_ra_prefix, sizeof(cei_ra_prefix));
    		}
    
    		LIST_FOREACH(rdns, &ra->rdns_servers, entries) {
    			memset(&cei_ra_rdns, 0, sizeof(cei_ra_rdns));
    			memcpy(&cei_ra_rdns.rdns, &rdns->rdns,
    			    sizeof(cei_ra_rdns.rdns));
    			cei_ra_rdns.lifetime = ra->rdns_lifetime;
    			engine_imsg_compose_frontend(
    			    IMSG_CTL_SHOW_INTERFACE_INFO_RA_RDNS, pid,
    			    &cei_ra_rdns, sizeof(cei_ra_rdns));
    		}
    	}
    
    	if (!LIST_EMPTY(&iface->addr_proposals))
    		engine_imsg_compose_frontend(
    		    IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSALS, pid, NULL, 0);
    
    	LIST_FOREACH(addr_proposal, &iface->addr_proposals, entries) {
    		memset(&cei_addr_proposal, 0, sizeof(cei_addr_proposal));
    		cei_addr_proposal.id = addr_proposal->id;
    		if (strlcpy(cei_addr_proposal.state,
    		    proposal_state_name(addr_proposal->state),
    		    sizeof(cei_addr_proposal.state)) >=
    		    sizeof(cei_addr_proposal.state))
    			log_warnx("truncated state name");
    		cei_addr_proposal.next_timeout = addr_proposal->timo.tv_sec;
    		cei_addr_proposal.when = addr_proposal->when;
    		cei_addr_proposal.uptime = addr_proposal->uptime;
    		memcpy(&cei_addr_proposal.addr, &addr_proposal->addr, sizeof(
    		    cei_addr_proposal.addr));
    		memcpy(&cei_addr_proposal.prefix, &addr_proposal->prefix,
    		    sizeof(cei_addr_proposal.prefix));
    		cei_addr_proposal.prefix_len = addr_proposal->prefix_len;
    		cei_addr_proposal.temporary = addr_proposal->temporary;
    		cei_addr_proposal.vltime = addr_proposal->vltime;
    		cei_addr_proposal.pltime = addr_proposal->pltime;
    
    		engine_imsg_compose_frontend(
    		    IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSAL, pid,
    			    &cei_addr_proposal, sizeof(cei_addr_proposal));
    	}
    
    	if (!LIST_EMPTY(&iface->dfr_proposals))
    		engine_imsg_compose_frontend(
    		    IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSALS, pid, NULL, 0);
    
    	LIST_FOREACH(dfr_proposal, &iface->dfr_proposals, entries) {
    		memset(&cei_dfr_proposal, 0, sizeof(cei_dfr_proposal));
    		cei_dfr_proposal.id = dfr_proposal->id;
    		if (strlcpy(cei_dfr_proposal.state,
    		    proposal_state_name(dfr_proposal->state),
    		    sizeof(cei_dfr_proposal.state)) >=
    		    sizeof(cei_dfr_proposal.state))
    			log_warnx("truncated state name");
    		cei_dfr_proposal.next_timeout = dfr_proposal->timo.tv_sec;
    		cei_dfr_proposal.when = dfr_proposal->when;
    		cei_dfr_proposal.uptime = dfr_proposal->uptime;
    		memcpy(&cei_dfr_proposal.addr, &dfr_proposal->addr, sizeof(
    		    cei_dfr_proposal.addr));
    		cei_dfr_proposal.router_lifetime =
    		    dfr_proposal->router_lifetime;
    		if (strlcpy(cei_dfr_proposal.rpref,
    		    rpref_name[dfr_proposal->rpref],
    		    sizeof(cei_dfr_proposal.rpref)) >=
    		    sizeof(cei_dfr_proposal.rpref))
    			log_warnx("truncated router preference");
    		engine_imsg_compose_frontend(
    		    IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSAL, pid,
    			    &cei_dfr_proposal, sizeof(cei_dfr_proposal));
    	}
    
    	if (!LIST_EMPTY(&iface->rdns_proposals))
    		engine_imsg_compose_frontend(
    		    IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSALS, pid, NULL, 0);
    
    	LIST_FOREACH(rdns_proposal, &iface->rdns_proposals, entries) {
    		memset(&cei_rdns_proposal, 0, sizeof(cei_rdns_proposal));
    		cei_rdns_proposal.id = rdns_proposal->id;
    		if (strlcpy(cei_rdns_proposal.state,
    		    proposal_state_name(rdns_proposal->state),
    		    sizeof(cei_rdns_proposal.state)) >=
    		    sizeof(cei_rdns_proposal.state))
    			log_warnx("truncated state name");
    		cei_rdns_proposal.next_timeout = rdns_proposal->timo.tv_sec;
    		cei_rdns_proposal.when = rdns_proposal->when;
    		cei_rdns_proposal.uptime = rdns_proposal->uptime;
    		memcpy(&cei_rdns_proposal.from, &rdns_proposal->from, sizeof(
    		    cei_rdns_proposal.from));
    		cei_rdns_proposal.rdns_count = rdns_proposal->rdns_count;
    		memcpy(&cei_rdns_proposal.rdns,
    		    &rdns_proposal->rdns, sizeof(cei_rdns_proposal.rdns));
    		cei_rdns_proposal.rdns_lifetime =
    		    rdns_proposal->rdns_lifetime;
    		engine_imsg_compose_frontend(
    		    IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSAL, pid,
    			    &cei_rdns_proposal, sizeof(cei_rdns_proposal));
    	}
    }
    
    void
    engine_showinfo_ctl(pid_t pid, uint32_t if_index)
    {
    	struct slaacd_iface			*iface;
    
    	if (if_index == 0) {
    		LIST_FOREACH (iface, &slaacd_interfaces, entries)
    			send_interface_info(iface, pid);
    	} else {
    		if ((iface = get_slaacd_iface_by_id(if_index)) != NULL)
    			send_interface_info(iface, pid);
    	}
    	engine_imsg_compose_frontend(IMSG_CTL_END, pid, NULL, 0);
    }
    
    #endif	/* SMALL */
    
    struct slaacd_iface*
    get_slaacd_iface_by_id(uint32_t if_index)
    {
    	struct slaacd_iface	*iface;
    	LIST_FOREACH (iface, &slaacd_interfaces, entries) {
    		if (iface->if_index == if_index)
    			return (iface);
    	}
    
    	return (NULL);
    }
    
    void
    remove_slaacd_iface(uint32_t if_index)
    {
    	struct slaacd_iface	*iface;
    	struct radv		*ra;
    	struct address_proposal	*addr_proposal;
    	struct dfr_proposal	*dfr_proposal;
    	struct rdns_proposal	*rdns_proposal;
    
    	iface = get_slaacd_iface_by_id(if_index);
    
    	if (iface == NULL)
    		return;
    
    	LIST_REMOVE(iface, entries);
    	while(!LIST_EMPTY(&iface->radvs)) {
    		ra = LIST_FIRST(&iface->radvs);
    		LIST_REMOVE(ra, entries);
    		free_ra(ra);
    	}
    	while(!LIST_EMPTY(&iface->addr_proposals)) {
    		addr_proposal = LIST_FIRST(&iface->addr_proposals);
    		free_address_proposal(addr_proposal);
    	}
    	while(!LIST_EMPTY(&iface->dfr_proposals)) {
    		dfr_proposal = LIST_FIRST(&iface->dfr_proposals);
    		free_dfr_proposal(dfr_proposal);
    	}
    	while(!LIST_EMPTY(&iface->rdns_proposals)) {
    		rdns_proposal = LIST_FIRST(&iface->rdns_proposals);
    		free_rdns_proposal(rdns_proposal);
    	}
    	compose_rdns_proposal(iface->if_index, iface->rdomain);
    	evtimer_del(&iface->timer);
    	free(iface);
    }
    
    void
    free_ra(struct radv *ra)
    {
    	struct radv_prefix	*prefix;
    	struct radv_rdns	*rdns;
    
    	if (ra == NULL)
    		return;
    
    	evtimer_del(&ra->timer);
    
    	while (!LIST_EMPTY(&ra->prefixes)) {
    		prefix = LIST_FIRST(&ra->prefixes);
    		LIST_REMOVE(prefix, entries);
    		free(prefix);
    	}
    
    	while (!LIST_EMPTY(&ra->rdns_servers)) {
    		rdns = LIST_FIRST(&ra->rdns_servers);
    		LIST_REMOVE(rdns, entries);
    		free(rdns);
    	}
    
    	free(ra);
    }
    
    void
    iface_state_transition(struct slaacd_iface *iface, enum if_state new_state)
    {
    	enum if_state		 old_state = iface->state;
    	struct address_proposal	*addr_proposal;
    	struct dfr_proposal	*dfr_proposal;
    	struct rdns_proposal	*rdns_proposal;
    
    	iface->state = new_state;
    
    	switch (new_state) {
    	case IF_DOWN:
    		if (old_state != IF_DOWN) {
    			LIST_FOREACH (addr_proposal, &iface->addr_proposals,
    			    entries)
    				addr_proposal_state_transition(addr_proposal,
    				    PROPOSAL_IF_DOWN);
    			LIST_FOREACH (dfr_proposal, &iface->dfr_proposals,
    			    entries)
    				dfr_proposal_state_transition(dfr_proposal,
    					PROPOSAL_IF_DOWN);
    			LIST_FOREACH (rdns_proposal, &iface->rdns_proposals,
    			    entries)
    				rdns_proposal_state_transition(rdns_proposal,
    				    PROPOSAL_IF_DOWN);
    		}
    
    		/* nothing else to do until interface comes back up */
    		iface->timo.tv_sec = -1;
    		break;
    	case IF_INIT:
    		switch (old_state) {
    		case IF_INIT:
    			iface->probes++;
    			break;
    		case IF_DOWN:
    			LIST_FOREACH (addr_proposal, &iface->addr_proposals,
    			    entries)
    				addr_proposal_state_transition(addr_proposal,
    				    PROPOSAL_WITHDRAWN);
    			LIST_FOREACH (dfr_proposal, &iface->dfr_proposals,
    			    entries)
    				dfr_proposal_state_transition(dfr_proposal,
    				    PROPOSAL_WITHDRAWN);
    			LIST_FOREACH (rdns_proposal, &iface->rdns_proposals,
    			    entries)
    				rdns_proposal_state_transition(rdns_proposal,
    				    PROPOSAL_WITHDRAWN);
    		default:
    			iface->probes = 0;
    		}
    		if (iface->probes < MAX_RTR_SOLICITATIONS) {
    			iface->timo.tv_sec = RTR_SOLICITATION_INTERVAL;
    			request_solicitation(iface);
    		} else
    			/* no router available, stop probing */
    			iface->timo.tv_sec = -1;
    		break;
    	case IF_BOUND:
    		iface->timo.tv_sec = -1;
    		break;
    	}
    
    	if (log_getverbose()) {
    		char	 ifnamebuf[IF_NAMESIZE], *if_name;
    		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 addr_proposal_state_transition(struct address_proposal *addr_proposal,
        enum proposal_state new_state)
    {
    	enum proposal_state	 old_state = addr_proposal->state;
    	struct slaacd_iface	*iface;
    	uint32_t		 lifetime;
    
    	addr_proposal->state = new_state;
    
    	if ((iface = get_slaacd_iface_by_id(addr_proposal->if_index)) == NULL)
    		return;
    
    	switch (addr_proposal->state) {
    	case PROPOSAL_IF_DOWN:
    		if (old_state == PROPOSAL_IF_DOWN) {
    			withdraw_addr(addr_proposal);
    			addr_proposal->timo.tv_sec = -1;
    		} else {
    			addr_proposal->timo.tv_sec =
    			    real_lifetime(&addr_proposal->uptime,
    				addr_proposal->vltime);
    		}
    		break;
    	case PROPOSAL_NOT_CONFIGURED:
    		break;
    	case PROPOSAL_CONFIGURED:
    		lifetime = real_lifetime(&addr_proposal->uptime,
    		    addr_proposal->pltime);
    		if (lifetime == 0)
    			lifetime = real_lifetime(&addr_proposal->uptime,
    			    addr_proposal->vltime);
    		if (lifetime > MAX_RTR_SOLICITATIONS *
    		    (RTR_SOLICITATION_INTERVAL + 1))
    			addr_proposal->timo.tv_sec = lifetime -
    			    MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL;
    		else
    			addr_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL;
    		break;
    	case PROPOSAL_NEARLY_EXPIRED:
    		lifetime = real_lifetime(&addr_proposal->uptime,
    		    addr_proposal->pltime);
    		if (lifetime == 0)
    			lifetime = real_lifetime(&addr_proposal->uptime,
    			    addr_proposal->vltime);
    		if (lifetime > MAX_RTR_SOLICITATIONS *
    		    (RTR_SOLICITATION_INTERVAL + 1))
    			addr_proposal->timo.tv_sec = lifetime -
    			    MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL;
    		else
    			addr_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL;
    		request_solicitation(iface);
    		break;
    	case PROPOSAL_WITHDRAWN:
    		withdraw_addr(addr_proposal);
    		addr_proposal->timo.tv_sec = MAX_RTR_SOLICITATIONS *
    		    RTR_SOLICITATION_INTERVAL;
    		break;
    	case PROPOSAL_DUPLICATED:
    		addr_proposal->timo.tv_sec = 0;
    		break;
    	case PROPOSAL_STALE:
    		addr_proposal->timo.tv_sec = 0; /* remove immediately */
    		break;
    	}
    
    	if (log_getverbose()) {
    		char	 ifnamebuf[IF_NAMESIZE], *if_name;
    		if_name = if_indextoname(addr_proposal->if_index, ifnamebuf);
    		log_debug("%s[%s] %s -> %s, timo: %lld", __func__,
    		    if_name == NULL ? "?" : if_name,
    		    proposal_state_name(old_state),
    		    proposal_state_name(new_state), addr_proposal->timo.tv_sec);
    	}
    
    	if (addr_proposal->timo.tv_sec == -1) {
    		if (evtimer_pending(&addr_proposal->timer, NULL))
    			evtimer_del(&addr_proposal->timer);
    	} else
    		evtimer_add(&addr_proposal->timer, &addr_proposal->timo);
    }
    
    void dfr_proposal_state_transition(struct dfr_proposal *dfr_proposal,
        enum proposal_state new_state)
    {
    	enum proposal_state	 old_state = dfr_proposal->state;
    	struct slaacd_iface	*iface;
    	uint32_t		 lifetime;
    
    	dfr_proposal->state = new_state;
    
    	if ((iface = get_slaacd_iface_by_id(dfr_proposal->if_index)) == NULL)
    		return;
    
    	switch (dfr_proposal->state) {
    	case PROPOSAL_IF_DOWN:
    		if (old_state == PROPOSAL_IF_DOWN) {
    			withdraw_dfr(dfr_proposal);
    			dfr_proposal->timo.tv_sec = -1;
    		} else {
    			dfr_proposal->timo.tv_sec =
    			    real_lifetime(&dfr_proposal->uptime,
    				dfr_proposal->router_lifetime);
    		}
    		break;
    	case PROPOSAL_NOT_CONFIGURED:
    		break;
    	case PROPOSAL_CONFIGURED:
    		lifetime = real_lifetime(&dfr_proposal->uptime,
    		    dfr_proposal->router_lifetime);
    		if (lifetime > MAX_RTR_SOLICITATIONS *
    		    (RTR_SOLICITATION_INTERVAL + 1))
    			dfr_proposal->timo.tv_sec = lifetime -
    			    MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL;
    		else
    			dfr_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL;
    		break;
    	case PROPOSAL_NEARLY_EXPIRED:
    		lifetime = real_lifetime(&dfr_proposal->uptime,
    		    dfr_proposal->router_lifetime);
    		if (lifetime > MAX_RTR_SOLICITATIONS *
    		    (RTR_SOLICITATION_INTERVAL + 1))
    			dfr_proposal->timo.tv_sec = lifetime -
    			    MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL;
    		else
    			dfr_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL;
    		request_solicitation(iface);
    		break;
    	case PROPOSAL_WITHDRAWN:
    		withdraw_dfr(dfr_proposal);
    		dfr_proposal->timo.tv_sec = MAX_RTR_SOLICITATIONS *
    		    RTR_SOLICITATION_INTERVAL;
    		break;
    	case PROPOSAL_STALE:
    		dfr_proposal->timo.tv_sec = 0; /* remove immediately */
    		break;
    	case PROPOSAL_DUPLICATED:
    		fatalx("invalid dfr state: PROPOSAL_DUPLICATED");
    		break;
    	}
    
    	if (log_getverbose()) {
    		char	 ifnamebuf[IF_NAMESIZE], *if_name;
    
    		if_name = if_indextoname(dfr_proposal->if_index, ifnamebuf);
    		log_debug("%s[%s] %s -> %s, timo: %lld", __func__,
    		    if_name == NULL ? "?" : if_name,
    		    proposal_state_name(old_state),
    		    proposal_state_name(new_state), dfr_proposal->timo.tv_sec);
    	}
    
    	if (dfr_proposal->timo.tv_sec == -1) {
    		if (evtimer_pending(&dfr_proposal->timer, NULL))
    			evtimer_del(&dfr_proposal->timer);
    	} else
    		evtimer_add(&dfr_proposal->timer, &dfr_proposal->timo);
    
    }
    
    void rdns_proposal_state_transition(struct rdns_proposal *rdns_proposal,
        enum proposal_state new_state)
    {
    	enum proposal_state	 old_state = rdns_proposal->state;
    	struct slaacd_iface	*iface;
    	uint32_t		 lifetime;
    
    	rdns_proposal->state = new_state;
    
    	if ((iface = get_slaacd_iface_by_id(rdns_proposal->if_index)) == NULL)
    		return;
    
    	switch (rdns_proposal->state) {
    	case PROPOSAL_IF_DOWN:
    		if (old_state == PROPOSAL_IF_DOWN) {
    			withdraw_rdns(rdns_proposal);
    			rdns_proposal->timo.tv_sec = -1;
    		} else {
    			rdns_proposal->timo.tv_sec =
    			    real_lifetime(&rdns_proposal->uptime,
    				rdns_proposal->rdns_lifetime);
    		}
    		break;
    	case PROPOSAL_NOT_CONFIGURED:
    		break;
    	case PROPOSAL_CONFIGURED:
    		lifetime = real_lifetime(&rdns_proposal->uptime,
    		    rdns_proposal->rdns_lifetime);
    		if (lifetime > MAX_RTR_SOLICITATIONS *
    		    (RTR_SOLICITATION_INTERVAL + 1))
    			rdns_proposal->timo.tv_sec = lifetime -
    			    MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL;
    		else
    			rdns_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL;
    		break;
    	case PROPOSAL_NEARLY_EXPIRED:
    		lifetime = real_lifetime(&rdns_proposal->uptime,
    		    rdns_proposal->rdns_lifetime);
    		if (lifetime > MAX_RTR_SOLICITATIONS *
    		    (RTR_SOLICITATION_INTERVAL + 1))
    			rdns_proposal->timo.tv_sec = lifetime -
    			    MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL;
    		else
    			rdns_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL;
    		request_solicitation(iface);
    		break;
    	case PROPOSAL_WITHDRAWN:
    		withdraw_rdns(rdns_proposal);
    		rdns_proposal->timo.tv_sec = MAX_RTR_SOLICITATIONS *
    		    RTR_SOLICITATION_INTERVAL;
    		break;
    	case PROPOSAL_STALE:
    		rdns_proposal->timo.tv_sec = 0; /* remove immediately */
    		break;
    	case PROPOSAL_DUPLICATED:
    		fatalx("invalid rdns state: PROPOSAL_DUPLICATED");
    		break;
    	}
    
    	if (log_getverbose()) {
    		char	 ifnamebuf[IF_NAMESIZE], *if_name;
    
    		if_name = if_indextoname(rdns_proposal->if_index, ifnamebuf);
    		log_debug("%s[%s] %s -> %s, timo: %lld", __func__,
    		    if_name == NULL ? "?" : if_name,
    		    proposal_state_name(old_state),
    		    proposal_state_name(new_state), rdns_proposal->timo.tv_sec);
    	}
    
    	if (rdns_proposal->timo.tv_sec == -1) {
    		if (evtimer_pending(&rdns_proposal->timer, NULL))
    			evtimer_del(&rdns_proposal->timer);
    	} else
    		evtimer_add(&rdns_proposal->timer, &rdns_proposal->timo);
    }
    
    void
    request_solicitation(struct slaacd_iface *iface)
    {
    	struct timespec	now, diff, sol_delay = {RTR_SOLICITATION_INTERVAL, 0};
    
    	clock_gettime(CLOCK_MONOTONIC, &now);
    	timespecsub(&now, &iface->last_sol, &diff);
    	if (timespeccmp(&diff, &sol_delay, <)) {
    		log_debug("last solicitation less than %d seconds ago",
    		    RTR_SOLICITATION_INTERVAL);
    		return;
    	}
    
    	iface->last_sol = now;
    	engine_imsg_compose_frontend(IMSG_CTL_SEND_SOLICITATION, 0,
    	    &iface->if_index, sizeof(iface->if_index));
    }
    
    void
    engine_update_iface(struct imsg_ifinfo *imsg_ifinfo)
    {
    	struct slaacd_iface	*iface;
    	int			 need_refresh = 0;
    
    	iface = get_slaacd_iface_by_id(imsg_ifinfo->if_index);
    	if (iface == NULL) {
    		if ((iface = calloc(1, sizeof(*iface))) == NULL)
    			fatal("calloc");
    		iface->state = IF_DOWN;
    		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->autoconf = imsg_ifinfo->autoconf;
    		iface->temporary = imsg_ifinfo->temporary;
    		iface->soii = imsg_ifinfo->soii;
    		memcpy(&iface->hw_address, &imsg_ifinfo->hw_address,
    		    sizeof(struct ether_addr));
    		memcpy(&iface->ll_address, &imsg_ifinfo->ll_address,
    		    sizeof(struct sockaddr_in6));
    		memcpy(iface->soiikey, imsg_ifinfo->soiikey,
    		    sizeof(iface->soiikey));
    		LIST_INIT(&iface->radvs);
    		LIST_INSERT_HEAD(&slaacd_interfaces, iface, entries);
    		LIST_INIT(&iface->addr_proposals);
    		LIST_INIT(&iface->dfr_proposals);
    		LIST_INIT(&iface->rdns_proposals);
    		need_refresh = 1;
    	} else {
    		memcpy(&iface->ll_address, &imsg_ifinfo->ll_address,
    		    sizeof(struct sockaddr_in6));
    
    		if (iface->autoconf != imsg_ifinfo->autoconf) {
    			iface->autoconf = imsg_ifinfo->autoconf;
    			need_refresh = 1;
    		}
    
    		if (iface->temporary != imsg_ifinfo->temporary) {
    			iface->temporary = imsg_ifinfo->temporary;
    			need_refresh = 1;
    		}
    
    		if (iface->soii != imsg_ifinfo->soii) {
    			iface->soii = imsg_ifinfo->soii;
    			need_refresh = 1;
    		}
    
    		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 (memcmp(iface->soiikey, imsg_ifinfo->soiikey,
    		    sizeof(iface->soiikey)) != 0) {
    			memcpy(iface->soiikey, imsg_ifinfo->soiikey,
    			    sizeof(iface->soiikey));
    			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))
    		iface_state_transition(iface, IF_INIT);
    
    	else
    		iface_state_transition(iface, IF_DOWN);
    }
    
    void
    parse_ra(struct slaacd_iface *iface, struct imsg_ra *ra)
    {
    	struct icmp6_hdr	*icmp6_hdr;
    	struct nd_router_advert	*nd_ra;
    	struct radv		*radv;
    	struct radv_prefix	*prefix;
    	struct radv_rdns	*rdns;
    	ssize_t			 len = ra->len;
    	const char		*hbuf;
    	uint8_t			*p;
    
    #ifndef	SMALL
    	if (log_getverbose() > 1)
    		debug_log_ra(ra);
    #endif	/* SMALL */
    
    	hbuf = sin6_to_str(&ra->from);
    	if ((size_t)len < sizeof(struct icmp6_hdr)) {
    		log_warnx("received too short message (%ld) from %s", len,
    		    hbuf);
    		return;
    	}
    
    	p = ra->packet;
    	icmp6_hdr = (struct icmp6_hdr *)p;
    	if (icmp6_hdr->icmp6_type != ND_ROUTER_ADVERT)
    		return;
    
    	if (!IN6_IS_ADDR_LINKLOCAL(&ra->from.sin6_addr)) {
    		log_debug("RA from non link local address %s", hbuf);
    		return;
    	}
    
    	if ((size_t)len < sizeof(struct nd_router_advert)) {
    		log_warnx("received too short message (%ld) from %s", len,
    		    hbuf);
    		return;
    	}
    
    	if ((radv = calloc(1, sizeof(*radv))) == NULL)
    		fatal("calloc");
    
    	LIST_INIT(&radv->prefixes);
    	LIST_INIT(&radv->rdns_servers);
    
    	radv->min_lifetime = UINT32_MAX;
    
    	nd_ra = (struct nd_router_advert *)p;
    	len -= sizeof(struct nd_router_advert);
    	p += sizeof(struct nd_router_advert);
    
    	log_debug("ICMPv6 type(%d), code(%d) from %s of length %ld",
    	    nd_ra->nd_ra_type, nd_ra->nd_ra_code, hbuf, len);
    
    	if (nd_ra->nd_ra_code != 0) {
    		log_warnx("invalid ICMPv6 code (%d) from %s", nd_ra->nd_ra_code,
    		    hbuf);
    		goto err;
    	}
    
    	memcpy(&radv->from, &ra->from, sizeof(ra->from));
    
    	if (clock_gettime(CLOCK_REALTIME, &radv->when))
    		fatal("clock_gettime");
    	if (clock_gettime(CLOCK_MONOTONIC, &radv->uptime))
    		fatal("clock_gettime");
    
    	radv->curhoplimit = nd_ra->nd_ra_curhoplimit;
    	radv->managed = nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED;
    	radv->other = nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER;
    
    	switch (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_RTPREF_MASK) {
    	case ND_RA_FLAG_RTPREF_HIGH:
    		radv->rpref=HIGH;
    		break;
    	case ND_RA_FLAG_RTPREF_LOW:
    		radv->rpref=LOW;
    		break;
    	case ND_RA_FLAG_RTPREF_MEDIUM:
    		/* fallthrough */
    	default:
    		radv->rpref=MEDIUM;
    		break;
    	}
    	radv->router_lifetime = ntohs(nd_ra->nd_ra_router_lifetime);
    	if (radv->router_lifetime != 0)
    		radv->min_lifetime = radv->router_lifetime;
    	radv->reachable_time = ntohl(nd_ra->nd_ra_reachable);
    	radv->retrans_time = ntohl(nd_ra->nd_ra_retransmit);
    
    	while ((size_t)len >= sizeof(struct nd_opt_hdr)) {
    		struct nd_opt_hdr *nd_opt_hdr = (struct nd_opt_hdr *)p;
    		struct nd_opt_prefix_info *prf;
    		struct nd_opt_rdnss *rdnss;
    		struct nd_opt_mtu *mtu;
    		struct in6_addr *in6;
    		int i;
    
    		if (nd_opt_hdr->nd_opt_len == 0)
    			goto err;
    		len -= sizeof(struct nd_opt_hdr);
    		p += sizeof(struct nd_opt_hdr);
    
    		if (nd_opt_hdr->nd_opt_len * 8 - 2 > len) {
    			log_warnx("invalid option len: %u > %ld",
    			    nd_opt_hdr->nd_opt_len, len);
    			goto err;
    		}
    
    		switch (nd_opt_hdr->nd_opt_type) {
    		case ND_OPT_PREFIX_INFORMATION:
    			if (nd_opt_hdr->nd_opt_len != 4) {
    				log_warnx("invalid ND_OPT_PREFIX_INFORMATION: "
    				   "len != 4");
    				goto err;
    			}
    
    			if ((prefix = calloc(1, sizeof(*prefix))) == NULL)
    				fatal("calloc");
    
    			prf = (struct nd_opt_prefix_info*) nd_opt_hdr;
    			prefix->prefix = prf->nd_opt_pi_prefix;
    			prefix->prefix_len = prf->nd_opt_pi_prefix_len;
    			prefix->onlink = prf->nd_opt_pi_flags_reserved &
    			    ND_OPT_PI_FLAG_ONLINK;
    			prefix->autonomous = prf->nd_opt_pi_flags_reserved &
    			    ND_OPT_PI_FLAG_AUTO;
    			prefix->vltime = ntohl(prf->nd_opt_pi_valid_time);
    			prefix->pltime = ntohl(prf->nd_opt_pi_preferred_time);
    			if (radv->min_lifetime > prefix->pltime)
    				radv->min_lifetime = prefix->pltime;
    
    			LIST_INSERT_HEAD(&radv->prefixes, prefix, entries);
    
    			break;
    
    		case ND_OPT_RDNSS:
    			if (nd_opt_hdr->nd_opt_len  < 3) {
    				log_warnx("invalid ND_OPT_RDNSS: len < 24");
    				goto err;
    			}
    
    			if ((nd_opt_hdr->nd_opt_len - 1) % 2 != 0) {
    				log_warnx("invalid ND_OPT_RDNSS: length with"
    				    "out header is not multiply of 16: %d",
    				    (nd_opt_hdr->nd_opt_len - 1) * 8);
    				goto err;
    			}
    
    			rdnss = (struct nd_opt_rdnss*) nd_opt_hdr;
    
    			radv->rdns_lifetime = ntohl(
    			    rdnss->nd_opt_rdnss_lifetime);
    			if (radv->min_lifetime > radv->rdns_lifetime)
    				radv->min_lifetime = radv->rdns_lifetime;
    
    			in6 = (struct in6_addr*) (p + 6);
    			for (i=0; i < (nd_opt_hdr->nd_opt_len - 1)/2; i++,
    			    in6++) {
    				if ((rdns = calloc(1, sizeof(*rdns))) == NULL)
    					fatal("calloc");
    				memcpy(&rdns->rdns, in6, sizeof(rdns->rdns));
    				LIST_INSERT_HEAD(&radv->rdns_servers, rdns,
    				    entries);
    			}
    			break;
    		case ND_OPT_MTU:
    			if (nd_opt_hdr->nd_opt_len != 1) {
    				log_warnx("invalid ND_OPT_MTU: len != 1");
    				goto err;
    			}
    			mtu = (struct nd_opt_mtu*) nd_opt_hdr;
    			radv->mtu = ntohl(mtu->nd_opt_mtu_mtu);
    
    			/* path MTU cannot be less than IPV6_MMTU */
    			if (radv->mtu < IPV6_MMTU) {
    				radv->mtu = 0;
    				log_warnx("invalid advertised MTU");
    			}
    
    			break;
    		case ND_OPT_DNSSL:
    		case ND_OPT_REDIRECTED_HEADER:
    		case ND_OPT_SOURCE_LINKADDR:
    		case ND_OPT_TARGET_LINKADDR:
    		case ND_OPT_ROUTE_INFO:
    #if 0
    			log_debug("\tOption: %u (len: %u) not implemented",
    			    nd_opt_hdr->nd_opt_type, nd_opt_hdr->nd_opt_len *
    			    8);
    #endif
    			break;
    		default:
    			log_debug("\t\tUNKNOWN: %d", nd_opt_hdr->nd_opt_type);
    			break;
    
    		}
    		len -= nd_opt_hdr->nd_opt_len * 8 - 2;
    		p += nd_opt_hdr->nd_opt_len * 8 - 2;
    	}
    	update_iface_ra(iface, radv);
    	return;
    
    err:
    	free_ra(radv);
    }
    
    void
    gen_addr(struct slaacd_iface *iface, struct radv_prefix *prefix, struct
        address_proposal *addr_proposal, int temporary)
    {
    	SHA2_CTX ctx;
    	struct in6_addr	iid;
    	int i;
    	u_int8_t digest[SHA512_DIGEST_LENGTH];
    
    	memset(&iid, 0, sizeof(iid));
    
    	/* from in6_ifadd() in nd6_rtr.c */
    	/* XXX from in6.h, guarded by #ifdef _KERNEL   XXX nonstandard */
    #define s6_addr32 __u6_addr.__u6_addr32
    
    	in6_prefixlen2mask(&addr_proposal->mask, addr_proposal->prefix_len);
    
    	memset(&addr_proposal->addr, 0, sizeof(addr_proposal->addr));
    
    	addr_proposal->addr.sin6_family = AF_INET6;
    	addr_proposal->addr.sin6_len = sizeof(addr_proposal->addr);
    
    	memcpy(&addr_proposal->addr.sin6_addr, &prefix->prefix,
    	    sizeof(addr_proposal->addr.sin6_addr));
    
    	for (i = 0; i < 4; i++)
    		addr_proposal->addr.sin6_addr.s6_addr32[i] &=
    		    addr_proposal->mask.s6_addr32[i];
    
    	if (temporary) {
    		arc4random_buf(&iid.s6_addr, sizeof(iid.s6_addr));
    	} else if (iface->soii) {
    		SHA512Init(&ctx);
    		SHA512Update(&ctx, &prefix->prefix,
    		    sizeof(prefix->prefix));
    		SHA512Update(&ctx, &iface->hw_address,
    		    sizeof(iface->hw_address));
    		SHA512Update(&ctx, &prefix->dad_counter,
    		    sizeof(prefix->dad_counter));
    		SHA512Update(&ctx, addr_proposal->soiikey,
    		    sizeof(addr_proposal->soiikey));
    		SHA512Final(digest, &ctx);
    
    		memcpy(&iid.s6_addr, digest + (sizeof(digest) -
    		    sizeof(iid.s6_addr)), sizeof(iid.s6_addr));
    	} else {
    		/* This is safe, because we have a 64 prefix len */
    		memcpy(&iid.s6_addr, &iface->ll_address.sin6_addr,
    		    sizeof(iid.s6_addr));
    	}
    
    	for (i = 0; i < 4; i++)
    		addr_proposal->addr.sin6_addr.s6_addr32[i] |=
    		    (iid.s6_addr32[i] & ~addr_proposal->mask.s6_addr32[i]);
    #undef s6_addr32
    }
    
    /* from sys/netinet6/in6.c */
    void
    in6_prefixlen2mask(struct in6_addr *maskp, int len)
    {
    	u_char maskarray[8] = {0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff};
    	int bytelen, bitlen, i;
    
    	if (0 > len || len > 128)
    		fatalx("%s: invalid prefix length(%d)\n", __func__, len);
    
    	bzero(maskp, sizeof(*maskp));
    	bytelen = len / 8;
    	bitlen = len % 8;
    	for (i = 0; i < bytelen; i++)
    		maskp->s6_addr[i] = 0xff;
    	/* len == 128 is ok because bitlen == 0 then */
    	if (bitlen)
    		maskp->s6_addr[bytelen] = maskarray[bitlen - 1];
    }
    
    #ifndef	SMALL
    /* from kame via ifconfig, where it's called prefix() */
    int
    in6_mask2prefixlen(struct in6_addr *in6)
    {
    	u_char *nam = (u_char *)in6;
    	int byte, bit, plen = 0, size = sizeof(struct in6_addr);
    
    	for (byte = 0; byte < size; byte++, plen += 8)
    		if (nam[byte] != 0xff)
    			break;
    	if (byte == size)
    		return (plen);
    	for (bit = 7; bit != 0; bit--, plen++)
    		if (!(nam[byte] & (1 << bit)))
    			break;
    	for (; bit != 0; bit--)
    		if (nam[byte] & (1 << bit))
    			return (0);
    	byte++;
    	for (; byte < size; byte++)
    		if (nam[byte])
    			return (0);
    	return (plen);
    }
    
    void
    debug_log_ra(struct imsg_ra *ra)
    {
    	struct nd_router_advert	*nd_ra;
    	ssize_t			 len = ra->len;
    	char			 ntopbuf[INET6_ADDRSTRLEN];
    	const char		*hbuf;
    	uint8_t			*p;
    
    	hbuf = sin6_to_str(&ra->from);
    
    	if (!IN6_IS_ADDR_LINKLOCAL(&ra->from.sin6_addr)) {
    		log_warnx("RA from non link local address %s", hbuf);
    		return;
    	}
    
    	if ((size_t)len < sizeof(struct nd_router_advert)) {
    		log_warnx("received too short message (%ld) from %s", len,
    		    hbuf);
    		return;
    	}
    
    	p = ra->packet;
    	nd_ra = (struct nd_router_advert *)p;
    	len -= sizeof(struct nd_router_advert);
    	p += sizeof(struct nd_router_advert);
    
    	log_debug("ICMPv6 type(%d), code(%d) from %s of length %ld",
    	    nd_ra->nd_ra_type, nd_ra->nd_ra_code, hbuf, len);
    
    	if (nd_ra->nd_ra_type != ND_ROUTER_ADVERT) {
    		log_warnx("invalid ICMPv6 type (%d) from %s", nd_ra->nd_ra_type,
    		    hbuf);
    		return;
    	}
    
    	if (nd_ra->nd_ra_code != 0) {
    		log_warnx("invalid ICMPv6 code (%d) from %s", nd_ra->nd_ra_code,
    		    hbuf);
    		return;
    	}
    
    	log_debug("---");
    	log_debug("RA from %s", hbuf);
    	log_debug("\tCur Hop Limit: %u", nd_ra->nd_ra_curhoplimit);
    	log_debug("\tManaged address configuration: %d",
    	    (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) ? 1 : 0);
    	log_debug("\tOther configuration: %d",
    	    (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) ? 1 : 0);
    	switch (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_RTPREF_MASK) {
    	case ND_RA_FLAG_RTPREF_HIGH:
    		log_debug("\tRouter Preference: high");
    		break;
    	case ND_RA_FLAG_RTPREF_MEDIUM:
    		log_debug("\tRouter Preference: medium");
    		break;
    	case ND_RA_FLAG_RTPREF_LOW:
    		log_debug("\tRouter Preference: low");
    		break;
    	case ND_RA_FLAG_RTPREF_RSV:
    		log_debug("\tRouter Preference: reserved");
    		break;
    	}
    	log_debug("\tRouter Lifetime: %hds",
    	    ntohs(nd_ra->nd_ra_router_lifetime));
    	log_debug("\tReachable Time: %ums", ntohl(nd_ra->nd_ra_reachable));
    	log_debug("\tRetrans Timer: %ums", ntohl(nd_ra->nd_ra_retransmit));
    
    	while ((size_t)len >= sizeof(struct nd_opt_hdr)) {
    		struct nd_opt_hdr *nd_opt_hdr = (struct nd_opt_hdr *)p;
    		struct nd_opt_mtu *mtu;
    		struct nd_opt_prefix_info *prf;
    		struct nd_opt_rdnss *rdnss;
    		struct in6_addr *in6;
    		int i;
    
    		if (nd_opt_hdr->nd_opt_len == 0)
    			return;
    		len -= sizeof(struct nd_opt_hdr);
    		p += sizeof(struct nd_opt_hdr);
    		if (nd_opt_hdr->nd_opt_len * 8 - 2 > len) {
    			log_warnx("invalid option len: %u > %ld",
    			    nd_opt_hdr->nd_opt_len, len);
    			return;
    		}
    		log_debug("\tOption: %u (len: %u)", nd_opt_hdr->nd_opt_type,
    		    nd_opt_hdr->nd_opt_len * 8);
    		switch (nd_opt_hdr->nd_opt_type) {
    		case ND_OPT_SOURCE_LINKADDR:
    			if (nd_opt_hdr->nd_opt_len == 1)
    				log_debug("\t\tND_OPT_SOURCE_LINKADDR: "
    				    "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
    				    p[0], p[1], p[2], p[3], p[4], p[5], p[6],
    				    p[7]);
    			else
    				log_debug("\t\tND_OPT_SOURCE_LINKADDR");
    			break;
    		case ND_OPT_TARGET_LINKADDR:
    			if (nd_opt_hdr->nd_opt_len == 1)
    				log_debug("\t\tND_OPT_TARGET_LINKADDR: "
    				    "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
    				    p[0], p[1], p[2], p[3], p[4], p[5], p[6],
    				    p[7]);
    			else
    				log_debug("\t\tND_OPT_TARGET_LINKADDR");
    			break;
    		case ND_OPT_PREFIX_INFORMATION:
    			if (nd_opt_hdr->nd_opt_len != 4) {
    				log_warnx("invalid ND_OPT_PREFIX_INFORMATION: "
    				   "len != 4");
    				return;
    			}
    			prf = (struct nd_opt_prefix_info*) nd_opt_hdr;
    
    			log_debug("\t\tND_OPT_PREFIX_INFORMATION: %s/%u",
    			    inet_ntop(AF_INET6, &prf->nd_opt_pi_prefix,
    			    ntopbuf, INET6_ADDRSTRLEN),
    			    prf->nd_opt_pi_prefix_len);
    			log_debug("\t\t\tOn-link: %d",
    			    prf->nd_opt_pi_flags_reserved &
    			    ND_OPT_PI_FLAG_ONLINK ? 1:0);
    			log_debug("\t\t\tAutonomous address-configuration: %d",
    			    prf->nd_opt_pi_flags_reserved &
    			    ND_OPT_PI_FLAG_AUTO ? 1 : 0);
    			log_debug("\t\t\tvltime: %u",
    			    ntohl(prf->nd_opt_pi_valid_time));
    			log_debug("\t\t\tpltime: %u",
    			    ntohl(prf->nd_opt_pi_preferred_time));
    			break;
    		case ND_OPT_REDIRECTED_HEADER:
    			log_debug("\t\tND_OPT_REDIRECTED_HEADER");
    			break;
    		case ND_OPT_MTU:
    			if (nd_opt_hdr->nd_opt_len != 1) {
    				log_warnx("invalid ND_OPT_MTU: len != 1");
    				return;
    			}
    			mtu = (struct nd_opt_mtu*) nd_opt_hdr;
    			log_debug("\t\tND_OPT_MTU: %u",
    			    ntohl(mtu->nd_opt_mtu_mtu));
    			break;
    		case ND_OPT_ROUTE_INFO:
    			log_debug("\t\tND_OPT_ROUTE_INFO");
    			break;
    		case ND_OPT_RDNSS:
    			if (nd_opt_hdr->nd_opt_len  < 3) {
    				log_warnx("invalid ND_OPT_RDNSS: len < 24");
    				return;
    			}
    			if ((nd_opt_hdr->nd_opt_len - 1) % 2 != 0) {
    				log_warnx("invalid ND_OPT_RDNSS: length with"
    				    "out header is not multiply of 16: %d",
    				    (nd_opt_hdr->nd_opt_len - 1) * 8);
    				return;
    			}
    			rdnss = (struct nd_opt_rdnss*) nd_opt_hdr;
    			log_debug("\t\tND_OPT_RDNSS: lifetime: %u", ntohl(
    			    rdnss->nd_opt_rdnss_lifetime));
    			in6 = (struct in6_addr*) (p + 6);
    			for (i=0; i < (nd_opt_hdr->nd_opt_len - 1)/2; i++,
    			    in6++) {
    				log_debug("\t\t\t%s", inet_ntop(AF_INET6, in6,
    				    ntopbuf, INET6_ADDRSTRLEN));
    			}
    			break;
    		default:
    			log_debug("\t\tUNKNOWN: %d", nd_opt_hdr->nd_opt_type);
    			break;
    
    		}
    		len -= nd_opt_hdr->nd_opt_len * 8 - 2;
    		p += nd_opt_hdr->nd_opt_len * 8 - 2;
    	}
    }
    #endif	/* SMALL */
    
    void update_iface_ra(struct slaacd_iface *iface, struct radv *ra)
    {
    	struct radv		*old_ra;
    	struct radv_prefix	*prefix;
    
    	if ((old_ra = find_ra(iface, &ra->from)) == NULL)
    		LIST_INSERT_HEAD(&iface->radvs, ra, entries);
    	else {
    		LIST_REPLACE(old_ra, ra, entries);
    		merge_dad_couters(old_ra, ra);
    		free_ra(old_ra);
    	}
    
    	update_iface_ra_dfr(iface, ra);
    
    	LIST_FOREACH(prefix, &ra->prefixes, entries) {
    		if (!prefix->autonomous || prefix->vltime == 0 ||
    		    prefix->pltime > prefix->vltime ||
    		    IN6_IS_ADDR_LINKLOCAL(&prefix->prefix))
    			continue;
    		update_iface_ra_prefix(iface, ra, prefix);
    	}
    
    	update_iface_ra_rdns(iface, ra);
    }
    
    void
    update_iface_ra_dfr(struct slaacd_iface *iface, struct radv *ra)
    {
    	struct dfr_proposal	*dfr_proposal;
    
    	dfr_proposal = find_dfr_proposal_by_gw(iface, &ra->from);
    
    	if (ra->router_lifetime == 0) {
    		free_dfr_proposal(dfr_proposal);
    		return;
    	}
    
    	if (!dfr_proposal) {
    		/* new proposal */
    		gen_dfr_proposal(iface, ra);
    		return;
    	}
    
    	dfr_proposal->when = ra->when;
    	dfr_proposal->uptime = ra->uptime;
    	dfr_proposal->router_lifetime = ra->router_lifetime;
    
    	log_debug("%s, dfr state: %s, rl: %d", __func__,
    	    proposal_state_name(dfr_proposal->state),
    	    real_lifetime(&dfr_proposal->uptime,
    	    dfr_proposal->router_lifetime));
    
    	switch (dfr_proposal->state) {
    	case PROPOSAL_CONFIGURED:
    	case PROPOSAL_NEARLY_EXPIRED:
    		/* routes do not expire in the kernel, update timeout */
    		dfr_proposal_state_transition(dfr_proposal,
    		    PROPOSAL_CONFIGURED);
    		break;
    	case PROPOSAL_IF_DOWN:
    	case PROPOSAL_WITHDRAWN:
    		log_debug("updating dfr");
    		configure_dfr(dfr_proposal);
    		break;
    	default:
    		log_debug("%s: iface %d: %s", __func__, iface->if_index,
    		    sin6_to_str(&dfr_proposal->addr));
    		break;
    	}
    }
    
    void
    update_iface_ra_prefix(struct slaacd_iface *iface, struct radv *ra,
        struct radv_prefix *prefix)
    {
    	struct address_proposal	*addr_proposal;
    	uint32_t		 pltime, vltime;
    	int			 found, found_temporary, duplicate_found;
    
    	found = found_temporary = duplicate_found = 0;
    
    	if (!!iface->autoconf != !!iface->temporary) {
    		struct address_proposal	*tmp;
    		/*
    		 * If only the autoconf or temporary flag is set, check if we
    		 * have the "other kind" of address configured and delete it.
    		 */
    		LIST_FOREACH_SAFE (addr_proposal, &iface->addr_proposals,
    		    entries, tmp) {
    			if ((!addr_proposal->temporary && !iface->autoconf) ||
    			    (addr_proposal->temporary && !iface->temporary))
    				free_address_proposal(addr_proposal);
    		}
    	}
    
    	LIST_FOREACH(addr_proposal, &iface->addr_proposals, entries) {
    		if (prefix->prefix_len == addr_proposal-> prefix_len &&
    		    memcmp(&prefix->prefix, &addr_proposal->prefix,
    		    sizeof(struct in6_addr)) != 0)
    			continue;
    
    		if (memcmp(&addr_proposal->hw_address,
    		    &iface->hw_address,
    		    sizeof(addr_proposal->hw_address)) != 0)
    			continue;
    
    		if (memcmp(&addr_proposal->soiikey, &iface->soiikey,
    		    sizeof(addr_proposal->soiikey)) != 0)
    			continue;
    
    		if (addr_proposal->state == PROPOSAL_DUPLICATED) {
    			duplicate_found = 1;
    			continue;
    		}
    
    		vltime = prefix->vltime;
    
    		if (addr_proposal->temporary) {
    			struct timespec	now;
    			int64_t		ltime, mtime;
    
    			if (clock_gettime(CLOCK_MONOTONIC, &now))
    				fatal("clock_gettime");
    
    			mtime = addr_proposal->created.tv_sec +
    			    PRIV_PREFERRED_LIFETIME -
    			    addr_proposal->desync_factor;
    
    			ltime = MINIMUM(mtime, now.tv_sec + prefix->pltime) -
    			    now.tv_sec;
    
    			pltime = ltime > 0 ? ltime : 0;
    
    			ltime = MINIMUM(addr_proposal->created.tv_sec +
    			    PRIV_VALID_LIFETIME, now.tv_sec + vltime) -
    			    now.tv_sec;
    			vltime = ltime > 0 ? ltime : 0;
    
    			if ((mtime - now.tv_sec) > PRIV_REGEN_ADVANCE)
    				found_temporary = 1;
    		} else {
    			pltime = prefix->pltime;
    			found = 1;
    		}
    
    		addr_proposal->from = ra->from;
    		addr_proposal->when = ra->when;
    		addr_proposal->uptime = ra->uptime;
    
    		addr_proposal->vltime = vltime;
    		addr_proposal->pltime = pltime;
    
    		if (ra->mtu == iface->cur_mtu)
    			addr_proposal->mtu = 0;
    		else {
    			addr_proposal->mtu = ra->mtu;
    			iface->cur_mtu = ra->mtu;
    		}
    
    		log_debug("%s, addr state: %s", __func__,
    		    proposal_state_name(addr_proposal->state));
    
    		switch (addr_proposal->state) {
    		case PROPOSAL_CONFIGURED:
    		case PROPOSAL_NEARLY_EXPIRED:
    		case PROPOSAL_IF_DOWN:
    		case PROPOSAL_WITHDRAWN:
    			log_debug("updating address");
    			configure_address(addr_proposal);
    			break;
    		default:
    			log_debug("%s: iface %d: %s", __func__, iface->if_index,
    			    sin6_to_str(&addr_proposal->addr));
    			break;
    		}
    	}
    
    	if (!found && iface->autoconf && duplicate_found && iface->soii) {
    		prefix->dad_counter++;
    		log_debug("%s dad_counter: %d", __func__, prefix->dad_counter);
    		gen_address_proposal(iface, ra, prefix, 0);
    	} else if (!found  && iface->autoconf && (iface->soii ||
    	    prefix->prefix_len <= 64))
    		/* new proposal */
    		gen_address_proposal(iface, ra, prefix, 0);
    
    	/* temporary addresses do not depend on eui64 */
    	if (!found_temporary && iface->temporary) {
    		if (prefix->pltime >= PRIV_REGEN_ADVANCE) {
    			/* new temporary proposal */
    			gen_address_proposal(iface, ra, prefix, 1);
    		} else if (prefix->pltime > 0) {
    			log_warnx("%s: pltime from %s is too small: %d < %d; "
    			    "not generating temporary address", __func__,
    			    sin6_to_str(&ra->from), prefix->pltime,
    			    PRIV_REGEN_ADVANCE);
    		}
    	}
    }
    
    void
    update_iface_ra_rdns(struct slaacd_iface *iface, struct radv *ra)
    {
    	struct rdns_proposal	*rdns_proposal;
    	struct radv_rdns	*radv_rdns;
    	struct in6_addr		 rdns[MAX_RDNS_COUNT];
    	int			 rdns_count;
    
    	rdns_proposal = find_rdns_proposal_by_gw(iface, &ra->from);
    
    	if (!rdns_proposal) {
    		/* new proposal */
    		if (!LIST_EMPTY(&ra->rdns_servers))
    			gen_rdns_proposal(iface, ra);
    		return;
    	}
    
    	rdns_count = 0;
    	memset(&rdns, 0, sizeof(rdns));
    	LIST_FOREACH(radv_rdns, &ra->rdns_servers, entries) {
    		memcpy(&rdns[rdns_count++],
    		    &radv_rdns->rdns, sizeof(struct in6_addr));
    		if (rdns_count == MAX_RDNS_COUNT)
    			break;
    	}
    
    	if (rdns_count == 0) {
    		free_rdns_proposal(rdns_proposal);
    		return;
    	}
    
    	if (rdns_proposal->rdns_count != rdns_count ||
    	    memcmp(&rdns_proposal->rdns, &rdns, sizeof(rdns)) != 0) {
    		memcpy(&rdns_proposal->rdns, &rdns, sizeof(rdns));
    		rdns_proposal->rdns_count = rdns_count;
    		rdns_proposal->state = PROPOSAL_NOT_CONFIGURED;
    	}
    	rdns_proposal->when = ra->when;
    	rdns_proposal->uptime = ra->uptime;
    	rdns_proposal->rdns_lifetime = ra->rdns_lifetime;
    
    	log_debug("%s, rdns state: %s, rl: %d", __func__,
    	    proposal_state_name(rdns_proposal->state),
    	    real_lifetime(&rdns_proposal->uptime,
    	    rdns_proposal->rdns_lifetime));
    
    	switch (rdns_proposal->state) {
    	case PROPOSAL_CONFIGURED:
    	case PROPOSAL_NEARLY_EXPIRED:
    		/* rdns are not expired by the kernel, update timeout */
    		rdns_proposal_state_transition(rdns_proposal,
    		    PROPOSAL_CONFIGURED);
    		break;
    	case PROPOSAL_IF_DOWN:
    	case PROPOSAL_WITHDRAWN:
    	case PROPOSAL_NOT_CONFIGURED:
    		log_debug("updating rdns");
    		rdns_proposal_state_transition(rdns_proposal,
    		    PROPOSAL_CONFIGURED);
    		compose_rdns_proposal(rdns_proposal->if_index,
    		    rdns_proposal->rdomain);
    		break;
    	default:
    		log_debug("%s: iface %d: %s", __func__, iface->if_index,
    		    sin6_to_str(&rdns_proposal->from));
    		break;
    	}
    }
    
    
    void
    configure_address(struct address_proposal *addr_proposal)
    {
    	struct imsg_configure_address	 address;
    	struct slaacd_iface		*iface;
    
    	log_debug("%s: %d", __func__, addr_proposal->if_index);
    
    	address.if_index = addr_proposal->if_index;
    	memcpy(&address.addr, &addr_proposal->addr, sizeof(address.addr));
    	memcpy(&address.gw, &addr_proposal->from, sizeof(address.gw));
    	memcpy(&address.mask, &addr_proposal->mask, sizeof(address.mask));
    	address.vltime = addr_proposal->vltime;
    	address.pltime = addr_proposal->pltime;
    	address.temporary = addr_proposal->temporary;
    	address.mtu = addr_proposal->mtu;
    
    	engine_imsg_compose_main(IMSG_CONFIGURE_ADDRESS, 0, &address,
    	    sizeof(address));
    
    	if ((iface = get_slaacd_iface_by_id(addr_proposal->if_index)) != NULL)
    		iface_state_transition(iface, IF_BOUND);
    	addr_proposal_state_transition(addr_proposal, PROPOSAL_CONFIGURED);
    }
    
    void
    gen_address_proposal(struct slaacd_iface *iface, struct radv *ra, struct
        radv_prefix *prefix, int temporary)
    {
    	struct address_proposal	*addr_proposal;
    	const char		*hbuf;
    
    	if ((addr_proposal = calloc(1, sizeof(*addr_proposal))) == NULL)
    		fatal("calloc");
    	addr_proposal->id = ++proposal_id;
    	evtimer_set(&addr_proposal->timer, address_proposal_timeout,
    	    addr_proposal);
    	addr_proposal->timo.tv_sec = 1;
    	addr_proposal->timo.tv_usec = arc4random_uniform(1000000);
    	addr_proposal->state = PROPOSAL_NOT_CONFIGURED;
    	if (clock_gettime(CLOCK_MONOTONIC, &addr_proposal->created))
    		fatal("clock_gettime");
    	addr_proposal->when = ra->when;
    	addr_proposal->uptime = ra->uptime;
    	addr_proposal->if_index = iface->if_index;
    	memcpy(&addr_proposal->from, &ra->from,
    	    sizeof(addr_proposal->from));
    	memcpy(&addr_proposal->hw_address, &iface->hw_address,
    	    sizeof(addr_proposal->hw_address));
    	memcpy(&addr_proposal->soiikey, &iface->soiikey,
    	    sizeof(addr_proposal->soiikey));
    	addr_proposal->temporary = temporary;
    	memcpy(&addr_proposal->prefix, &prefix->prefix,
    	    sizeof(addr_proposal->prefix));
    	addr_proposal->prefix_len = prefix->prefix_len;
    
    	if (temporary) {
    		addr_proposal->vltime = MINIMUM(prefix->vltime,
    		    PRIV_VALID_LIFETIME);
    		addr_proposal->desync_factor =
    		    arc4random_uniform(PRIV_MAX_DESYNC_FACTOR);
    
    		addr_proposal->pltime = MINIMUM(prefix->pltime,
    		    PRIV_PREFERRED_LIFETIME - addr_proposal->desync_factor);
    	} else {
    		addr_proposal->vltime = prefix->vltime;
    		addr_proposal->pltime = prefix->pltime;
    	}
    
    	if (ra->mtu == iface->cur_mtu)
    		addr_proposal->mtu = 0;
    	else {
    		addr_proposal->mtu = ra->mtu;
    		iface->cur_mtu = ra->mtu;
    	}
    
    	gen_addr(iface, prefix, addr_proposal, temporary);
    
    	LIST_INSERT_HEAD(&iface->addr_proposals, addr_proposal, entries);
    	configure_address(addr_proposal);
    
    	hbuf = sin6_to_str(&addr_proposal->addr);
    	log_debug("%s: iface %d: %s", __func__, iface->if_index, hbuf);
    }
    
    void
    free_address_proposal(struct address_proposal *addr_proposal)
    {
    	if (addr_proposal == NULL)
    		return;
    
    	LIST_REMOVE(addr_proposal, entries);
    	evtimer_del(&addr_proposal->timer);
    	switch (addr_proposal->state) {
    	case PROPOSAL_CONFIGURED:
    	case PROPOSAL_NEARLY_EXPIRED:
    	case PROPOSAL_STALE:
    		withdraw_addr(addr_proposal);
    		break;
    	default:
    		break;
    	}
    	free(addr_proposal);
    }
    
    void
    withdraw_addr(struct address_proposal *addr_proposal)
    {
    	struct imsg_configure_address	address;
    
    	log_debug("%s: %d", __func__, addr_proposal->if_index);
    	memset(&address, 0, sizeof(address));
    	address.if_index = addr_proposal->if_index;
    	memcpy(&address.addr, &addr_proposal->addr, sizeof(address.addr));
    
    	engine_imsg_compose_main(IMSG_WITHDRAW_ADDRESS, 0, &address,
    	    sizeof(address));
    }
    
    void
    gen_dfr_proposal(struct slaacd_iface *iface, struct radv *ra)
    {
    	struct dfr_proposal	*dfr_proposal;
    	const char		*hbuf;
    
    	if ((dfr_proposal = calloc(1, sizeof(*dfr_proposal))) == NULL)
    		fatal("calloc");
    	dfr_proposal->id = ++proposal_id;
    	evtimer_set(&dfr_proposal->timer, dfr_proposal_timeout,
    	    dfr_proposal);
    	dfr_proposal->timo.tv_sec = 1;
    	dfr_proposal->timo.tv_usec = arc4random_uniform(1000000);
    	dfr_proposal->state = PROPOSAL_NOT_CONFIGURED;
    	dfr_proposal->when = ra->when;
    	dfr_proposal->uptime = ra->uptime;
    	dfr_proposal->if_index = iface->if_index;
    	dfr_proposal->rdomain = iface->rdomain;
    	memcpy(&dfr_proposal->addr, &ra->from,
    	    sizeof(dfr_proposal->addr));
    	dfr_proposal->router_lifetime = ra->router_lifetime;
    	dfr_proposal->rpref = ra->rpref;
    
    	LIST_INSERT_HEAD(&iface->dfr_proposals, dfr_proposal, entries);
    	configure_dfr(dfr_proposal);
    
    	hbuf = sin6_to_str(&dfr_proposal->addr);
    	log_debug("%s: iface %d: %s", __func__, iface->if_index, hbuf);
    }
    
    void
    configure_dfr(struct dfr_proposal *dfr_proposal)
    {
    	struct imsg_configure_dfr	 dfr;
    
    	log_debug("%s: %d", __func__, dfr_proposal->if_index);
    
    	dfr.if_index = dfr_proposal->if_index;
    	dfr.rdomain = dfr_proposal->rdomain;
    	memcpy(&dfr.addr, &dfr_proposal->addr, sizeof(dfr.addr));
    	dfr.router_lifetime = dfr_proposal->router_lifetime;
    
    	engine_imsg_compose_main(IMSG_CONFIGURE_DFR, 0, &dfr, sizeof(dfr));
    
    	dfr_proposal_state_transition(dfr_proposal, PROPOSAL_CONFIGURED);
    }
    
    void
    withdraw_dfr(struct dfr_proposal *dfr_proposal)
    {
    	struct imsg_configure_dfr	 dfr;
    
    	log_debug("%s: %d", __func__, dfr_proposal->if_index);
    
    	dfr.if_index = dfr_proposal->if_index;
    	dfr.rdomain = dfr_proposal->rdomain;
    	memcpy(&dfr.addr, &dfr_proposal->addr, sizeof(dfr.addr));
    	dfr.router_lifetime = dfr_proposal->router_lifetime;
    
    	engine_imsg_compose_main(IMSG_WITHDRAW_DFR, 0, &dfr, sizeof(dfr));
    }
    
    void
    free_dfr_proposal(struct dfr_proposal *dfr_proposal)
    {
    	if (dfr_proposal == NULL)
    		return;
    
    	LIST_REMOVE(dfr_proposal, entries);
    	evtimer_del(&dfr_proposal->timer);
    	switch (dfr_proposal->state) {
    	case PROPOSAL_CONFIGURED:
    	case PROPOSAL_NEARLY_EXPIRED:
    	case PROPOSAL_STALE:
    		withdraw_dfr(dfr_proposal);
    		break;
    	default:
    		break;
    	}
    	free(dfr_proposal);
    }
    
    void
    gen_rdns_proposal(struct slaacd_iface *iface, struct radv *ra)
    {
    	struct rdns_proposal	*rdns_proposal;
    	struct radv_rdns	*rdns;
    	const char		*hbuf;
    
    	if ((rdns_proposal = calloc(1, sizeof(*rdns_proposal))) == NULL)
    		fatal("calloc");
    	rdns_proposal->id = ++proposal_id;
    	evtimer_set(&rdns_proposal->timer, rdns_proposal_timeout,
    	    rdns_proposal);
    	rdns_proposal->timo.tv_sec = 1;
    	rdns_proposal->timo.tv_usec = arc4random_uniform(1000000);
    	rdns_proposal->state = PROPOSAL_NOT_CONFIGURED;
    	rdns_proposal->when = ra->when;
    	rdns_proposal->uptime = ra->uptime;
    	rdns_proposal->if_index = iface->if_index;
    	rdns_proposal->rdomain = iface->rdomain;
    	memcpy(&rdns_proposal->from, &ra->from,
    	    sizeof(rdns_proposal->from));
    	rdns_proposal->rdns_lifetime = ra->rdns_lifetime;
    	LIST_FOREACH(rdns, &ra->rdns_servers, entries) {
    		memcpy(&rdns_proposal->rdns[rdns_proposal->rdns_count++],
    		    &rdns->rdns, sizeof(struct in6_addr));
    		if (rdns_proposal->rdns_count == MAX_RDNS_COUNT)
    			break;
    	}
    
    	LIST_INSERT_HEAD(&iface->rdns_proposals, rdns_proposal, entries);
    	compose_rdns_proposal(iface->if_index, iface->rdomain);
    
    	hbuf = sin6_to_str(&rdns_proposal->from);
    	log_debug("%s: iface %d: %s", __func__, iface->if_index, hbuf);
    }
    
    void
    compose_rdns_proposal(uint32_t if_index, int rdomain)
    {
    	struct imsg_propose_rdns rdns;
    	struct slaacd_iface	*iface;
    	struct rdns_proposal	*rdns_proposal;
    	int			 i;
    
    	memset(&rdns, 0, sizeof(rdns));
    	rdns.if_index = if_index;
    	rdns.rdomain = rdomain;
    
    	if ((iface = get_slaacd_iface_by_id(if_index)) != NULL) {
    		LIST_FOREACH(rdns_proposal, &iface->rdns_proposals, entries) {
    			if (rdns_proposal->state == PROPOSAL_WITHDRAWN ||
    			    rdns_proposal->state == PROPOSAL_STALE)
    				continue;
    			rdns_proposal_state_transition(rdns_proposal,
    			    PROPOSAL_CONFIGURED);
    			for (i = 0; i < rdns_proposal->rdns_count &&
    				 rdns.rdns_count < MAX_RDNS_COUNT; i++) {
    				rdns.rdns[rdns.rdns_count++] =
    				    rdns_proposal->rdns[i];
    			}
    		}
    	}
    
    	engine_imsg_compose_main(IMSG_PROPOSE_RDNS, 0, &rdns, sizeof(rdns));
    }
    
    void
    free_rdns_proposal(struct rdns_proposal *rdns_proposal)
    {
    	if (rdns_proposal == NULL)
    		return;
    
    	LIST_REMOVE(rdns_proposal, entries);
    	evtimer_del(&rdns_proposal->timer);
    	switch (rdns_proposal->state) {
    	case PROPOSAL_CONFIGURED:
    	case PROPOSAL_NEARLY_EXPIRED:
    	case PROPOSAL_STALE:
    		withdraw_rdns(rdns_proposal);
    		break;
    	default:
    		break;
    	}
    	free(rdns_proposal);
    }
    
    void
    withdraw_rdns(struct rdns_proposal *rdns_proposal)
    {
    	log_debug("%s: %d", __func__, rdns_proposal->if_index);
    
    	rdns_proposal->state = PROPOSAL_WITHDRAWN;
    
    	/* we have to re-propose all rdns servers, minus one */
    	compose_rdns_proposal(rdns_proposal->if_index, rdns_proposal->rdomain);
    }
    
    void
    address_proposal_timeout(int fd, short events, void *arg)
    {
    	struct address_proposal	*addr_proposal;
    	struct slaacd_iface	*iface = NULL;
    	struct radv		*ra = NULL;
    	struct radv_prefix	*prefix = NULL;
    	const char		*hbuf;
    
    	addr_proposal = (struct address_proposal *)arg;
    
    	hbuf = sin6_to_str(&addr_proposal->addr);
    	log_debug("%s: iface %d: %s [%s], priv: %s", __func__,
    	    addr_proposal->if_index, hbuf,
    	    proposal_state_name(addr_proposal->state),
    	    addr_proposal->temporary ? "y" : "n");
    
    	switch (addr_proposal->state) {
    	case PROPOSAL_IF_DOWN:
    		addr_proposal_state_transition(addr_proposal, PROPOSAL_STALE);
    		break;
    	case PROPOSAL_CONFIGURED:
    		addr_proposal_state_transition(addr_proposal,
    		    PROPOSAL_NEARLY_EXPIRED);
    		break;
    	case PROPOSAL_NEARLY_EXPIRED:
    		if (real_lifetime(&addr_proposal->uptime,
    		    addr_proposal->vltime) > 0)
    			addr_proposal_state_transition(addr_proposal,
    			    PROPOSAL_NEARLY_EXPIRED);
    		else
    			addr_proposal_state_transition(addr_proposal,
    			    PROPOSAL_STALE);
    		break;
    	case PROPOSAL_DUPLICATED:
    		iface = get_slaacd_iface_by_id(addr_proposal->if_index);
    		if (iface != NULL)
    			ra = find_ra(iface, &addr_proposal->from);
    		if (ra != NULL)
    			prefix = find_prefix(ra, &addr_proposal->prefix,
    			    addr_proposal->prefix_len);
    		if (prefix != NULL) {
    			if (!addr_proposal->temporary) {
    				prefix->dad_counter++;
    				gen_address_proposal(iface, ra, prefix, 0);
    			} else
    				gen_address_proposal(iface, ra, prefix, 1);
    		}
    		addr_proposal_state_transition(addr_proposal, PROPOSAL_STALE);
    		break;
    	case PROPOSAL_STALE:
    		free_address_proposal(addr_proposal);
    		addr_proposal = NULL;
    		break;
    	case PROPOSAL_WITHDRAWN:
    		free_address_proposal(addr_proposal);
    		addr_proposal = NULL;
    		break;
    	default:
    		log_debug("%s: unhandled state: %s", __func__,
    		    proposal_state_name(addr_proposal->state));
    	}
    }
    
    void
    dfr_proposal_timeout(int fd, short events, void *arg)
    {
    	struct dfr_proposal	*dfr_proposal;
    	const char		*hbuf;
    
    	dfr_proposal = (struct dfr_proposal *)arg;
    
    	hbuf = sin6_to_str(&dfr_proposal->addr);
    	log_debug("%s: iface %d: %s [%s]", __func__, dfr_proposal->if_index,
    	    hbuf, proposal_state_name(dfr_proposal->state));
    
    	switch (dfr_proposal->state) {
    	case PROPOSAL_IF_DOWN:
    		dfr_proposal_state_transition(dfr_proposal, PROPOSAL_STALE);
    		break;
    	case PROPOSAL_CONFIGURED:
    		dfr_proposal_state_transition(dfr_proposal,
    		    PROPOSAL_NEARLY_EXPIRED);
    		break;
    	case PROPOSAL_NEARLY_EXPIRED:
    		if (real_lifetime(&dfr_proposal->uptime,
    		    dfr_proposal->router_lifetime) > 0)
    			dfr_proposal_state_transition(dfr_proposal,
    			    PROPOSAL_NEARLY_EXPIRED);
    		else
    			dfr_proposal_state_transition(dfr_proposal,
    			    PROPOSAL_STALE);
    		break;
    	case PROPOSAL_STALE:
    		free_dfr_proposal(dfr_proposal);
    		dfr_proposal = NULL;
    		break;
    	case PROPOSAL_WITHDRAWN:
    		free_dfr_proposal(dfr_proposal);
    		dfr_proposal = NULL;
    		break;
    
    	default:
    		log_debug("%s: unhandled state: %s", __func__,
    		    proposal_state_name(dfr_proposal->state));
    	}
    }
    
    void
    rdns_proposal_timeout(int fd, short events, void *arg)
    {
    	struct rdns_proposal	*rdns_proposal;
    	const char		*hbuf;
    
    	rdns_proposal = (struct rdns_proposal *)arg;
    
    	hbuf = sin6_to_str(&rdns_proposal->from);
    	log_debug("%s: iface %d: %s [%s]", __func__, rdns_proposal->if_index,
    	    hbuf, proposal_state_name(rdns_proposal->state));
    
    	switch (rdns_proposal->state) {
    	case PROPOSAL_IF_DOWN:
    		rdns_proposal_state_transition(rdns_proposal, PROPOSAL_STALE);
    		break;
    	case PROPOSAL_CONFIGURED:
    		rdns_proposal_state_transition(rdns_proposal,
    		    PROPOSAL_NEARLY_EXPIRED);
    		break;
    	case PROPOSAL_NEARLY_EXPIRED:
    		if (real_lifetime(&rdns_proposal->uptime,
    		    rdns_proposal->rdns_lifetime) > 0)
    			rdns_proposal_state_transition(rdns_proposal,
    			    PROPOSAL_NEARLY_EXPIRED);
    		else
    			rdns_proposal_state_transition(rdns_proposal,
    			    PROPOSAL_STALE);
    		break;
    	case PROPOSAL_STALE:
    		free_rdns_proposal(rdns_proposal);
    		rdns_proposal = NULL;
    		break;
    	case PROPOSAL_WITHDRAWN:
    		free_rdns_proposal(rdns_proposal);
    		rdns_proposal = NULL;
    		break;
    
    	default:
    		log_debug("%s: unhandled state: %s", __func__,
    		    proposal_state_name(rdns_proposal->state));
    	}
    }
    
    void
    iface_timeout(int fd, short events, void *arg)
    {
    	struct slaacd_iface	*iface = (struct slaacd_iface *)arg;
    
    	log_debug("%s[%d]: %s", __func__, iface->if_index,
    	    if_state_name(iface->state));
    
    	switch (iface->state) {
    	case IF_DOWN:
    		fatalx("%s: timeout in wrong state IF_DOWN", __func__);
    		break;
    	case IF_INIT:
    		iface_state_transition(iface, IF_INIT);
    		break;
    	default:
    		break;
    	}
    }
    
    struct radv*
    find_ra(struct slaacd_iface *iface, struct sockaddr_in6 *from)
    {
    	struct radv	*ra;
    
    	LIST_FOREACH (ra, &iface->radvs, entries) {
    		if (memcmp(&ra->from.sin6_addr, &from->sin6_addr,
    		    sizeof(from->sin6_addr)) == 0)
    			return (ra);
    	}
    
    	return (NULL);
    }
    
    struct address_proposal*
    find_address_proposal_by_addr(struct slaacd_iface *iface, struct sockaddr_in6
        *addr)
    {
    	struct address_proposal	*addr_proposal;
    
    	LIST_FOREACH (addr_proposal, &iface->addr_proposals, entries) {
    		if (memcmp(&addr_proposal->addr, addr, sizeof(*addr)) == 0)
    			return (addr_proposal);
    	}
    
    	return (NULL);
    }
    
    struct dfr_proposal*
    find_dfr_proposal_by_gw(struct slaacd_iface *iface, struct sockaddr_in6
        *addr)
    {
    	struct dfr_proposal	*dfr_proposal;
    
    	LIST_FOREACH (dfr_proposal, &iface->dfr_proposals, entries) {
    		if (memcmp(&dfr_proposal->addr, addr, sizeof(*addr)) == 0)
    			return (dfr_proposal);
    	}
    
    	return (NULL);
    }
    
    struct rdns_proposal*
    find_rdns_proposal_by_gw(struct slaacd_iface *iface, struct sockaddr_in6
        *from)
    {
    	struct rdns_proposal	*rdns_proposal;
    
    	LIST_FOREACH (rdns_proposal, &iface->rdns_proposals, entries) {
    		if (memcmp(&rdns_proposal->from, from, sizeof(*from)) == 0)
    			return (rdns_proposal);
    	}
    
    	return (NULL);
    }
    
    struct radv_prefix *
    find_prefix(struct radv *ra, struct in6_addr *prefix, uint8_t prefix_len)
    {
    	struct radv_prefix	*result;
    
    
    	LIST_FOREACH(result, &ra->prefixes, entries) {
    		if (memcmp(&result->prefix, prefix,
    		    sizeof(result->prefix)) == 0 && result->prefix_len ==
    		    prefix_len)
    			return (result);
    	}
    	return (NULL);
    }
    
    uint32_t
    real_lifetime(struct timespec *received_uptime, uint32_t ltime)
    {
    	struct timespec	 now, diff;
    	int64_t		 remaining;
    
    	if (clock_gettime(CLOCK_MONOTONIC, &now))
    		fatal("clock_gettime");
    
    	timespecsub(&now, received_uptime, &diff);
    
    	remaining = ((int64_t)ltime) - diff.tv_sec;
    
    	if (remaining < 0)
    		remaining = 0;
    
    	return (remaining);
    }
    
    void
    merge_dad_couters(struct radv *old_ra, struct radv *new_ra)
    {
    
    	struct radv_prefix	*old_prefix, *new_prefix;
    
    	LIST_FOREACH(old_prefix, &old_ra->prefixes, entries) {
    		if (!old_prefix->dad_counter)
    			continue;
    		if ((new_prefix = find_prefix(new_ra, &old_prefix->prefix,
    		    old_prefix->prefix_len)) != NULL)
    			new_prefix->dad_counter = old_prefix->dad_counter;
    	}
    }