Rewrite state machine in the style of dhcpleased(8).
authorflorian <florian@openbsd.org>
Tue, 12 Jul 2022 16:54:59 +0000 (16:54 +0000)
committerflorian <florian@openbsd.org>
Tue, 12 Jul 2022 16:54:59 +0000 (16:54 +0000)
It is less cluttered, easier to reason about and fixes some bugs in
passing that would have been difficult in the old state machine.

Stale IPv6 addresses, default routes and nameservers are now correctly
removed when moving from one IPv6 enabled network to another IPv6
enabled network.
Default routes and nameservers correctly expire when they are not
refreshed and nameservers are updated when router advertisements
change the nameserver option.

Testing & input caspar@

Putting it in now to get wider testing and shake out bugs, discussed
with deraadt@ at r2k22.

sbin/slaacd/engine.c
sbin/slaacd/frontend.c
sbin/slaacd/slaacd.c
sbin/slaacd/slaacd.h

index db6d619..f12a8db 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: engine.c,v 1.80 2022/06/28 13:35:42 florian Exp $     */
+/*     $OpenBSD: engine.c,v 1.81 2022/07/12 16:54:59 florian Exp $     */
 
 /*
  * Copyright (c) 2017 Florian Obser <florian@openbsd.org>
 
 enum if_state {
        IF_DOWN,
-       IF_DELAY,
-       IF_PROBE,
-       IF_IDLE,
-       IF_DEAD,
-};
-
-const char* if_state_name[] = {
-       "IF_DOWN",
-       "IF_DELAY",
-       "IF_PROBE",
-       "IF_IDLE",
-       "IF_DEAD",
+       IF_INIT,
+       IF_BOUND,
 };
 
 enum proposal_state {
+       PROPOSAL_IF_DOWN,
        PROPOSAL_NOT_CONFIGURED,
-       PROPOSAL_SENT,
        PROPOSAL_CONFIGURED,
        PROPOSAL_NEARLY_EXPIRED,
        PROPOSAL_WITHDRAWN,
@@ -124,16 +114,6 @@ enum proposal_state {
        PROPOSAL_STALE,
 };
 
-const char* proposal_state_name[] = {
-       "NOT_CONFIGURED",
-       "SENT",
-       "CONFIGURED",
-       "NEARLY_EXPIRED",
-       "WITHDRAWN",
-       "DUPLICATED",
-       "STALE",
-};
-
 const char* rpref_name[] = {
        "Low",
        "Medium",
@@ -181,12 +161,13 @@ struct address_proposal {
        struct event                     timer;
        int64_t                          id;
        enum proposal_state              state;
-       time_t                           next_timeout;
+       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;
@@ -204,7 +185,7 @@ struct dfr_proposal {
        struct event                     timer;
        int64_t                          id;
        enum proposal_state              state;
-       time_t                           next_timeout;
+       struct timeval                   timo;
        struct timespec                  when;
        struct timespec                  uptime;
        uint32_t                         if_index;
@@ -219,7 +200,7 @@ struct rdns_proposal {
        struct event                     timer;
        int64_t                          id;
        enum proposal_state              state;
-       time_t                           next_timeout;
+       struct timeval                   timo;
        struct timespec                  when;
        struct timespec                  uptime;
        uint32_t                         if_index;
@@ -234,6 +215,8 @@ 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;
@@ -264,12 +247,19 @@ void                       engine_showinfo_ctl(struct imsg *, uint32_t);
 void                    debug_log_ra(struct imsg_ra *);
 int                     in6_mask2prefixlen(struct in6_addr *);
 #endif /* SMALL */
-void                    deprecate_all_proposals(struct slaacd_iface *);
-void                    send_rdns_withdraw(struct slaacd_iface *);
 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);
@@ -277,7 +267,6 @@ 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                    timeout_from_lifetime(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
@@ -289,15 +278,14 @@ void                       update_iface_ra_rdns(struct slaacd_iface *,
                             struct radv *);
 void                    gen_rdns_proposal(struct slaacd_iface *, struct
                             radv *);
-void                    propose_rdns(struct rdns_proposal *);
 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                    start_probe(struct slaacd_iface *);
 void                    address_proposal_timeout(int, short, void *);
 void                    dfr_proposal_timeout(int, short, void *);
 void                    rdns_proposal_timeout(int, short, void *);
@@ -309,7 +297,7 @@ 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 radv_prefix *);
+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 *);
@@ -318,6 +306,33 @@ static struct imsgev       *iev_frontend;
 static struct imsgev   *iev_main;
 int64_t                         proposal_id;
 
+
+#define        CASE(x) case x : return #x
+
+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);
+       }
+}
+
 void
 engine_sig_handler(int sig, short event, void *arg)
 {
@@ -441,7 +456,6 @@ engine_dispatch_frontend(int fd, short event, void *bula)
        struct imsg_del_addr             del_addr;
        struct imsg_del_route            del_route;
        struct imsg_dup_addr             dup_addr;
-       struct timeval                   tv;
        ssize_t                          n;
        int                              shut = 0;
 #ifndef        SMALL
@@ -499,7 +513,13 @@ engine_dispatch_frontend(int fd, short event, void *bula)
                                    __func__, IMSG_DATA_SIZE(imsg));
                        memcpy(&ra, imsg.data, sizeof(ra));
                        iface = get_slaacd_iface_by_id(ra.if_index);
