Add client side support for DNS configuration. Use RTM_PROPOSAL_STATIC
authortobhe <tobhe@openbsd.org>
Wed, 1 Sep 2021 15:30:06 +0000 (15:30 +0000)
committertobhe <tobhe@openbsd.org>
Wed, 1 Sep 2021 15:30:06 +0000 (15:30 +0000)
route messages to propose the name server to resolvd(8).
For now, iked will only propose a single name server from the first
established connection.

Automatic name server configuration is enabled by default for policies using
the 'iface' option.

discussed with deraadt@
ok for the DNS parts florian@
ok for the rest patrick@

sbin/iked/config.c
sbin/iked/iked.c
sbin/iked/iked.h
sbin/iked/ikev2.c
sbin/iked/ikev2_msg.c
sbin/iked/ikev2_pld.c
sbin/iked/policy.c
sbin/iked/types.h
sbin/iked/vroute.c

index 60f6443..029ee2b 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: config.c,v 1.79 2021/05/13 15:20:48 tobhe Exp $       */
+/*     $OpenBSD: config.c,v 1.80 2021/09/01 15:30:06 tobhe Exp $       */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -174,6 +174,7 @@ config_free_sa(struct iked *env, struct iked_sa *sa)
 
        free(sa->sa_cp_addr);
        free(sa->sa_cp_addr6);
+       free(sa->sa_cp_dns);
 
        free(sa->sa_tag);
        free(sa);
index 777a228..4cd1320 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: iked.c,v 1.57 2021/05/13 15:20:48 tobhe Exp $ */
+/*     $OpenBSD: iked.c,v 1.58 2021/09/01 15:30:06 tobhe Exp $ */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -459,6 +459,9 @@ parent_dispatch_ikev2(int fd, struct privsep_proc *p, struct imsg *imsg)
        case IMSG_IF_ADDADDR:
        case IMSG_IF_DELADDR:
                return (vroute_getaddr(env, imsg));
+       case IMSG_VDNS_ADD:
+       case IMSG_VDNS_DEL:
+               return (vroute_getdns(env, imsg));
        case IMSG_VROUTE_ADD:
        case IMSG_VROUTE_DEL:
                return (vroute_getroute(env, imsg));
