This ties slaacd(8) and unwind(8) together.

slaacd sends RTM_PROPOSAL messages with learned nameservers that
unwind uses as forwarders (like the dhcp learned nameservers).

Whe unwind starts up it asks with a route message (RTM_PROPOSAL with
priority RPT_PROPOSAL_SOLICIT) all dynamic network configuration
daemons (currently only slaacd, but hopefully dhclient can follow!)
for proposals.

Needs really -current, you might even need to do "make includes" in
/usr/src as root.

Tests, OKs?



commit 3f8f12c06278e09b6989499bb5ddb1cbf1a24e11
Author: Florian Obser <[email protected]>
Date:   Fri Nov 8 13:54:20 2019 +0100

    Send DNS proposals on route socket when new nameservers are learned
    from router advertisements.
    unwind(8) can solicit DNS proposals by sending an empty RTM_PROPOSAL
    message with priority RTP_PROPOSAL_SOLICIT.

diff --git sbin/slaacd/engine.c sbin/slaacd/engine.c
index 2fb5271aa98..b8098900d83 100644
--- sbin/slaacd/engine.c
+++ sbin/slaacd/engine.c
@@ -104,6 +104,7 @@ const char* if_state_name[] = {
 
 enum proposal_state {
        PROPOSAL_NOT_CONFIGURED,
+       PROPOSAL_SENT,
        PROPOSAL_CONFIGURED,
        PROPOSAL_NEARLY_EXPIRED,
        PROPOSAL_WITHDRAWN,
@@ -113,6 +114,7 @@ enum proposal_state {
 
 const char* proposal_state_name[] = {
        "NOT_CONFIGURED",
+       "SENT",
        "CONFIGURED",
        "NEARLY_EXPIRED",
        "WITHDRAWN",
@@ -206,6 +208,22 @@ struct dfr_proposal {
        enum rpref                       rpref;
 };
 
+struct rdns_proposal {
+       LIST_ENTRY(rdns_proposal)        entries;
+       struct event                     timer;
+       int64_t                          id;
+       enum proposal_state              state;
+       time_t                           next_timeout;
+       int                              timeout_count;
+       struct timespec                  when;
+       struct timespec                  uptime;
+       uint32_t                         if_index;
+       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;
@@ -223,6 +241,7 @@ struct slaacd_iface {
        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;
@@ -256,11 +275,23 @@ void                       gen_dfr_proposal(struct 
slaacd_iface *, struct
 void                    configure_dfr(struct dfr_proposal *);
 void                    free_dfr_proposal(struct dfr_proposal *);
 void                    withdraw_dfr(struct dfr_proposal *);
+#ifndef        SMALL
+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(enum imsg_type,
+                            struct rdns_proposal *);
+#endif /* SMALL */
 char                   *parse_dnssl(char *, int);
 void                    update_iface_ra(struct slaacd_iface *, struct radv *);
 void                    start_probe(struct slaacd_iface *);
 void                    address_proposal_timeout(int, short, void *);
 void                    dfr_proposal_timeout(int, short, void *);
+#ifndef        SMALL
+void                    rdns_proposal_timeout(int, short, void *);
+#endif /* SMALL */
 void                    iface_timeout(int, short, void *);
 struct radv            *find_ra(struct slaacd_iface *, struct sockaddr_in6 *);
 struct address_proposal        *find_address_proposal_by_id(struct 
slaacd_iface *,
@@ -271,6 +302,12 @@ struct dfr_proposal        *find_dfr_proposal_by_id(struct 
slaacd_iface *,
                             int64_t);
 struct dfr_proposal    *find_dfr_proposal_by_gw(struct slaacd_iface *,
                             struct sockaddr_in6 *);
+#ifndef        SMALL
+struct rdns_proposal   *find_rdns_proposal_by_id(struct slaacd_iface *,
+                            int64_t);
+struct rdns_proposal   *find_rdns_proposal_by_gw(struct slaacd_iface *,
+                            struct sockaddr_in6 *);
+#endif /* SMALL */
 struct radv_prefix     *find_prefix(struct radv *, struct radv_prefix *);
 int                     engine_imsg_compose_main(int, pid_t, void *, uint16_t);
 uint32_t                real_lifetime(struct timespec *, uint32_t);
@@ -398,6 +435,9 @@ engine_dispatch_frontend(int fd, short event, void *bula)
        struct imsg_ra                   ra;
        struct address_proposal         *addr_proposal = NULL;
        struct dfr_proposal             *dfr_proposal = NULL;
+#ifndef        SMALL
+       struct rdns_proposal            *rdns_proposal = NULL;
+#endif /* SMALL */
        struct imsg_del_addr             del_addr;
        struct imsg_del_route            del_route;
        struct imsg_dup_addr             dup_addr;
@@ -539,6 +579,17 @@ engine_dispatch_frontend(int fd, short event, void *bula)
                                evtimer_add(&addr_proposal->timer, &tv);
                        }
                        break;
+#ifndef        SMALL
+               case IMSG_REPROPOSE_RDNS:
+                       LIST_FOREACH (iface, &slaacd_interfaces, entries) {
+                               LIST_FOREACH (rdns_proposal,
+                                   &iface->rdns_proposals, entries) {
+                                       compose_rdns_proposal(IMSG_PROPOSE_RDNS,
+                                           rdns_proposal);
+                               }
+                       }
+                       break;
+#endif /* SMALL */
                default:
                        log_debug("%s: unexpected imsg %d", __func__,
                            imsg.hdr.type);
@@ -655,6 +706,7 @@ engine_dispatch_main(int fd, short event, void *bula)
                                    iface, entries);
                                LIST_INIT(&iface->addr_proposals);
                                LIST_INIT(&iface->dfr_proposals);
+                               LIST_INIT(&iface->rdns_proposals);
                        } else {
                                int need_refresh = 0;
 
@@ -814,12 +866,14 @@ send_interface_info(struct slaacd_iface *iface, pid_t pid)
        struct ctl_engine_info_ra_dnssl          cei_ra_dnssl;
        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 radv_dnssl                       *dnssl;
        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;
@@ -943,6 +997,34 @@ send_interface_info(struct slaacd_iface *iface, pid_t pid)
                    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->next_timeout;
+               cei_rdns_proposal.timeout_count = rdns_proposal->timeout_count;
+               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
@@ -1001,6 +1083,9 @@ remove_slaacd_iface(uint32_t if_index)
        struct radv             *ra;
        struct address_proposal *addr_proposal;
        struct dfr_proposal     *dfr_proposal;
+#ifndef        SMALL
+       struct rdns_proposal    *rdns_proposal;
+#endif /* SMALL */
 
        iface = get_slaacd_iface_by_id(if_index);
 
@@ -1022,6 +1107,12 @@ remove_slaacd_iface(uint32_t if_index)
                dfr_proposal = LIST_FIRST(&iface->dfr_proposals);
                free_dfr_proposal(dfr_proposal);
        }
+#ifndef        SMALL
+       while(!LIST_EMPTY(&iface->rdns_proposals)) {
+               rdns_proposal = LIST_FIRST(&iface->rdns_proposals);
+               free_rdns_proposal(rdns_proposal);
+       }
+#endif /* SMALL */
        evtimer_del(&iface->timer);
        free(iface);
 }
@@ -1640,6 +1731,9 @@ void update_iface_ra(struct slaacd_iface *iface, struct 
radv *ra)
        struct radv_prefix      *prefix;
        struct address_proposal *addr_proposal;
        struct dfr_proposal     *dfr_proposal;
+#ifndef        SMALL
+       struct rdns_proposal    *rdns_proposal;
+#endif /* SMALL */
        uint32_t                 remaining_lifetime;
        int                      found, found_privacy, duplicate_found;
        const char              *hbuf;
@@ -1824,6 +1918,45 @@ void update_iface_ra(struct slaacd_iface *iface, struct 
radv *ra)
                                            1);
                        }
                }
+#ifndef        SMALL
+               rdns_proposal = find_rdns_proposal_by_gw(iface, &ra->from);
+               if (rdns_proposal) {
+                       if (real_lifetime(&rdns_proposal->uptime,
+                           rdns_proposal->rdns_lifetime) >
+                           ra->rdns_lifetime)
+                               log_warnx("ignoring router advertisement "
+                                   "lowering router lifetime");
+                       else {
+                               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_SENT:
+                               case PROPOSAL_NEARLY_EXPIRED:
+                                       log_debug("updating rdns");
+                                       propose_rdns(rdns_proposal);
+                                       break;
+                               default:
+                                       hbuf = sin6_to_str(
+                                           &rdns_proposal->from);
+                                       log_debug("%s: iface %d: %s",
+                                           __func__, iface->if_index,
+                                           hbuf);
+                                       break;
+                               }
+                       }
+               } else
+                       /* new proposal */
+                       gen_rdns_proposal(iface, ra);
+#endif /* SMALL */
        }
 }
 