-                       if (iface != NULL)
+
+                       /*
+                        * 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:
@@ -512,10 +532,10 @@ engine_dispatch_frontend(int fd, short event, void *bula)
                        if (iface == NULL)
                                log_warnx("requested to send solicitation on "
                                    "non-autoconf interface: %u", if_index);
-                       else
-                               engine_imsg_compose_frontend(
-                                   IMSG_CTL_SEND_SOLICITATION, imsg.hdr.pid,
-                                   &iface->if_index, sizeof(iface->if_index));
+                       else {
+                               iface->last_sol.tv_sec = 0; /* no rate limit */
+                               request_solicitation(iface);
+                       }
                        break;
                case IMSG_DEL_ADDRESS:
                        if (IMSG_DATA_SIZE(imsg) != sizeof(del_addr))
@@ -531,8 +551,14 @@ engine_dispatch_frontend(int fd, short event, void *bula)
 
                        addr_proposal = find_address_proposal_by_addr(iface,
                            &del_addr.addr);
-
-                       free_address_proposal(addr_proposal);
+                       /*
+                        * 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_DATA_SIZE(imsg) != sizeof(del_route))
@@ -552,7 +578,6 @@ engine_dispatch_frontend(int fd, short event, void *bula)
                        if (dfr_proposal) {
                                dfr_proposal->state = PROPOSAL_WITHDRAWN;
                                free_dfr_proposal(dfr_proposal);
-                               start_probe(iface);
                        }
                        break;
                case IMSG_DUP_ADDRESS:
@@ -570,13 +595,9 @@ engine_dispatch_frontend(int fd, short event, void *bula)
                        addr_proposal = find_address_proposal_by_addr(iface,
                            &dup_addr.addr);
 
-                       if (addr_proposal) {
-                               addr_proposal->state = PROPOSAL_DUPLICATED;
-                               tv.tv_sec = 0;
-                               tv.tv_usec = arc4random_uniform(1000000);
-                               addr_proposal->next_timeout = 0;
-                               evtimer_add(&addr_proposal->timer, &tv);
-                       }
+                       if (addr_proposal)
+                               addr_proposal_state_transition(addr_proposal,
+                                   PROPOSAL_DUPLICATED);
                        break;
                case IMSG_REPROPOSE_RDNS:
                        LIST_FOREACH (iface, &slaacd_interfaces, entries)
@@ -608,10 +629,6 @@ engine_dispatch_main(int fd, short event, void *bula)
        struct imsg_ifinfo       imsg_ifinfo;
        ssize_t                  n;
        int                      shut = 0;
-       struct slaacd_iface     *iface;
-       struct imsg_addrinfo     imsg_addrinfo;
-       struct address_proposal *addr_proposal = NULL;
-       size_t                   i;
 
        if (event & EV_READ) {
                if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
@@ -669,65 +686,6 @@ engine_dispatch_main(int fd, short event, void *bula)
                        memcpy(&imsg_ifinfo, imsg.data, sizeof(imsg_ifinfo));
                        engine_update_iface(&imsg_ifinfo);
                        break;
-#ifndef        SMALL
-               case IMSG_UPDATE_ADDRESS:
-                       if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_addrinfo))
-                               fatalx("%s: IMSG_UPDATE_ADDRESS wrong length: "
-                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
-
-                       memcpy(&imsg_addrinfo, imsg.data,
-                           sizeof(imsg_addrinfo));
-
-                       iface = get_slaacd_iface_by_id(imsg_addrinfo.if_index);
-                       if (iface == NULL)
-                               break;
-
-                       log_debug("%s: IMSG_UPDATE_ADDRESS", __func__);
-
-                       addr_proposal = find_address_proposal_by_addr(iface,
-                           &imsg_addrinfo.addr);
-                       if (addr_proposal)
-                               break;
-
-                       if ((addr_proposal = calloc(1,
-                           sizeof(*addr_proposal))) == NULL)
-                               fatal("calloc");
-                       evtimer_set(&addr_proposal->timer,
-                           address_proposal_timeout, addr_proposal);
-                       addr_proposal->id = ++proposal_id;
-                       addr_proposal->state = PROPOSAL_CONFIGURED;
-                       addr_proposal->vltime = imsg_addrinfo.vltime;
-                       addr_proposal->pltime = imsg_addrinfo.pltime;
-
-                       timeout_from_lifetime(addr_proposal);
-
-                       /* leave created 0, we don't know when it was created */
-                       if (clock_gettime(CLOCK_REALTIME, &addr_proposal->when))
-                               fatal("clock_gettime");
-                       if (clock_gettime(CLOCK_MONOTONIC,
-                           &addr_proposal->uptime))
-                               fatal("clock_gettime");
-                       addr_proposal->if_index = imsg_addrinfo.if_index;
-                       memcpy(&addr_proposal->hw_address, &iface->hw_address,
-                           sizeof(addr_proposal->hw_address));
-                       addr_proposal->addr = imsg_addrinfo.addr;
-                       addr_proposal->mask = imsg_addrinfo.mask;
-                       addr_proposal->prefix = addr_proposal->addr.sin6_addr;
-
-                       for (i = 0; i < sizeof(addr_proposal->prefix.s6_addr) /
-                           sizeof(addr_proposal->prefix.s6_addr[0]); i++)
-                               addr_proposal->prefix.s6_addr[i] &=
-                                   addr_proposal->mask.s6_addr[i];
-
-                       addr_proposal->temporary = imsg_addrinfo.temporary;
-                       addr_proposal->prefix_len =
-                           in6_mask2prefixlen(&addr_proposal->mask);
-
-                       LIST_INSERT_HEAD(&iface->addr_proposals,
-                           addr_proposal, entries);
-
-                       break;
-#endif /* SMALL */
                default:
                        log_debug("%s: unexpected imsg %d", __func__,
                            imsg.hdr.type);