index e9aa454..02d2363 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: iked.h,v 1.192 2021/06/23 12:11:40 tobhe Exp $        */
+/*     $OpenBSD: iked.h,v 1.193 2021/09/01 15:30:06 tobhe Exp $        */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -429,6 +429,7 @@ struct iked_sa {
        int                              sa_cp;         /* XXX */
        struct iked_addr                *sa_cp_addr;    /* requested address */
        struct iked_addr                *sa_cp_addr6;   /* requested address */
+       struct iked_addr                *sa_cp_dns;     /* requested dns */
 
        struct iked_policy              *sa_policy;
        struct timeval                   sa_timecreated;
@@ -611,6 +612,7 @@ struct iked_message {
        int                      msg_cp;
        struct iked_addr        *msg_cp_addr;   /* requested address */
        struct iked_addr        *msg_cp_addr6;  /* requested address */
+       struct iked_addr        *msg_cp_dns;    /* requested dns */
 
        /* MOBIKE */
        int                      msg_update_sa_addresses;
@@ -752,6 +754,7 @@ struct iked {
 
        int                              sc_pfkey;      /* ike process */
        struct event                     sc_pfkeyev;
+       struct event                     sc_routeev;
        uint8_t                          sc_certreqtype;
        struct ibuf                     *sc_certreq;
        void                            *sc_vroute;
@@ -975,6 +978,8 @@ void vroute_init(struct iked *);
 int vroute_setaddr(struct iked *, int, struct sockaddr *, int, unsigned int);
 void vroute_cleanup(struct iked *);
 int vroute_getaddr(struct iked *, struct imsg *);
+int vroute_setdns(struct iked *, int, struct sockaddr *, unsigned int);
+int vroute_getdns(struct iked *, struct imsg *);
 int vroute_setaddroute(struct iked *, uint8_t, struct sockaddr *,
     uint8_t, struct sockaddr *);
 int vroute_setcloneroute(struct iked *, uint8_t, struct sockaddr *,
index c941351..5cbe1c4 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2.c,v 1.325 2021/06/29 15:39:20 tobhe Exp $       */
+/*     $OpenBSD: ikev2.c,v 1.326 2021/09/01 15:30:06 tobhe Exp $       */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -998,6 +998,13 @@ ikev2_ike_auth_recv(struct iked *env, struct iked_sa *sa,
                        log_info("%s: obtained lease: %s", SPI_SA(sa, __func__),
                            print_host((struct sockaddr *)&sa->sa_cp_addr6->addr, NULL, 0));
                }
+               if (msg->msg_cp_dns) {
+                       sa->sa_cp_dns = msg->msg_cp_dns;
+                       msg->msg_cp_dns = NULL;
+                       log_debug("%s: DNS: %s", __func__,
+                           print_host((struct sockaddr *)&sa->sa_cp_dns->addr,
+                           NULL, 0));
+               }
                sa->sa_cp = msg->msg_cp;
        }
 
@@ -4508,6 +4515,8 @@ ikev2_ikesa_enable(struct iked *env, struct iked_sa *sa, struct iked_sa *nsa)
        sa->sa_cp_addr = NULL;
        nsa->sa_cp_addr6 = sa->sa_cp_addr6;
        sa->sa_cp_addr6 = NULL;
+       nsa->sa_cp_dns = sa->sa_cp_dns;
+       sa->sa_cp_dns = NULL;
        /* Transfer other attributes */
         if (sa->sa_dstid_entry_valid) {
                sa_dstid_remove(env, sa);
index ad633a7..32b7399 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2_msg.c,v 1.77 2020/10/29 21:49:58 tobhe Exp $    */
+/*     $OpenBSD: ikev2_msg.c,v 1.78 2021/09/01 15:30:06 tobhe Exp $    */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -197,6 +197,7 @@ ikev2_msg_cleanup(struct iked *env, struct iked_message *msg)
                free(msg->msg_eap.eam_user);
                free(msg->msg_cp_addr);
                free(msg->msg_cp_addr6);
+               free(msg->msg_cp_dns);
 
                msg->msg_nonce = NULL;
                msg->msg_ke = NULL;
@@ -209,6 +210,7 @@ ikev2_msg_cleanup(struct iked *env, struct iked_message *msg)
                msg->msg_eap.eam_user = NULL;
                msg->msg_cp_addr = NULL;
                msg->msg_cp_addr6 = NULL;
+               msg->msg_cp_dns = NULL;
 
                config_free_proposals(&msg->msg_proposals, 0);
                while ((cr = SIMPLEQ_FIRST(&msg->msg_certreqs))) {
index 026d245..d1a8fee 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2_pld.c,v 1.117 2021/02/19 21:52:53 tobhe Exp $   */
+/*     $OpenBSD: ikev2_pld.c,v 1.118 2021/09/01 15:30:06 tobhe Exp $   */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -1842,6 +1842,7 @@ ikev2_pld_cp(struct iked *env, struct ikev2_payload *pld,
        uint8_t                 *ptr;
        size_t                   len;
        uint8_t                  buf[128];
+       int                      cfg_type;
 
        if (ikev2_validate_cp(msg, offset, left, &cp))
                return (-1);
@@ -1878,8 +1879,10 @@ ikev2_pld_cp(struct iked *env, struct ikev2_payload *pld,
 
                print_hex(ptr, sizeof(*cfg), betoh16(cfg->cfg_length));
 
-               switch (betoh16(cfg->cfg_type)) {
+               cfg_type = betoh16(cfg->cfg_type);
+               switch (cfg_type) {
                case IKEV2_CFG_INTERNAL_IP4_ADDRESS:
+               case IKEV2_CFG_INTERNAL_IP4_DNS:
                        if (!ikev2_msg_frompeer(msg))
                                break;
                        if (betoh16(cfg->cfg_length) == 0)
@@ -1891,8 +1894,20 @@ ikev2_pld_cp(struct iked *env, struct ikev2_payload *pld,
                                    __func__, betoh16(cfg->cfg_length), 4);
                                return (-1);
                        }
-                       if (msg->msg_parent->msg_cp_addr != NULL) {
-                               log_debug("%s: address already set", __func__);
+                       switch(cfg_type) {
+                       case IKEV2_CFG_INTERNAL_IP4_ADDRESS:
+                               if (msg->msg_parent->msg_cp_addr != NULL) {
+                                       log_debug("%s: address already set", __func__);
+                                       goto skip;
+                               }
+                               break;
+                       case IKEV2_CFG_INTERNAL_IP4_DNS:
+                               if (msg->msg_parent->msg_cp_dns != NULL) {
+                                       log_debug("%s: dns already set", __func__);
+                                       goto skip;
+                               }
+                               break;
+                       default:
                                break;
                        }
                        if ((addr = calloc(1, sizeof(*addr))) == NULL) {
@@ -1907,22 +1922,42 @@ ikev2_pld_cp(struct iked *env, struct ikev2_payload *pld,
                        print_host((struct sockaddr *)in4, (char *)buf,
                            sizeof(buf));
                        log_debug("%s: cfg %s", __func__, buf);
-                       msg->msg_parent->msg_cp_addr = addr;
+                       switch(cfg_type) {
+                       case IKEV2_CFG_INTERNAL_IP4_ADDRESS:
+                               msg->msg_parent->msg_cp_addr = addr;
+                               log_debug("%s: IP4_ADDRESS %s", __func__, buf);
+                               break;
+                       case IKEV2_CFG_INTERNAL_IP4_DNS:
+                               msg->msg_parent->msg_cp_dns = addr;
+                               log_debug("%s: IP4_DNS %s", __func__, buf);
+                               break;
+                       }
                        break;
                case IKEV2_CFG_INTERNAL_IP6_ADDRESS:
+               case IKEV2_CFG_INTERNAL_IP6_DNS:
                        if (!ikev2_msg_frompeer(msg))
                                break;
                        if (betoh16(cfg->cfg_length) == 0)
                                break;
                        /* XXX multiple-valued */
-                       if (betoh16(cfg->cfg_length) < 16 + 1) {
+                       if (betoh16(cfg->cfg_length) < 16) {
                                log_debug("%s: malformed payload: too short "
                                    "for ipv6 addr w/prefixlen (%u < %u)",
-                                   __func__, betoh16(cfg->cfg_length), 16 + 1);
+                                   __func__, betoh16(cfg->cfg_length), 16);
                                return (-1);
                        }
-                       if (msg->msg_parent->msg_cp_addr6 != NULL) {
-                               log_debug("%s: address already set", __func__);
+                       switch(cfg_type) {
+                       case IKEV2_CFG_INTERNAL_IP6_ADDRESS:
+                               if (msg->msg_parent->msg_cp_addr6 != NULL) {
+                                       log_debug("%s: address6 already set", __func__);
+                                       goto skip;
+                               }
+                               break;
+                       case IKEV2_CFG_INTERNAL_IP6_DNS:
+                               if (msg->msg_parent->msg_cp_dns != NULL) {
+                                       log_debug("%s: dns already set", __func__);
+                                       goto skip;
+                               }
                                break;
                        }
                        if ((addr = calloc(1, sizeof(*addr))) == NULL) {
@@ -1937,10 +1972,20 @@ ikev2_pld_cp(struct iked *env, struct ikev2_payload *pld,
                        print_host((struct sockaddr *)in6, (char *)buf,
                            sizeof(buf));
                        log_debug("%s: cfg %s/%d", __func__, buf, ptr[16]);
-                       msg->msg_parent->msg_cp_addr6 = addr;
+                       switch(cfg_type) {
+                       case IKEV2_CFG_INTERNAL_IP6_ADDRESS:
+                               msg->msg_parent->msg_cp_addr6 = addr;
+                               log_debug("%s: IP6_ADDRESS %s", __func__, buf);
+                               break;
+                       case IKEV2_CFG_INTERNAL_IP6_DNS:
+                               msg->msg_parent->msg_cp_dns = addr;
+                               log_debug("%s: IP6_DNS %s", __func__, buf);
+                               break;
+                       }
                        break;
                }
 
+ skip:
                ptr += betoh16(cfg->cfg_length);
                len -= betoh16(cfg->cfg_length);
        }
index 57077c4..5176301 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: policy.c,v 1.82 2021/06/23 12:11:40 tobhe Exp $       */
+/*     $OpenBSD: policy.c,v 1.83 2021/09/01 15:30:06 tobhe Exp $       */
 
 /*
  * Copyright (c) 2020-2021 Tobias Heider <tobhe@openbsd.org>
@@ -680,6 +680,13 @@ sa_configure_iface(struct iked *env, struct iked_sa *sa, int add)
        if (sa->sa_policy == NULL || sa->sa_policy->pol_iface == 0)
                return (0);
 
+       if (sa->sa_cp_dns) {
+               if (vroute_setdns(env, add,
+                   (struct sockaddr *)&sa->sa_cp_dns->addr,
+                   sa->sa_policy->pol_iface) != 0)
+                       return (-1);
+       }
+
        if (!sa->sa_cp_addr && !sa->sa_cp_addr6)
                return (0);
 
index b16d04c..18f8304 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: types.h,v 1.44 2021/08/03 12:46:30 tobhe Exp $        */
+/*     $OpenBSD: types.h,v 1.45 2021/09/01 15:30:06 tobhe Exp $        */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -119,6 +119,8 @@ enum imsg_type {
        IMSG_VROUTE_ADD,
        IMSG_VROUTE_DEL,
        IMSG_VROUTE_CLONE,
+       IMSG_VDNS_ADD,
+       IMSG_VDNS_DEL,
        IMSG_OCSP_FD,
        IMSG_OCSP_CFG,
        IMSG_AUTH,
index 3934cf7..b893da8 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: vroute.c,v 1.12 2021/06/23 12:21:23 tobhe Exp $       */
+/*     $OpenBSD: vroute.c,v 1.13 2021/09/01 15:30:06 tobhe Exp $       */
 
 /*
  * Copyright (c) 2021 Tobias Heider <tobhe@openbsd.org>
@@ -35,6 +35,7 @@
 
 #include <iked.h>
 
+#define ROUTE_SOCKET_BUF_SIZE  16384
 #define IKED_VROUTE_PRIO       6
 
 #define ROUNDUP(a) (a>0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
@@ -44,10 +45,14 @@ int vroute_setroute(struct iked *, uint8_t, struct sockaddr *, uint8_t,
 int vroute_doroute(struct iked *, int, int, int, uint8_t, struct sockaddr *,
     struct sockaddr *, struct sockaddr *, int *);
 int vroute_doaddr(struct iked *, char *, struct sockaddr *, struct sockaddr *, int);
+int vroute_dodns(struct iked *, struct sockaddr *, int, unsigned int);
 void vroute_cleanup(struct iked *);
+void vroute_rtmsg_cb(int, short, void *);
 
 void vroute_insertaddr(struct iked *, int, struct sockaddr *, struct sockaddr *);
 void vroute_removeaddr(struct iked *, int, struct sockaddr *, struct sockaddr *);
+void vroute_insertdns(struct iked *, int, struct sockaddr *);
+void vroute_removedns(struct iked *, int, struct sockaddr *);
 void vroute_insertroute(struct iked *, int, struct sockaddr *, struct sockaddr *);
 void vroute_removeroute(struct iked *, int, struct sockaddr *, struct sockaddr *);
 
@@ -68,14 +73,21 @@ struct vroute_route {
 };
 TAILQ_HEAD(vroute_routes, vroute_route);
 
+struct vroute_dns {
+       struct  sockaddr_storage        vd_addr;
+       int                             vd_ifidx;
+};
+
 struct iked_vroute_sc {
-       struct vroute_addrs     ivr_addrs;
-       struct vroute_routes    ivr_routes;
-       int                     ivr_iosock;
-       int                     ivr_iosock6;
-       int                     ivr_rtsock;
-       int                     ivr_rtseq;
-       pid_t                   ivr_pid;
+       struct vroute_addrs      ivr_addrs;
+       struct vroute_routes     ivr_routes;
+       struct vroute_dns       *ivr_dns;
+       struct event             ivr_routeev;
+       int                      ivr_iosock;
+       int                      ivr_iosock6;
+       int                      ivr_rtsock;
+       int                      ivr_rtseq;
+       pid_t                    ivr_pid;
 };
 
 struct vroute_msg {
@@ -86,10 +98,58 @@ struct vroute_msg {
 int vroute_process(struct iked *, int msglen, struct vroute_msg *,
     struct sockaddr *, struct sockaddr *, struct sockaddr *, int *);
 
+void
+vroute_rtmsg_cb(int fd, short events, void *arg)
+{
+       struct iked             *env = (struct iked *) arg;
+       struct iked_vroute_sc   *ivr = env->sc_vroute;
+       static uint8_t          *buf;
+       struct rt_msghdr        *rtm;
+       ssize_t                  n;
+
+       if (buf == NULL) {
+               buf = malloc(ROUTE_SOCKET_BUF_SIZE);
+               if (buf == NULL)
+                       fatal("malloc");
+       }
+       rtm = (struct rt_msghdr *)buf;
+       if ((n = read(fd, buf, ROUTE_SOCKET_BUF_SIZE)) == -1) {
+               if (errno == EAGAIN || errno == EINTR)
+                       return;
+               log_warn("%s: read error", __func__);
+               return;
+       }
+
+       if (n == 0)
+               fatal("routing socket closed");
+
+       if (n < (ssize_t)sizeof(rtm->rtm_msglen) || n < rtm->rtm_msglen) {
+               log_warnx("partial rtm of %zd in buffer", n);
+               return;
+       }
+
+       if (rtm->rtm_version != RTM_VERSION)
+               return;
+
+       switch(rtm->rtm_type) {
+       case RTM_PROPOSAL:
+               if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) {
+                       log_debug("%s: got solicit", __func__);
+                       vroute_dodns(env, (struct sockaddr *)&ivr->ivr_dns->vd_addr, 1,
+                           ivr->ivr_dns->vd_ifidx);
+               }
+               break;
+       default:
+               log_debug("%s: unexpected RTM: %d", __func__, rtm->rtm_type);
+               break;
+       }
+}
+
 void
 vroute_init(struct iked *env)
 {
        struct iked_vroute_sc   *ivr;
+       int                      rtfilter;
 
        ivr = calloc(1, sizeof(*ivr));
        if (ivr == NULL)
@@ -104,12 +164,21 @@ vroute_init(struct iked *env)
        if ((ivr->ivr_rtsock = socket(AF_ROUTE, SOCK_RAW, AF_UNSPEC)) == -1)
                fatal("%s: failed to create routing socket", __func__);
 
+       rtfilter = ROUTE_FILTER(RTM_GET) | ROUTE_FILTER(RTM_PROPOSAL);
+       if (setsockopt(ivr->ivr_rtsock, AF_ROUTE, ROUTE_MSGFILTER, &rtfilter,
+           sizeof(rtfilter)) == -1)
+               fatal("%s: setsockopt(ROUTE_MSGFILTER)", __func__);
+
        TAILQ_INIT(&ivr->ivr_addrs);
        TAILQ_INIT(&ivr->ivr_routes);
 
        ivr->ivr_pid = getpid();
 
        env->sc_vroute = ivr;
+
+       event_set(&ivr->ivr_routeev, ivr->ivr_rtsock, EV_READ | EV_PERSIST,
+           vroute_rtmsg_cb, env);
+       event_add(&ivr->ivr_routeev, NULL);
 }
 
 void
@@ -138,6 +207,12 @@ vroute_cleanup(struct iked *env)
                TAILQ_REMOVE(&ivr->ivr_routes, route, vr_entry);
                free(route);
        }
+
+       if (ivr->ivr_dns) {
+               vroute_dodns(env, (struct sockaddr *)&ivr->ivr_dns->vd_addr, 0,
+                   ivr->ivr_dns->vd_ifidx);
+               free(ivr->ivr_dns);
+       }
 }
 
 int
@@ -239,6 +314,64 @@ vroute_getaddr(struct iked *env, struct imsg *imsg)
        return (vroute_doaddr(env, ifname, addr, mask, add));
 }
 
+int
+vroute_setdns(struct iked *env, int add, struct sockaddr *addr,
+    unsigned int ifidx)
+{
+       struct iovec             iov[2];
+
+       iov[0].iov_base = addr;
+       iov[0].iov_len = addr->sa_len;
+
+       iov[1].iov_base = &ifidx;
+       iov[1].iov_len = sizeof(ifidx);
+
+       return (proc_composev(&env->sc_ps, PROC_PARENT,
+           add ? IMSG_VDNS_ADD: IMSG_VDNS_DEL, iov, 2));
+}
+
+int
+vroute_getdns(struct iked *env, struct imsg *imsg)
+{
+       struct iked_vroute_sc   *ivr = env->sc_vroute;
+       struct sockaddr         *dns;
+       uint8_t                 *ptr;
+       size_t                   left;
+       int                      add;
+       unsigned int             ifidx;
+
+       ptr = imsg->data;
+       left = IMSG_DATA_SIZE(imsg);
+
+       if (left < sizeof(*dns))
+               fatalx("bad length imsg received");
+
+       dns = (struct sockaddr *) ptr;
+       if (left < dns->sa_len)
+               fatalx("bad length imsg received");
+       ptr += dns->sa_len;
+       left -= dns->sa_len;
+
+       if (left != sizeof(ifidx))
+               fatalx("bad length imsg received");
+       memcpy(&ifidx, ptr, sizeof(ifidx));
+       ptr += sizeof(ifidx);
+       left -= sizeof(ifidx);
+
+       add = (imsg->hdr.type == IMSG_VDNS_ADD);
+       if (add) {
+               if (ivr->ivr_dns != NULL)
+                       return (0);
+               vroute_insertdns(env, ifidx, dns);
+       } else {
+               if (ivr->ivr_dns == NULL)
+                       return (0);
+               vroute_removedns(env, ifidx, dns);
+       }
+
+       return (vroute_dodns(env, dns, add, ifidx));
+}
+
 void
 vroute_insertroute(struct iked *env, int rdomain, struct sockaddr *dest,
     struct sockaddr *mask)
@@ -284,6 +417,35 @@ vroute_removeroute(struct iked *env, int rdomain, struct sockaddr *dest,
        }
 }
 
+void
+vroute_insertdns(struct iked *env, int ifidx, struct sockaddr *addr)
+{
+       struct iked_vroute_sc   *ivr = env->sc_vroute;
+       struct vroute_dns       *dns;
+       
+       dns = calloc(1, sizeof(*dns));
+       if (dns == NULL)
+               fatalx("%s: calloc.", __func__);
+
+       memcpy(&dns->vd_addr, addr, addr->sa_len);
+       dns->vd_ifidx = ifidx;
+       
+       ivr->ivr_dns = dns;
+}
+
+void
+vroute_removedns(struct iked *env, int ifidx, struct sockaddr *addr)
+{
+       struct iked_vroute_sc   *ivr = env->sc_vroute;
+
+       if (ifidx == ivr->ivr_dns->vd_ifidx &&
+           sockaddr_cmp(addr, (struct sockaddr *)
+           &ivr->ivr_dns->vd_addr, -1) == 0) {
+               free(ivr->ivr_dns);
+               ivr->ivr_dns = NULL;
+       }
+}
+
 void
 vroute_insertaddr(struct iked *env, int ifidx, struct sockaddr *addr,
     struct sockaddr *mask)
@@ -527,6 +689,73 @@ vroute_getcloneroute(struct iked *env, struct imsg *imsg)
            (struct sockaddr *)&addr, NULL));
 }
 
+int
+vroute_dodns(struct iked *env, struct sockaddr *dns, int add,
+    unsigned int ifidx)
+{
+       struct vroute_msg        m_rtmsg;
+       struct sockaddr_in       *in;
+       struct sockaddr_in6      *in6;
+       struct sockaddr_rtdns    rtdns;
+       struct iked_vroute_sc   *ivr = env->sc_vroute;
+       struct iovec             iov[3];
+       int                      i;
+       long                     pad = 0;
+       int                      iovcnt = 0, padlen;
+
+       bzero(&m_rtmsg, sizeof(m_rtmsg));
+#define rtm m_rtmsg.vm_rtm
+       rtm.rtm_version = RTM_VERSION;
+       rtm.rtm_type = RTM_PROPOSAL;
+       rtm.rtm_seq = ++ivr->ivr_rtseq;
+       rtm.rtm_priority = RTP_PROPOSAL_STATIC;
+       rtm.rtm_flags = RTF_UP;
+       rtm.rtm_addrs = RTA_DNS;
+       rtm.rtm_index = ifidx;
+
+       iov[iovcnt].iov_base = &rtm;
+       iov[iovcnt].iov_len = sizeof(rtm);
+       iovcnt++;
+
+       bzero(&rtdns, sizeof(rtdns));
+       rtdns.sr_family = dns->sa_family;
+       rtdns.sr_len = 2;
+       if (add) {
+               switch(dns->sa_family) {
+               case AF_INET:
+                       rtdns.sr_family = AF_INET;
+                       rtdns.sr_len += sizeof(struct in_addr);
+                       in = (struct sockaddr_in *)dns;
+                       memcpy(rtdns.sr_dns, &in->sin_addr, sizeof(struct in_addr));
+                       break;
+               case AF_INET6:
+                       rtdns.sr_family = AF_INET6;
+                       rtdns.sr_len += sizeof(struct in6_addr);
+                       in6 = (struct sockaddr_in6 *)dns;
+                       memcpy(rtdns.sr_dns, &in6->sin6_addr, sizeof(struct in6_addr));
+                       break;
+               default:
+                       return (-1);
+               }
+       }
+       iov[iovcnt].iov_base = &rtdns;
+       iov[iovcnt++].iov_len = sizeof(rtdns);
+       padlen = ROUNDUP(sizeof(rtdns)) - sizeof(rtdns);
+       if (padlen > 0) {
+               iov[iovcnt].iov_base = &pad;
+               iov[iovcnt++].iov_len = padlen;
+       }
+
+       for (i = 0; i < iovcnt; i++)
+               rtm.rtm_msglen += iov[i].iov_len;
+#undef rtm
+
+       if (writev(ivr->ivr_rtsock, iov, iovcnt) == -1)
+               log_warn("failed to send route message");
+
+       return (0);
+}
+
 int
 vroute_doroute(struct iked *env, int flags, int addrs, int rdomain, uint8_t type,
     struct sockaddr *dest, struct sockaddr *mask, struct sockaddr *addr, int *need_gw)