@@ -2067,6 +2200,115 @@ free_dfr_proposal(struct dfr_proposal *dfr_proposal)
        free(dfr_proposal);
 }
 
+#ifndef        SMALL
+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->next_timeout = 1;
+       rdns_proposal->timeout_count = 0;
+       rdns_proposal->state = PROPOSAL_NOT_CONFIGURED;
+       rdns_proposal->when = ra->when;
+       rdns_proposal->uptime = ra->uptime;
+       rdns_proposal->if_index = iface->if_index;
+       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 sockaddr_in6));
+               if (rdns_proposal->rdns_count == MAX_RDNS_COUNT)
+                       break;
+       }
+
+       LIST_INSERT_HEAD(&iface->rdns_proposals, rdns_proposal, entries);
+       propose_rdns(rdns_proposal);
+
+       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(IMSG_PROPOSE_RDNS, rdns_proposal);
+}
+
+void
+withdraw_rdns(struct rdns_proposal *rdns_proposal)
+{
+       compose_rdns_proposal(IMSG_WITHDRAW_RDNS, rdns_proposal);
+}
+
+void
+compose_rdns_proposal(enum imsg_type type, struct rdns_proposal *rdns_proposal)
+{
+       struct imsg_propose_rdns         rdns;
+
+       rdns.if_index = rdns_proposal->if_index;
+       memcpy(&rdns.from, &rdns_proposal->from, sizeof(rdns.from));
+       rdns.rdns_lifetime = rdns_proposal->rdns_lifetime;
+       rdns.rdns_count = rdns_proposal->rdns_count;
+       memcpy(&rdns.rdns, &rdns_proposal->rdns, sizeof(rdns.rdns));
+
+       engine_imsg_compose_main(type, 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_SENT:
+       case PROPOSAL_NEARLY_EXPIRED:
+       case PROPOSAL_STALE:
+               withdraw_rdns(rdns_proposal);
+               break;
+       default:
+               break;
+       }
+       free(rdns_proposal);
+}
+#endif /* SMALL */
+
 void
 start_probe(struct slaacd_iface *iface)
 {
@@ -2210,6 +2452,58 @@ dfr_proposal_timeout(int fd, short events, void *arg)
        }
 }
 