@@ -824,11 +782,11 @@ send_interface_info(struct slaacd_iface *iface, pid_t pid)
                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],
+                   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->next_timeout;
+               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(
@@ -853,11 +811,11 @@ send_interface_info(struct slaacd_iface *iface, pid_t pid)
                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],
+                   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->next_timeout;
+               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(
@@ -882,11 +840,11 @@ send_interface_info(struct slaacd_iface *iface, pid_t pid)
                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],
+                   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->next_timeout;
+               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(
@@ -927,32 +885,6 @@ engine_showinfo_ctl(struct imsg *imsg, uint32_t if_index)
 
 #endif /* SMALL */
 
-void
-deprecate_all_proposals(struct slaacd_iface *iface)
-{
-       struct address_proposal *addr_proposal;
-
-       log_debug("%s: iface: %d", __func__, iface->if_index);
-
-       LIST_FOREACH (addr_proposal, &iface->addr_proposals, entries) {
-               addr_proposal->pltime = 0;
-               configure_address(addr_proposal);
-               addr_proposal->state = PROPOSAL_NEARLY_EXPIRED;
-       }
-}
-
-void
-send_rdns_withdraw(struct slaacd_iface *iface)
-{
-       struct rdns_proposal    *rdns_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);
-}
-
 struct slaacd_iface*
 get_slaacd_iface_by_id(uint32_t if_index)
 {
@@ -1028,6 +960,326 @@ free_ra(struct radv *ra)
        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;
+       char                     ifnamebuf[IF_NAMESIZE], *if_name;
+
+       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_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;
+       char                     ifnamebuf[IF_NAMESIZE], *if_name;
+
+       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_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;
+       char                     ifnamebuf[IF_NAMESIZE], *if_name;
+
+       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_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;
+       char                     ifnamebuf[IF_NAMESIZE], *if_name;
+
+       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_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_warnx("last solicitation less then %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)
 {
@@ -1039,6 +1291,7 @@ engine_update_iface(struct imsg_ifinfo *imsg_ifinfo)
                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;
@@ -1106,16 +1359,10 @@ engine_update_iface(struct imsg_ifinfo *imsg_ifinfo)
                return;
 
        if (iface->running && LINK_STATE_IS_UP(iface->link_state))
-               start_probe(iface);
+               iface_state_transition(iface, IF_INIT);
 
-       else {
-               /* XXX correct state transition */
-               send_rdns_withdraw(iface);
-               deprecate_all_proposals(iface);
-               iface->state = IF_DOWN;
-               if (evtimer_pending(&iface->timer, NULL))
-                       evtimer_del(&iface->timer);
-       }
+       else
+               iface_state_transition(iface, IF_DOWN);
 }
 
 void
@@ -1318,7 +1565,6 @@ parse_ra(struct slaacd_iface *iface, struct imsg_ra *ra)
                p += nd_opt_hdr->nd_opt_len * 8 - 2;
        }
        update_iface_ra(iface, radv);
-       iface->state = IF_IDLE;
        return;
 
 err:
@@ -1646,25 +1892,24 @@ update_iface_ra_dfr(struct slaacd_iface *iface, struct radv *ra)
                return;
        }
 
-       if (real_lifetime(&dfr_proposal->uptime, dfr_proposal->router_lifetime)
-           > ra->router_lifetime) {
-               log_warnx("ignoring router advertisement lowering router "
-                   "lifetime");
-               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],
+           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;
@@ -1744,6 +1989,7 @@ update_iface_ra_prefix(struct slaacd_iface *iface, struct radv *ra,
                        found = 1;
                }
 
+               addr_proposal->from = ra->from;
                addr_proposal->when = ra->when;
                addr_proposal->uptime = ra->uptime;
 
@@ -1758,11 +2004,13 @@ update_iface_ra_prefix(struct slaacd_iface *iface, struct radv *ra,
                }
 
                log_debug("%s, addr state: %s", __func__,
-                   proposal_state_name[addr_proposal->state]);
+                   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;
@@ -1800,37 +2048,63 @@ 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 */
-               gen_rdns_proposal(iface, ra);
+               if (!LIST_EMPTY(&ra->rdns_servers))
+                       gen_rdns_proposal(iface, ra);
                return;
        }
 
-       if (real_lifetime(&rdns_proposal->uptime, rdns_proposal->rdns_lifetime)
-           > ra->rdns_lifetime) {
-               /* XXX check RFC */
-               log_warnx("ignoring router advertisement lowering rdns "
-                   "lifetime");
+       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_proposal->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],
+           proposal_state_name(rdns_proposal->state),
            real_lifetime(&rdns_proposal->uptime,
            rdns_proposal->rdns_lifetime));
 
        switch (rdns_proposal->state) {
-       case PROPOSAL_SENT:
+       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");
-               propose_rdns(rdns_proposal);
+               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,
@@ -1839,39 +2113,12 @@ update_iface_ra_rdns(struct slaacd_iface *iface, struct radv *ra)
        }
 }
 
-void
-timeout_from_lifetime(struct address_proposal *addr_proposal)
-{
-       struct timeval   tv;
-       time_t           lifetime;
-
-       addr_proposal->next_timeout = 0;
-
-       if (addr_proposal->pltime > MAX_RTR_SOLICITATIONS *
-           (RTR_SOLICITATION_INTERVAL + 1))
-               lifetime = addr_proposal->pltime;
-       else
-               lifetime = addr_proposal->vltime;
-
-       if (lifetime > MAX_RTR_SOLICITATIONS *
-           (RTR_SOLICITATION_INTERVAL + 1)) {
-               addr_proposal->next_timeout = lifetime - MAX_RTR_SOLICITATIONS *
-                   (RTR_SOLICITATION_INTERVAL + 1);
-               tv.tv_sec = addr_proposal->next_timeout;
-               tv.tv_usec = arc4random_uniform(1000000);
-               evtimer_add(&addr_proposal->timer, &tv);
-               log_debug("%s: %d, scheduling new timeout in %llds.%06ld",
-                   __func__, addr_proposal->if_index, tv.tv_sec, tv.tv_usec);
-       }
-}
 
 void
 configure_address(struct address_proposal *addr_proposal)
 {
        struct imsg_configure_address    address;
-
-       timeout_from_lifetime(addr_proposal);
-       addr_proposal->state = PROPOSAL_CONFIGURED;
+       struct slaacd_iface             *iface;
 
        log_debug("%s: %d", __func__, addr_proposal->if_index);
 
@@ -1885,6 +2132,10 @@ configure_address(struct address_proposal *addr_proposal)
 
        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
@@ -1899,13 +2150,16 @@ gen_address_proposal(struct slaacd_iface *iface, struct radv *ra, struct
        addr_proposal->id = ++proposal_id;
        evtimer_set(&addr_proposal->timer, address_proposal_timeout,
            addr_proposal);
-       addr_proposal->next_timeout = 1;
+       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,
@@ -1987,7 +2241,8 @@ gen_dfr_proposal(struct slaacd_iface *iface, struct radv *ra)
        dfr_proposal->id = ++proposal_id;
        evtimer_set(&dfr_proposal->timer, dfr_proposal_timeout,
            dfr_proposal);
-       dfr_proposal->next_timeout = 1;
+       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;
@@ -2009,39 +2264,17 @@ void
 configure_dfr(struct dfr_proposal *dfr_proposal)
 {
        struct imsg_configure_dfr        dfr;
-       struct timeval                   tv;
-       enum proposal_state              prev_state;
-
-       if (dfr_proposal->router_lifetime > MAX_RTR_SOLICITATIONS *
-           (RTR_SOLICITATION_INTERVAL + 1)) {
-               dfr_proposal->next_timeout = dfr_proposal->router_lifetime -
-                   MAX_RTR_SOLICITATIONS * (RTR_SOLICITATION_INTERVAL + 1);
-               tv.tv_sec = dfr_proposal->next_timeout;
-               tv.tv_usec = arc4random_uniform(1000000);
-               evtimer_add(&dfr_proposal->timer, &tv);
-               log_debug("%s: %d, scheduling new timeout in %llds.%06ld",
-                   __func__, dfr_proposal->if_index, tv.tv_sec, tv.tv_usec);
-       } else
-               dfr_proposal->next_timeout = 0;
-
-       prev_state = dfr_proposal->state;
-
-       dfr_proposal->state = PROPOSAL_CONFIGURED;
 
        log_debug("%s: %d", __func__, dfr_proposal->if_index);
 
-       if (prev_state == PROPOSAL_CONFIGURED || prev_state ==
-           PROPOSAL_NEARLY_EXPIRED) {
-               /* nothing to do here, routes do not expire in the kernel */
-               return;
-       }
-
        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
@@ -2091,7 +2324,8 @@ gen_rdns_proposal(struct slaacd_iface *iface, struct radv *ra)
        rdns_proposal->id = ++proposal_id;
        evtimer_set(&rdns_proposal->timer, rdns_proposal_timeout,
            rdns_proposal);
-       rdns_proposal->next_timeout = 1;
+       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;
@@ -2108,44 +2342,12 @@ gen_rdns_proposal(struct slaacd_iface *iface, struct radv *ra)
        }
 
        LIST_INSERT_HEAD(&iface->rdns_proposals, rdns_proposal, entries);
-       propose_rdns(rdns_proposal);
+       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
-propose_rdns(struct rdns_proposal *rdns_proposal)
-{
-       struct timeval                   tv;
-       enum proposal_state              prev_state;
-
-       if (rdns_proposal->rdns_lifetime > MAX_RTR_SOLICITATIONS *
-           (RTR_SOLICITATION_INTERVAL + 1)) {
-               rdns_proposal->next_timeout = rdns_proposal->rdns_lifetime -
-                   MAX_RTR_SOLICITATIONS * (RTR_SOLICITATION_INTERVAL + 1);
-               tv.tv_sec = rdns_proposal->next_timeout;
-               tv.tv_usec = arc4random_uniform(1000000);
-               evtimer_add(&rdns_proposal->timer, &tv);
-               log_debug("%s: %d, scheduling new timeout in %llds.%06ld",
-                   __func__, rdns_proposal->if_index, tv.tv_sec, tv.tv_usec);
-       } else
-               rdns_proposal->next_timeout = 0;
-
-       prev_state = rdns_proposal->state;
-
-       rdns_proposal->state = PROPOSAL_SENT;
-
-       log_debug("%s: %d", __func__, rdns_proposal->if_index);
-
-       if (prev_state == PROPOSAL_SENT || prev_state ==
-           PROPOSAL_NEARLY_EXPIRED) {
-               /* nothing to do here rDNS proposals do not expire */
-               return;
-       }
-       compose_rdns_proposal(rdns_proposal->if_index, rdns_proposal->rdomain);
-}
-
 void
 compose_rdns_proposal(uint32_t if_index, int rdomain)
 {
@@ -2160,6 +2362,11 @@ compose_rdns_proposal(uint32_t if_index, int 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++] =
@@ -2179,31 +2386,36 @@ free_rdns_proposal(struct rdns_proposal *rdns_proposal)
 
        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
-start_probe(struct slaacd_iface *iface)
+withdraw_rdns(struct rdns_proposal *rdns_proposal)
 {
-       struct timeval  tv;
-
-       iface->state = IF_DELAY;
-       iface->probes = 0;
-
-       tv.tv_sec = 0;
-       tv.tv_usec = arc4random_uniform(MAX_RTR_SOLICITATION_DELAY_USEC);
+       log_debug("%s: %d", __func__, rdns_proposal->if_index);
 
-       log_debug("%s: iface %d: sleeping for %ldusec", __func__,
-           iface->if_index, tv.tv_usec);
+       rdns_proposal->state = PROPOSAL_WITHDRAWN;
 
-       evtimer_add(&iface->timer, &tv);
+       /* 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 timeval           tv;
+       struct slaacd_iface     *iface = NULL;
+       struct radv             *ra = NULL;
+       struct radv_prefix      *prefix = NULL;
        const char              *hbuf;
 
        addr_proposal = (struct address_proposal *)arg;
@@ -2211,67 +2423,53 @@ address_proposal_timeout(int fd, short events, void *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],
+           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:
-               log_debug("PROPOSAL_CONFIGURED timeout: id: %lld, temporary: "
-                   "%s", addr_proposal->id, addr_proposal->temporary ?
-                   "y" : "n");
-
-               addr_proposal->next_timeout = 1;
-               addr_proposal->state = PROPOSAL_NEARLY_EXPIRED;
-
-               tv.tv_sec = 0;
-               tv.tv_usec = 0;
-               evtimer_add(&addr_proposal->timer, &tv);
-
+               addr_proposal_state_transition(addr_proposal,
+                   PROPOSAL_NEARLY_EXPIRED);
                break;
        case PROPOSAL_NEARLY_EXPIRED:
-               log_debug("%s: rl: %d", __func__,
-                   real_lifetime(&addr_proposal->uptime,
-                   addr_proposal->vltime));
-               /*
-                * we should have gotten a RTM_DELADDR from the kernel,
-                * in case we missed it, delete to not waste memory
-                */
                if (real_lifetime(&addr_proposal->uptime,
-                   addr_proposal->vltime) == 0) {
-                       evtimer_del(&addr_proposal->timer);
-                       free_address_proposal(addr_proposal);
-                       log_debug("%s: removing address proposal", __func__);
-                       break;
-               }
-
-               engine_imsg_compose_frontend(IMSG_CTL_SEND_SOLICITATION,
-                   0, &addr_proposal->if_index,
-                   sizeof(addr_proposal->if_index));
-
-               if (addr_proposal->temporary) {
-                       addr_proposal->next_timeout = 0;
-                       break; /* just let it expire */
-               }
-
-               tv.tv_sec = addr_proposal->next_timeout;
-               tv.tv_usec = arc4random_uniform(1000000);
-               addr_proposal->next_timeout *= 2;
-               evtimer_add(&addr_proposal->timer, &tv);
-               log_debug("%s: scheduling new timeout in %llds.%06ld",
-                   __func__, tv.tv_sec, tv.tv_usec);
+                   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:
-               engine_imsg_compose_frontend(IMSG_CTL_SEND_SOLICITATION,
-                   0, &addr_proposal->if_index,
-                   sizeof(addr_proposal->if_index));
-               log_debug("%s: address duplicated",
-                   __func__);
+               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]);
+                   proposal_state_name(addr_proposal->state));
        }
 }
 