+#ifndef        SMALL
+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]);
+
+       switch (rdns_proposal->state) {
+       case PROPOSAL_SENT:
+               log_debug("PROPOSAL_SENT timeout: id: %lld",
+                   rdns_proposal->id);
+
+               rdns_proposal->next_timeout = 1;
+               rdns_proposal->timeout_count = 0;
+               rdns_proposal->state = PROPOSAL_NEARLY_EXPIRED;
+
+               tv.tv_sec = 0;
+               tv.tv_usec = 0;
+               evtimer_add(&rdns_proposal->timer, &tv);
+
+               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__);
+                       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);
+               break;
+       default:
+               log_debug("%s: unhandled state: %s", __func__,
+                   proposal_state_name[rdns_proposal->state]);
+       }
+}
+#endif /* SMALL */
+
 void
 iface_timeout(int fd, short events, void *arg)
 {
@@ -2217,6 +2511,7 @@ iface_timeout(int fd, short events, void *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]);
@@ -2249,6 +2544,14 @@ iface_timeout(int fd, short events, void *arg)
                                dfr_proposal->state = PROPOSAL_STALE;
                                free_dfr_proposal(dfr_proposal);
                        }
+#ifndef        SMALL
+                       while(!LIST_EMPTY(&iface->rdns_proposals)) {
+                               rdns_proposal =
+                                   LIST_FIRST(&iface->rdns_proposals);
+                               rdns_proposal->state = PROPOSAL_STALE;
+                               free_rdns_proposal(rdns_proposal);
+                       }
+#endif /* SMALL */
                        break;
                case IF_DOWN:
                case IF_IDLE:
@@ -2325,6 +2628,34 @@ find_dfr_proposal_by_gw(struct slaacd_iface *iface, 
struct sockaddr_in6
        return (NULL);
 }
 
+#ifndef        SMALL
+struct rdns_proposal*
+find_rdns_proposal_by_id(struct slaacd_iface *iface, int64_t id)
+{
+       struct rdns_proposal    *rdns_proposal;
+
+       LIST_FOREACH (rdns_proposal, &iface->rdns_proposals, entries) {
+               if (rdns_proposal->id == id)
+                       return (rdns_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);
+}
+#endif /* SMALL */
 
 struct radv_prefix *
 find_prefix(struct radv *ra, struct radv_prefix *prefix)
diff --git sbin/slaacd/frontend.c sbin/slaacd/frontend.c
index 9ff6d62ac44..01cc4715515 100644
--- sbin/slaacd/frontend.c
+++ sbin/slaacd/frontend.c
@@ -425,6 +425,8 @@ frontend_dispatch_engine(int fd, short event, void *bula)
                case IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSAL:
                case IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSALS:
                case IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSAL:
+               case IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSALS:
+               case IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSAL:
                        control_imsg_relay(&imsg);
                        break;
 #endif /* SMALL */
@@ -852,6 +854,15 @@ handle_route_message(struct rt_msghdr *rtm, struct 
sockaddr **rti_info)
                    ifm->ifm_index);
 
                break;
+#ifndef        SMALL
+       case RTM_PROPOSAL:
+               if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) {
+                       log_debug("RTP_PROPOSAL_SOLICIT");
+                       frontend_imsg_compose_engine(IMSG_REPROPOSE_RDNS,
+                           0, 0, NULL, 0);
+               }
+               break;
+#endif /* SMALL */
        default:
                log_debug("unexpected RTM: %d", rtm->rtm_type);
                break;
diff --git sbin/slaacd/slaacd.c sbin/slaacd/slaacd.c
index 340424e36c0..1b16d53ba20 100644
--- sbin/slaacd/slaacd.c
+++ sbin/slaacd/slaacd.c
@@ -68,6 +68,9 @@ void  delete_address(struct imsg_configure_address *);
 void   configure_gateway(struct imsg_configure_dfr *, uint8_t);
 void   add_gateway(struct imsg_configure_dfr *);
 void   delete_gateway(struct imsg_configure_dfr *);
+#ifndef        SMALL
+void   send_rdns_proposal(struct imsg_propose_rdns *, uint8_t);
+#endif /* SMALL */
 int    get_soiikey(uint8_t *);
 
 static int     main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *);
@@ -267,7 +270,7 @@ main(int argc, char *argv[])
 
        rtfilter = ROUTE_FILTER(RTM_IFINFO) | ROUTE_FILTER(RTM_NEWADDR) |
            ROUTE_FILTER(RTM_DELADDR) | ROUTE_FILTER(RTM_DELETE) |
-           ROUTE_FILTER(RTM_CHGADDRATTR);
+           ROUTE_FILTER(RTM_CHGADDRATTR) | ROUTE_FILTER(RTM_PROPOSAL);
        if (setsockopt(frontend_routesock, AF_ROUTE, ROUTE_MSGFILTER,
            &rtfilter, sizeof(rtfilter)) == -1)
                fatal("setsockopt(ROUTE_MSGFILTER)");
@@ -477,6 +480,9 @@ main_dispatch_engine(int fd, short event, void *bula)
        struct imsg                      imsg;
        struct imsg_configure_address    address;
        struct imsg_configure_dfr        dfr;
+#ifndef        SMALL
+       struct imsg_propose_rdns         rdns;
+#endif /* SMALL */
        ssize_t                          n;
        int                              shut = 0;
 
@@ -534,6 +540,24 @@ main_dispatch_engine(int fd, short event, void *bula)
                        memcpy(&dfr, imsg.data, sizeof(dfr));
                        delete_gateway(&dfr);
                        break;