@@ -2279,48 +2477,43 @@ void
 dfr_proposal_timeout(int fd, short events, void *arg)
 {
        struct dfr_proposal     *dfr_proposal;
-       struct timeval           tv;
        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]);
+           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:
-               log_debug("PROPOSAL_CONFIGURED timeout: id: %lld",
-                   dfr_proposal->id);
-
-               dfr_proposal->next_timeout = 1;
-               dfr_proposal->state = PROPOSAL_NEARLY_EXPIRED;
-
-               tv.tv_sec = 0;
-               tv.tv_usec = 0;
-               evtimer_add(&dfr_proposal->timer, &tv);
-
+               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) {
-                       free_dfr_proposal(dfr_proposal);
-                       log_debug("%s: removing dfr proposal", __func__);
-                       break;
-               }
-               engine_imsg_compose_frontend(IMSG_CTL_SEND_SOLICITATION,
-                   0, &dfr_proposal->if_index,
-                   sizeof(dfr_proposal->if_index));
-               tv.tv_sec = dfr_proposal->next_timeout;
-               tv.tv_usec = arc4random_uniform(1000000);
-               dfr_proposal->next_timeout *= 2;
-               evtimer_add(&dfr_proposal->timer, &tv);
-               log_debug("%s: scheduling new timeout in %llds.%06ld",
-                   __func__, tv.tv_sec, tv.tv_usec);
+                   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]);
+                   proposal_state_name(dfr_proposal->state));
        }
 }
 
@@ -2328,50 +2521,43 @@ void
 rdns_proposal_timeout(int fd, short events, void *arg)
 {
        struct rdns_proposal    *rdns_proposal;
-       struct timeval           tv;
        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]);
+           hbuf, proposal_state_name(rdns_proposal->state));
 
        switch (rdns_proposal->state) {
-       case PROPOSAL_SENT:
-               log_debug("PROPOSAL_SENT timeout: id: %lld",
-                   rdns_proposal->id);
-
-               rdns_proposal->next_timeout = 1;
-               rdns_proposal->state = PROPOSAL_NEARLY_EXPIRED;
-
-               tv.tv_sec = 0;
-               tv.tv_usec = 0;
-               evtimer_add(&rdns_proposal->timer, &tv);
-
+       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) {
-                       free_rdns_proposal(rdns_proposal);
-                       log_debug("%s: removing rdns proposal", __func__);
-                       compose_rdns_proposal(rdns_proposal->if_index,
-                           rdns_proposal->rdomain);
-                       break;
-               }
-               engine_imsg_compose_frontend(IMSG_CTL_SEND_SOLICITATION,
-                   0, &rdns_proposal->if_index,
-                   sizeof(rdns_proposal->if_index));
-               tv.tv_sec = rdns_proposal->next_timeout;
-               tv.tv_usec = arc4random_uniform(1000000);
-               rdns_proposal->next_timeout *= 2;
-               evtimer_add(&rdns_proposal->timer, &tv);
-               log_debug("%s: scheduling new timeout in %llds.%06ld",
-                   __func__, tv.tv_sec, tv.tv_usec);
+                   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]);
+                   proposal_state_name(rdns_proposal->state));
        }
 }
 
@@ -2379,48 +2565,17 @@ void
 iface_timeout(int fd, short events, void *arg)
 {
        struct slaacd_iface     *iface = (struct slaacd_iface *)arg;
-       struct timeval           tv;
-       struct address_proposal *addr_proposal;
-       struct dfr_proposal     *dfr_proposal;
-       struct rdns_proposal    *rdns_proposal;
 
        log_debug("%s[%d]: %s", __func__, iface->if_index,
-           if_state_name[iface->state]);
+           if_state_name(iface->state));
 
        switch (iface->state) {
-       case IF_DELAY:
-       case IF_PROBE:
-               iface->state = IF_PROBE;
-               engine_imsg_compose_frontend(IMSG_CTL_SEND_SOLICITATION, 0,
-                   &iface->if_index, sizeof(iface->if_index));
-               if (++iface->probes >= MAX_RTR_SOLICITATIONS) {
-                       iface->state = IF_DEAD;
-                       tv.tv_sec = 0;
-               } else
-                       tv.tv_sec = RTR_SOLICITATION_INTERVAL;
-               tv.tv_usec = arc4random_uniform(1000000);
-               evtimer_add(&iface->timer, &tv);
-               break;
-       case IF_DEAD:
-               while(!LIST_EMPTY(&iface->addr_proposals)) {
-                       addr_proposal = LIST_FIRST(&iface->addr_proposals);
-                       addr_proposal->state = PROPOSAL_STALE;
-                       free_address_proposal(addr_proposal);
-               }
-               while(!LIST_EMPTY(&iface->dfr_proposals)) {
-                       dfr_proposal = LIST_FIRST(&iface->dfr_proposals);
-                       dfr_proposal->state = PROPOSAL_STALE;
-                       free_dfr_proposal(dfr_proposal);
-               }
-               while(!LIST_EMPTY(&iface->rdns_proposals)) {
-                       rdns_proposal = LIST_FIRST(&iface->rdns_proposals);
-                       rdns_proposal->state = PROPOSAL_STALE;
-                       free_rdns_proposal(rdns_proposal);
-               }
-               compose_rdns_proposal(iface->if_index, iface->rdomain);
-               break;
        case IF_DOWN:
-       case IF_IDLE:
+               fatalx("%s: timeout in wrong state IF_DOWN", __func__);
+               break;
+       case IF_INIT:
+               iface_state_transition(iface, IF_INIT);
+               break;
        default:
                break;
        }