+#ifndef        SMALL
+               case IMSG_PROPOSE_RDNS:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(rdns))
+                               fatalx("%s: IMSG_PROPOSE_RDNS wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&rdns, imsg.data, sizeof(rdns));
+                       send_rdns_proposal(&rdns, RTF_UP);
+                       break;
+               case IMSG_WITHDRAW_RDNS:
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(rdns))
+                               fatalx("%s: IMSG_WITHDRAW_RDNS wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&rdns, imsg.data, sizeof(rdns));
+                       send_rdns_proposal(&rdns, 0);
+                       break;
+#endif /* SMALL */
                default:
                        log_debug("%s: error handling imsg %d", __func__,
                            imsg.hdr.type);
@@ -809,6 +833,49 @@ delete_gateway(struct imsg_configure_dfr *dfr)
 }
 
 #ifndef        SMALL
+void
+send_rdns_proposal(struct imsg_propose_rdns *rdns, uint8_t rtm_flags)
+{
+       struct rt_msghdr                 rtm;
+       struct sockaddr_rtdns            rtdns;
+       struct iovec                     iov[3];
+       long                             pad = 0;
+       int                              iovcnt = 0, padlen;
+
+       memset(&rtm, 0, sizeof(rtm));
+
+       rtm.rtm_version = RTM_VERSION;
+       rtm.rtm_type = RTM_PROPOSAL;
+       rtm.rtm_msglen = sizeof(rtm);
+       rtm.rtm_tableid = 0; /* XXX imsg->rdomain; */
+       rtm.rtm_index = rdns->if_index;
+       rtm.rtm_seq = ++rtm_seq;
+       rtm.rtm_priority = RTP_PROPOSAL_SLAAC;
+       rtm.rtm_addrs = RTA_DNS;
+       rtm.rtm_flags = rtm_flags;
+
+       iov[iovcnt].iov_base = &rtm;
+       iov[iovcnt++].iov_len = sizeof(rtm);
+
+       memset(&rtdns, 0, sizeof(rtdns));
+       rtdns.sr_family = AF_INET6;
+       rtdns.sr_len = 2 + rdns->rdns_count * sizeof(struct in6_addr);
+       memcpy(rtdns.sr_dns, rdns->rdns, sizeof(rtdns.sr_dns));
+
+       iov[iovcnt].iov_base = &rtdns;
+       iov[iovcnt++].iov_len = sizeof(rtdns);
+       rtm.rtm_msglen += sizeof(rtdns);
+       padlen = ROUNDUP(sizeof(rtdns)) - sizeof(rtdns);
+       if (padlen > 0) {
+               iov[iovcnt].iov_base = &pad;
+               iov[iovcnt++].iov_len = padlen;
+               rtm.rtm_msglen += padlen;
+       }
+
+       if (writev(routesock, iov, iovcnt) == -1)
+               log_warn("failed to send route message");
+}
+
 const char*
 sin6_to_str(struct sockaddr_in6 *sin6)
 {
diff --git sbin/slaacd/slaacd.h sbin/slaacd/slaacd.h
index 9f730cfa39a..6acbf7dfe24 100644
--- sbin/slaacd/slaacd.h
+++ sbin/slaacd/slaacd.h
@@ -27,6 +27,8 @@
 /* MAXDNAME from arpa/namesr.h */
 #define SLAACD_MAX_DNSSL       1025
 
+#define        MAX_RDNS_COUNT          8 /* max nameserver in a RTM_PROPOSAL */
+
 #define        IMSG_DATA_SIZE(imsg)    ((imsg).hdr.len - IMSG_HEADER_SIZE)
 
 static const char * const log_procnames[] = {
@@ -55,9 +57,14 @@ enum imsg_type {
        IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSAL,
        IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSALS,
        IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSAL,
+       IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSALS,
+       IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSAL,
        IMSG_CTL_END,
        IMSG_UPDATE_ADDRESS,
        IMSG_UPDATE_LINK_STATE,
+       IMSG_PROPOSE_RDNS,
+       IMSG_WITHDRAW_RDNS,
+       IMSG_REPROPOSE_RDNS,
 #endif /* SMALL */
        IMSG_CTL_SEND_SOLICITATION,
        IMSG_SOCKET_IPC,
@@ -160,6 +167,19 @@ struct ctl_engine_info_dfr_proposal {
        char                     rpref[sizeof("MEDIUM")];
 };
 
+struct ctl_engine_info_rdns_proposal {
+       int64_t                  id;
+       char                     state[sizeof("PROPOSAL_NEARLY_EXPIRED")];
+       time_t                   next_timeout;
+       int                      timeout_count;
+       struct timespec          when;
+       struct timespec          uptime;
+       struct sockaddr_in6      from;
+       uint32_t                 rdns_lifetime;
+       int                      rdns_count;
+       struct in6_addr          rdns[MAX_RDNS_COUNT];
+};
+
 struct imsg_addrinfo {
        uint32_t                if_index;
        struct ether_addr       hw_address;
@@ -175,6 +195,15 @@ struct imsg_link_state {
        uint32_t        if_index;
        int             link_state;
 };
+
+struct imsg_propose_rdns {
+       uint32_t                 if_index;
+       struct sockaddr_in6      from;
+       uint32_t                 rdns_lifetime;
+       int                      rdns_count;
+       struct sockaddr_in6      rdns[MAX_RDNS_COUNT];
+};
+
 #endif /* SMALL */
 
 struct imsg_ifinfo {
diff --git usr.sbin/slaacctl/slaacctl.c usr.sbin/slaacctl/slaacctl.c
index 9831ebcc939..a2fb2ab0596 100644
--- usr.sbin/slaacctl/slaacctl.c
+++ usr.sbin/slaacctl/slaacctl.c
@@ -180,8 +180,10 @@ show_interface_msg(struct imsg *imsg)
        struct ctl_engine_info_ra_dnssl         *cei_ra_dnssl;
        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 tm                               *t;
        struct timespec                          now, diff;
+       int                                      i;
        char                                     buf[IF_NAMESIZE], *bufp;
        char                                     hbuf[NI_MAXHOST], whenbuf[255];
        char                                     ntopbuf[INET6_ADDRSTRLEN];
@@ -331,6 +333,44 @@ show_interface_msg(struct imsg *imsg)
                else
                        printf("\n");
 
+               break;
+       case IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSALS:
+               printf("\trDNS proposals\n");
+               break;
+       case IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSAL:
+               cei_rdns_proposal = imsg->data;
+
+               if (getnameinfo((struct sockaddr *)&cei_rdns_proposal->from,
+                   cei_rdns_proposal->from.sin6_len, hbuf, sizeof(hbuf),
+                   NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV))
+                       err(1, "cannot get router IP");
+
+               printf("\t\tid: %4lld, state: %15s\n",
+                   cei_rdns_proposal->id, cei_rdns_proposal->state);
+               printf("\t\trouter: %s\n", hbuf);
+               printf("\t\trdns lifetime: %10u\n",
+                   cei_rdns_proposal->rdns_lifetime);
+               printf("\t\trdns:\n");
+               for (i = 0; i < cei_rdns_proposal->rdns_count; i++) {
+                       printf("\t\t\t%s\n", inet_ntop(AF_INET6,
+                           &cei_rdns_proposal->rdns[i], ntopbuf,
+                           INET6_ADDRSTRLEN));
+               }
+
+               if (clock_gettime(CLOCK_MONOTONIC, &now))
+                       err(1, "clock_gettime");
+
+               timespecsub(&now, &cei_rdns_proposal->uptime, &diff);
+
+               t = localtime(&cei_rdns_proposal->when.tv_sec);
+               strftime(whenbuf, sizeof(whenbuf), "%F %T", t);
+               printf("\t\tupdated: %s; %llds ago", whenbuf, diff.tv_sec);
+               if (cei_rdns_proposal->next_timeout != 0)
+                       printf(", timeout: %10llds\n",
+                           cei_rdns_proposal->next_timeout - diff.tv_sec);
+               else
+                       printf("\n");
+
                break;
        case IMSG_CTL_END:
                return (1);

commit 41bd7b26a4b6b18cc9e95832c191ec235522a3ae
Author: Florian Obser <[email protected]>
Date:   Fri Nov 8 21:55:26 2019 +0100

    Implement DNS proposals; currently only slaacd is switched over so we
    need to keep the lease file parsing.

diff --git sbin/unwind/control.c sbin/unwind/control.c
index f1135befb1c..25207cceeb2 100644
--- sbin/unwind/control.c
+++ sbin/unwind/control.c
@@ -22,6 +22,8 @@
 #include <sys/time.h>
 #include <sys/un.h>
 
+#include <net/route.h>
+
 #include <errno.h>
 #include <event.h>
 #include <imsg.h>
diff --git sbin/unwind/frontend.c sbin/unwind/frontend.c
index a5003b5ca5a..979640707d0 100644
--- sbin/unwind/frontend.c
+++ sbin/unwind/frontend.c
@@ -1017,7 +1017,9 @@ get_rtaddrs(int addrs, struct sockaddr *sa, struct 
sockaddr **rti_info)
 void
 handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info)
 {
-       char    buf[IF_NAMESIZE], *bufp;
+       struct imsg_rdns_proposal        rdns_proposal;
+       struct sockaddr_rtdns           *rtdns;
+       char                             buf[IF_NAMESIZE], *bufp;
 
        switch (rtm->rtm_type) {
        case RTM_GET:
@@ -1052,6 +1054,37 @@ handle_route_message(struct rt_msghdr *rtm, struct 
sockaddr **rti_info)
                frontend_imsg_compose_resolver(IMSG_RECHECK_RESOLVERS, 0, NULL,
                    0);
                break;
+       case RTM_PROPOSAL:
+               if (!(rtm->rtm_addrs & RTA_DNS))
+                       break;
+
+               rtdns = (struct sockaddr_rtdns*)rti_info[RTAX_DNS];
+               switch (rtdns->sr_family) {
+               case AF_INET:
+                       if ((rtdns->sr_len - 2) % sizeof(struct in_addr) != 0) {
+                               log_warnx("ignoring invalid RTM_PROPOSAL");
+                               return;
+                       }
+                       break;
+               case AF_INET6:
+                       if ((rtdns->sr_len - 2) % sizeof(struct in6_addr) != 0) 
{
+                               log_warnx("ignoring invalid RTM_PROPOSAL");
+                               return;
+                       }
+                       break;
+               default:
+                       log_warnx("ignoring invalid RTM_PROPOSAL");
+                       return;
+               }
+               rdns_proposal.if_index = rtm->rtm_index;
+               memcpy(&rdns_proposal.rtdns, rtdns, 
sizeof(rdns_proposal.rtdns));
+               if (rtm->rtm_flags & RTF_UP)
+                       frontend_imsg_compose_resolver(IMSG_ADD_DNS, 0,
+                           &rdns_proposal, sizeof(rdns_proposal));
+               else
+                       frontend_imsg_compose_resolver(IMSG_REMOVE_DNS, 0,
+                           &rdns_proposal, sizeof(rdns_proposal));
+               break;
        default:
                break;
        }
diff --git sbin/unwind/frontend.h sbin/unwind/frontend.h
index 6057b8fac17..c9496ee09fa 100644
--- sbin/unwind/frontend.h
+++ sbin/unwind/frontend.h
@@ -26,6 +26,10 @@ struct trust_anchor {
 
 TAILQ_HEAD(trust_anchor_head, trust_anchor);
 
+struct imsg_rdns_proposal {
+       uint32_t                 if_index;
+       struct sockaddr_rtdns    rtdns;
+};
 
 void            frontend(int, int);
 void            frontend_dispatch_main(int, short, void *);
diff --git sbin/unwind/resolver.c sbin/unwind/resolver.c
index 4f096aa6bc6..401400d9f96 100644
--- sbin/unwind/resolver.c
+++ sbin/unwind/resolver.c
@@ -25,6 +25,8 @@
 #include <sys/syslog.h>
 #include <sys/time.h>
 
+#include <net/route.h>
+
 #include <errno.h>
 #include <event.h>
 #include <imsg.h>
@@ -148,12 +150,17 @@ void                       trust_anchor_resolve(void);
 void                    trust_anchor_timo(int, short, void *);
 void                    trust_anchor_resolve_done(void *, int, void *, int,
                             int, char *, int);
+void                    add_autoconf_forwarders(struct imsg_rdns_proposal *);
+void                    rem_autoconf_forwarders(struct imsg_rdns_proposal *);
+struct uw_forwarder    *find_forwarder(struct uw_forwarder_head *,
+                            const char *);
 
 struct uw_conf                 *resolver_conf;
 struct imsgev                  *iev_frontend;
 struct imsgev                  *iev_captiveportal;
 struct imsgev                  *iev_main;
 struct uw_forwarder_head        dhcp_forwarder_list;
+struct uw_forwarder_head        autoconf_forwarder_list;
 struct uw_resolver             *resolvers[UW_RES_NONE];
 struct timeval                  captive_portal_check_tv =
                                     {PORTAL_CHECK_SEC, 0};
@@ -340,6 +347,7 @@ resolver(int debug, int verbose)
        new_recursor();
 
        TAILQ_INIT(&dhcp_forwarder_list);
+       TAILQ_INIT(&autoconf_forwarder_list);
        TAILQ_INIT(&trust_anchors);
        TAILQ_INIT(&new_trust_anchors);
 
@@ -396,16 +404,16 @@ resolver_imsg_compose_captiveportal(int type, pid_t pid, 
void *data,
 void
 resolver_dispatch_frontend(int fd, short event, void *bula)
 {
-       struct imsgev           *iev = bula;
-       struct imsgbuf          *ibuf;
-       struct imsg              imsg;
-       struct query_imsg       *query_imsg;
-       struct uw_resolver      *res;
-       enum uw_resolver_type    type;
-       ssize_t                  n;
-       int                      shut = 0, verbose, err;
-       int                      update_resolvers;
-       char                    *ta;
+       struct imsgev                   *iev = bula;
+       struct imsgbuf                  *ibuf;
+       struct imsg                      imsg;
+       struct query_imsg               *query_imsg;
+       struct uw_resolver              *res;
+       enum uw_resolver_type            type;
+       ssize_t                          n;
+       int                              shut = 0, verbose, err;
+       int                              update_resolvers;
+       char                            *ta;
 
        ibuf = &iev->ibuf;
 
@@ -514,6 +522,22 @@ resolver_dispatch_frontend(int fd, short event, void *bula)
                case IMSG_RECHECK_RESOLVERS:
                        schedule_recheck_all_resolvers();
                        break;
+               case IMSG_ADD_DNS:
+                       if (IMSG_DATA_SIZE(imsg) !=
+                           sizeof(struct imsg_rdns_proposal))
+                               fatalx("%s: IMSG_ADD_DNS wrong length: %lu",
+                                   __func__, IMSG_DATA_SIZE(imsg));
+                       add_autoconf_forwarders((struct imsg_rdns_proposal *)
+                           imsg.data);
+                       break;
+               case IMSG_REMOVE_DNS:
+                       if (IMSG_DATA_SIZE(imsg) !=
+                           sizeof(struct imsg_rdns_proposal))
+                               fatalx("%s: IMSG_ADD_DNS wrong length: %lu",
+                                   __func__, IMSG_DATA_SIZE(imsg));
+                       rem_autoconf_forwarders((struct imsg_rdns_proposal *)
+                           imsg.data);
+                       break;
                default:
                        log_debug("%s: unexpected imsg %d", __func__,
                            imsg.hdr.type);
@@ -920,7 +944,8 @@ new_forwarders(int oppdot)
        free_resolver(resolvers[UW_RES_DHCP]);
        resolvers[UW_RES_DHCP] = NULL;
 
-       if (TAILQ_EMPTY(&dhcp_forwarder_list))
+       if (TAILQ_EMPTY(&dhcp_forwarder_list) &&
+           TAILQ_EMPTY(&autoconf_forwarder_list))
                return;
 
        if (TAILQ_EMPTY(&trust_anchors))
@@ -938,7 +963,8 @@ new_asr_forwarders(void)
        free_resolver(resolvers[UW_RES_ASR]);
        resolvers[UW_RES_ASR] = NULL;
 
-       if (TAILQ_EMPTY(&dhcp_forwarder_list))
+       if (TAILQ_EMPTY(&dhcp_forwarder_list) &&
+           TAILQ_EMPTY(&autoconf_forwarder_list))
                return;
 
        log_debug("%s: create_resolver", __func__);
@@ -1007,7 +1033,8 @@ create_resolver(enum uw_resolver_type type, int oppdot)
 
        switch (type) {
        case UW_RES_ASR:
-               if (TAILQ_EMPTY(&dhcp_forwarder_list)) {
+               if (TAILQ_EMPTY(&dhcp_forwarder_list) &&
+                   TAILQ_EMPTY(&autoconf_forwarder_list)) {
                        free(res);
                        return (NULL);
                }
@@ -1022,6 +1049,18 @@ create_resolver(enum uw_resolver_type type, int oppdot)
                        }
                        free(tmp);
                }
+               TAILQ_FOREACH(uw_forwarder, &autoconf_forwarder_list, entry) {
+                       tmp = resolv_conf;
+                       if (asprintf(&resolv_conf, "%snameserver %s\n", tmp ==
+                           NULL ? "" : tmp, uw_forwarder->name) == -1) {
+                               free(tmp);
+                               free(res);
+                               log_warnx("could not create asr context");
+                               return (NULL);
+                       }
+                       free(tmp);
+               }
+
                log_debug("%s: UW_RES_ASR resolv.conf: %s", __func__,
                    resolv_conf);
                if ((res->asr_ctx = asr_resolver_from_string(resolv_conf)) ==
@@ -1093,11 +1132,15 @@ create_resolver(enum uw_resolver_type type, int oppdot)
                res->oppdot = oppdot;
                if (oppdot) {
                        set_forwarders_oppdot(res, &dhcp_forwarder_list, 853);
+                       set_forwarders_oppdot(res, &autoconf_forwarder_list,
+                           853);
                        ub_ctx_set_option(res->ctx, "tls-cert-bundle:",
                            tls_default_ca_cert_file());
                        ub_ctx_set_tls(res->ctx, 1);
-               } else
+               } else {
                        set_forwarders_oppdot(res, &dhcp_forwarder_list, 53);
+                       set_forwarders_oppdot(res, &autoconf_forwarder_list, 
53);
+               }
                break;
        case UW_RES_FORWARDER:
                res->oppdot = oppdot;
@@ -1834,3 +1877,138 @@ out:
        resolver_unref(res);
        evtimer_add(&trust_anchor_timer, &tv);
 }
+
+void
+add_autoconf_forwarders(struct imsg_rdns_proposal *rdns_proposal)
+{
+       struct uw_forwarder     *uw_forwarder;
+       int                      i, rdns_count, af, changed = 0;
+       char                     ntopbuf[INET6_ADDRSTRLEN], *src;
+       const char              *ns;
+
+       af = rdns_proposal->rtdns.sr_family;
+       src = rdns_proposal->rtdns.sr_dns;
+
+       switch (af) {
+       case AF_INET:
+               rdns_count = (rdns_proposal->rtdns.sr_len -
+                   offsetof(struct sockaddr_rtdns, sr_dns)) /
+                   sizeof(struct in_addr);
+               break;
+       case AF_INET6:
+               rdns_count = (rdns_proposal->rtdns.sr_len -
+                   offsetof(struct sockaddr_rtdns, sr_dns)) /
+                   sizeof(struct in_addr);
+               break;
+       default:
+               log_warnx("%s: unsupported address family: %d", __func__, af);
+               return;
+       }
+
+       for (i = 0; i < rdns_count; i++) {
+               switch (af) {
+               case AF_INET:
+                       ns = inet_ntop(af, (struct in_addr *)src, ntopbuf,
+                           INET6_ADDRSTRLEN);
+                       src += sizeof(struct in_addr);
+                       break;
+               case AF_INET6:
+                       ns = inet_ntop(af, (struct in6_addr *)src, ntopbuf,
+                           INET6_ADDRSTRLEN);
+                       src += sizeof(struct in6_addr);
+               }
+
+               log_debug("%s: %s", __func__, ns);
+               if (find_forwarder(&autoconf_forwarder_list, ns) == NULL) {
+                       if ((uw_forwarder = calloc(1, sizeof(struct
+                           uw_forwarder))) == NULL)
+                               fatal(NULL);
+                       if (strlcpy(uw_forwarder->name, ns,
+                           sizeof(uw_forwarder->name)) >=
+                           sizeof(uw_forwarder->name))
+                               fatalx("strlcpy");
+                       TAILQ_INSERT_TAIL(&autoconf_forwarder_list,
+                           uw_forwarder, entry);
+                       changed = 1;
+               }
+       }
+       if (changed) {
+               new_forwarders(0);
+               new_asr_forwarders();
+               if (resolver_conf->captive_portal_auto)
+                       check_captive_portal(1);
+               log_debug("%s: forwarders changed", __func__);
+       } else
+               log_debug("%s: forwarders didn't change", __func__);
+}
+
+void
+rem_autoconf_forwarders(struct imsg_rdns_proposal *rdns_proposal)
+{
+       struct uw_forwarder             *uw_forwarder;
+       int                              i, rdns_count, af, changed = 0;
+       char                             ntopbuf[INET6_ADDRSTRLEN], *src;
+       const char                      *ns;
+
+       af = rdns_proposal->rtdns.sr_family;
+
+       src = rdns_proposal->rtdns.sr_dns;
+
+       switch (af) {
+       case AF_INET:
+               rdns_count = (rdns_proposal->rtdns.sr_len -
+                   offsetof(struct sockaddr_rtdns, sr_dns)) /
+                   sizeof(struct in_addr);
+               break;
+       case AF_INET6:
+               rdns_count = (rdns_proposal->rtdns.sr_len -
+                   offsetof(struct sockaddr_rtdns, sr_dns)) /
+                   sizeof(struct in_addr);
+               break;
+       default:
+               log_warnx("%s: unsupported address family: %d", __func__, af);
+               return;
+       }
+
+       for (i = 0; i < rdns_count; i++) {
+               switch (af) {
+               case AF_INET:
+                       ns = inet_ntop(af, (struct in_addr *)src, ntopbuf,
+                           INET6_ADDRSTRLEN);
+                       src += sizeof(struct in_addr);
+                       break;
+               case AF_INET6:
+                       ns = inet_ntop(af, (struct in6_addr *)src, ntopbuf,
+                           INET6_ADDRSTRLEN);
+                       src += sizeof(struct in6_addr);
+               }
+
+               log_debug("%s: %s", __func__, ns);
+               if ((uw_forwarder = find_forwarder(&autoconf_forwarder_list,
+                   ns)) != NULL) {
+                       TAILQ_REMOVE(&autoconf_forwarder_list, uw_forwarder,
+                           entry);
+                       changed = 1;
+               }
+       }
+
+       if (changed) {
+               new_forwarders(0);
+               new_asr_forwarders();
+               if (resolver_conf->captive_portal_auto)
+                       check_captive_portal(1);
+               log_debug("%s: forwarders changed", __func__);
+       } else
+               log_debug("%s: forwarders didn't change", __func__);
+}
+
+struct uw_forwarder *
+find_forwarder(struct uw_forwarder_head *list, const char *name) {
+       struct uw_forwarder     *uw_forwarder;
+
+       TAILQ_FOREACH(uw_forwarder, list, entry) {
+               if (strcmp(uw_forwarder->name, name) == 0)
+                       return uw_forwarder;
+       }
+       return NULL;
+}
diff --git sbin/unwind/unwind.c sbin/unwind/unwind.c
index 4c96dcaf31c..557664a957c 100644
--- sbin/unwind/unwind.c
+++ sbin/unwind/unwind.c
@@ -73,6 +73,7 @@ int           main_reload(void);
 int            main_sendall(enum imsg_type, void *, uint16_t);
 void           open_dhcp_lease(int);
 void           open_ports(void);
+void           solicit_dns_proposals(void);
 void           resolve_captive_portal(void);
 void           resolve_captive_portal_done(struct asr_result *, void *);
 void           send_blocklist_fd(void);
@@ -89,6 +90,8 @@ pid_t          captiveportal_pid;
 
 uint32_t        cmd_opts;
 
+int             routesock;
+
 void
 main_sig_handler(int sig, short event, void *arg)
 {
@@ -298,6 +301,11 @@ main(int argc, char *argv[])
            &rtfilter, sizeof(rtfilter)) == -1)
                fatal("setsockopt(ROUTE_MSGFILTER)");
 
+       if ((routesock = socket(AF_ROUTE, SOCK_RAW | SOCK_CLOEXEC |
+           SOCK_NONBLOCK, AF_INET6)) == -1)
+               fatal("route socket");
+       shutdown(SHUT_RD, routesock);
+
        if ((ta_fd = open(TRUST_ANCHOR_FILE, O_RDWR | O_CREAT, 0644)) == -1)
                log_warn("%s", TRUST_ANCHOR_FILE);
 
@@ -441,6 +449,7 @@ main_dispatch_frontend(int fd, short event, void *bula)
 
                switch (imsg.hdr.type) {
                case IMSG_STARTUP_DONE:
+                       solicit_dns_proposals();
                        open_ports();
                        break;
                case IMSG_CTL_RELOAD:
@@ -954,6 +963,30 @@ open_ports(void)
                main_imsg_compose_frontend_fd(IMSG_UDP6SOCK, 0, udp6sock);
 }
 
+void
+solicit_dns_proposals(void)
+{
+       struct rt_msghdr                 rtm;
+       struct iovec                     iov[1];
+       int                              iovcnt = 0;
+
+       memset(&rtm, 0, sizeof(rtm));
+
+       rtm.rtm_version = RTM_VERSION;
+       rtm.rtm_type = RTM_PROPOSAL;
+       rtm.rtm_msglen = sizeof(rtm);
+       rtm.rtm_tableid = 0;
+       rtm.rtm_index = 0;
+       rtm.rtm_seq = arc4random();
+       rtm.rtm_priority = RTP_PROPOSAL_SOLICIT;
+
+       iov[iovcnt].iov_base = &rtm;
+       iov[iovcnt++].iov_len = sizeof(rtm);
+
+       if (writev(routesock, iov, iovcnt) == -1)
+               log_warn("failed to send solicitation");
+}
+
 void
 resolve_captive_portal(void)
 {
diff --git sbin/unwind/unwind.h sbin/unwind/unwind.h
index e99a0714c26..1a8bffb9250 100644
--- sbin/unwind/unwind.h
+++ sbin/unwind/unwind.h
@@ -121,6 +121,8 @@ enum imsg_type {
        IMSG_RECHECK_RESOLVERS,
        IMSG_RESOLVE_CAPTIVE_PORTAL,
        IMSG_BLFD,
+       IMSG_ADD_DNS,
+       IMSG_REMOVE_DNS,
 };
 
 struct uw_forwarder {

-- 
I'm not entirely sure you are real.

Reply via email to