@@ -2483,15 +2638,15 @@ find_rdns_proposal_by_gw(struct slaacd_iface *iface, struct sockaddr_in6
 }
 
 struct radv_prefix *
-find_prefix(struct radv *ra, struct radv_prefix *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->prefix,
-                   sizeof(prefix->prefix)) == 0 && result->prefix_len ==
-                   prefix->prefix_len)
+               if (memcmp(&result->prefix, prefix,
+                   sizeof(result->prefix)) == 0 && result->prefix_len ==
+                   prefix_len)
                        return (result);
        }
        return (NULL);
@@ -2525,7 +2680,8 @@ merge_dad_couters(struct radv *old_ra, struct radv *new_ra)
        LIST_FOREACH(old_prefix, &old_ra->prefixes, entries) {
                if (!old_prefix->dad_counter)
                        continue;
-               if ((new_prefix = find_prefix(new_ra, old_prefix)) != NULL)
+               if ((new_prefix = find_prefix(new_ra, &old_prefix->prefix,
+                   old_prefix->prefix_len)) != NULL)
                        new_prefix->dad_counter = old_prefix->dad_counter;
        }
 }
index 4310568..24cf5c7 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: frontend.c,v 1.63 2022/03/21 16:25:47 florian Exp $   */
+/*     $OpenBSD: frontend.c,v 1.64 2022/07/12 16:54:59 florian Exp $   */
 
 /*
  * Copyright (c) 2017 Florian Obser <florian@openbsd.org>
@@ -96,7 +96,6 @@ void           unref_icmp6ev(struct iface *);
 void            set_icmp6sock(int, int);
 void            send_solicitation(uint32_t);
 #ifndef        SMALL
-void            update_autoconf_addresses(uint32_t, char*);
 const char     *flags_to_str(int);
 #endif /* SMALL */
 
@@ -613,103 +612,6 @@ update_iface(uint32_t if_index, char* if_name)
 }
 
 #ifndef        SMALL
-void
-update_autoconf_addresses(uint32_t if_index, char* if_name)
-{
-       struct in6_ifreq         ifr6;
-       struct imsg_addrinfo     imsg_addrinfo;
-       struct ifaddrs          *ifap, *ifa;
-       struct in6_addrlifetime *lifetime;
-       struct sockaddr_in6     *sin6;
-       time_t                   t;
-       int                      xflags;
-
-       if ((xflags = get_xflags(if_name)) == -1)
-               return;
-
-       if (!(xflags & (IFXF_AUTOCONF6 | IFXF_AUTOCONF6TEMP)))
-               return;
-
-       memset(&imsg_addrinfo, 0, sizeof(imsg_addrinfo));
-       imsg_addrinfo.if_index = if_index;
-
-       if (getifaddrs(&ifap) != 0)
-               fatal("getifaddrs");
-
-       for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
-               if (strcmp(if_name, ifa->ifa_name) != 0)
-                       continue;
-               if (ifa->ifa_addr == NULL)
-                       continue;
-
-               if (ifa->ifa_addr->sa_family != AF_INET6)
-                       continue;
-               sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
-               if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
-                       continue;
-
-               log_debug("%s: IP: %s", __func__, sin6_to_str(sin6));
-               imsg_addrinfo.addr = *sin6;
-
-               memset(&ifr6, 0, sizeof(ifr6));
-               strlcpy(ifr6.ifr_name, if_name, sizeof(ifr6.ifr_name));
-               memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr));
-
-               if (ioctl(ioctlsock, SIOCGIFAFLAG_IN6, (caddr_t)&ifr6) == -1) {
-                       log_warn("SIOCGIFAFLAG_IN6");
-                       continue;
-               }
-
-               if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF |
-                   IN6_IFF_TEMPORARY)))
-                       continue;
-
-               imsg_addrinfo.temporary = ifr6.ifr_ifru.ifru_flags6 &
-                   IN6_IFF_TEMPORARY ? 1 : 0;
-
-               memset(&ifr6, 0, sizeof(ifr6));
-               strlcpy(ifr6.ifr_name, if_name, sizeof(ifr6.ifr_name));
-               memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr));
-
-               if (ioctl(ioctlsock, SIOCGIFNETMASK_IN6, (caddr_t)&ifr6) ==
-                   -1) {
-                       log_warn("SIOCGIFNETMASK_IN6");
-                       continue;
-               }
-
-               imsg_addrinfo.mask = ((struct sockaddr_in6 *)&ifr6.ifr_addr)
-                   ->sin6_addr;
-
-               memset(&ifr6, 0, sizeof(ifr6));
-               strlcpy(ifr6.ifr_name, if_name, sizeof(ifr6.ifr_name));
-               memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr));
-               lifetime = &ifr6.ifr_ifru.ifru_lifetime;
-
-               if (ioctl(ioctlsock, SIOCGIFALIFETIME_IN6, (caddr_t)&ifr6) ==
-                   -1) {
-                       log_warn("SIOCGIFALIFETIME_IN6");
-                       continue;
-               }
-
-               imsg_addrinfo.vltime = ND6_INFINITE_LIFETIME;
-               imsg_addrinfo.pltime = ND6_INFINITE_LIFETIME;
-               t = time(NULL);
-
-               if (lifetime->ia6t_preferred)
-                       imsg_addrinfo.pltime = lifetime->ia6t_preferred < t ? 0
-                           : lifetime->ia6t_preferred - t;
-
-               if (lifetime->ia6t_expire)
-                       imsg_addrinfo.vltime = lifetime->ia6t_expire < t ? 0 :
-                           lifetime->ia6t_expire - t;
-
-               frontend_imsg_compose_main(IMSG_UPDATE_ADDRESS, 0,
-                   &imsg_addrinfo, sizeof(imsg_addrinfo));
-
-       }
-       freeifaddrs(ifap);
-}
-
 const char*
 flags_to_str(int flags)
 {
@@ -751,12 +653,8 @@ frontend_startup(void)
                fatalx("if_nameindex");
 
        for(ifnidx = ifnidxp; ifnidx->if_index !=0 && ifnidx->if_name != NULL;
-           ifnidx++) {
+           ifnidx++)
                update_iface(ifnidx->if_index, ifnidx->if_name);
-#ifndef        SMALL
-               update_autoconf_addresses(ifnidx->if_index, ifnidx->if_name);
-#endif /* SMALL */
-       }
 
        if_freenameindex(ifnidxp);
 }
@@ -831,16 +729,13 @@ handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info)
                xflags = get_xflags(if_name);
                if (xflags == -1 || !(xflags & (IFXF_AUTOCONF6 |
                    IFXF_AUTOCONF6TEMP))) {
-                       log_debug("RTM_IFINFO: %s(%d) no(longer) autoconf6", if_name,
-                           if_index);
+                       log_debug("RTM_IFINFO: %s(%d) no(longer) autoconf6",
+                           if_name, if_index);
                        frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0,
                            0, &if_index, sizeof(if_index));
                        remove_iface(if_index);
                } else {
                        update_iface(if_index, if_name);
-#ifndef        SMALL
-                       update_autoconf_addresses(if_index, if_name);
-#endif /* SMALL */
                }
                break;
        case RTM_IFANNOUNCE:
index e167f58..e149479 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: slaacd.c,v 1.64 2021/08/24 14:56:06 florian Exp $     */
+/*     $OpenBSD: slaacd.c,v 1.65 2022/07/12 16:54:59 florian Exp $     */
 
 /*
  * Copyright (c) 2017 Florian Obser <florian@openbsd.org>
@@ -381,7 +381,6 @@ main_dispatch_frontend(int fd, short event, void *bula)
        int                      shut = 0;
        int                      rdomain;
 #ifndef        SMALL
-       struct imsg_addrinfo     imsg_addrinfo;
        int                      verbose;
 #endif /* SMALL */
 
@@ -423,15 +422,6 @@ main_dispatch_frontend(int fd, short event, void *bula)
                        memcpy(&verbose, imsg.data, sizeof(verbose));
                        log_setverbose(verbose);
                        break;
-               case IMSG_UPDATE_ADDRESS:
-                       if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_addrinfo))
-                               fatalx("%s: IMSG_UPDATE_ADDRESS wrong length: "
-                                   "%lu", __func__, IMSG_DATA_SIZE(imsg));
-                       memcpy(&imsg_addrinfo, imsg.data,
-                           sizeof(imsg_addrinfo));
-                       main_imsg_compose_engine(IMSG_UPDATE_ADDRESS, 0,
-                           &imsg_addrinfo, sizeof(imsg_addrinfo));
-                       break;
 #endif /* SMALL */
                case IMSG_UPDATE_IF:
                        if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_ifinfo))
index d7880fb..2844f4a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: slaacd.h,v 1.37 2022/06/28 09:21:58 florian Exp $     */
+/*     $OpenBSD: slaacd.h,v 1.38 2022/07/12 16:55:00 florian Exp $     */
 
 /*
  * Copyright (c) 2017 Florian Obser <florian@openbsd.org>
@@ -51,7 +51,6 @@ enum imsg_type {
        IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSALS,
        IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSAL,
        IMSG_CTL_END,
-       IMSG_UPDATE_ADDRESS,
 #endif /* SMALL */
        IMSG_PROPOSE_RDNS,
        IMSG_REPROPOSE_RDNS,
@@ -158,15 +157,6 @@ struct ctl_engine_info_rdns_proposal {
 
 #endif /* SMALL */
 
-struct imsg_addrinfo {
-       uint32_t                if_index;
-       struct sockaddr_in6     addr;
-       struct in6_addr         mask;
-       int                     temporary;
-       uint32_t                vltime;
-       uint32_t                pltime;
-};
-
 struct imsg_propose_rdns {
        uint32_t                if_index;
        int                     rdomain;