Add dynamic address configuration for roadwarrior clients.
authortobhe <tobhe@openbsd.org>
Sat, 13 Feb 2021 16:14:12 +0000 (16:14 +0000)
committertobhe <tobhe@openbsd.org>
Sat, 13 Feb 2021 16:14:12 +0000 (16:14 +0000)
The new 'iface' config option can be used to specify an interface
for the virtual addresses received from the peer.
Routes are automatically added based on the configured flows.

Input from sthen@ and claudio@
ok patrick@

sbin/iked/Makefile
sbin/iked/config.c
sbin/iked/iked.c
sbin/iked/iked.conf.5
sbin/iked/iked.h
sbin/iked/ikev2.c
sbin/iked/parse.y
sbin/iked/policy.c
sbin/iked/types.h
sbin/iked/vroute.c [new file with mode: 0644]

index 6b8e301..387b746 100644 (file)
@@ -1,10 +1,10 @@
-# $OpenBSD: Makefile,v 1.17 2017/07/19 12:50:32 espie Exp $
+# $OpenBSD: Makefile,v 1.18 2021/02/13 16:14:12 tobhe Exp $
 
 PROG=          iked
 SRCS=          ca.c chap_ms.c config.c control.c crypto.c dh.c \
                eap.c iked.c ikev2.c ikev2_msg.c ikev2_pld.c \
                log.c ocsp.c pfkey.c policy.c proc.c timer.c util.c \
-               imsg_util.c smult_curve25519_ref.c
+               imsg_util.c smult_curve25519_ref.c vroute.c
 SRCS+=         eap_map.c ikev2_map.c
 SRCS+=         parse.y
 MAN=           iked.conf.5 iked.8
index 26c63f5..849916f 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: config.c,v 1.76 2021/02/08 16:13:58 tobhe Exp $       */
+/*     $OpenBSD: config.c,v 1.77 2021/02/13 16:14:12 tobhe Exp $       */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -117,6 +117,7 @@ config_free_sa(struct iked *env, struct iked_sa *sa)
        config_free_fragments(&sa->sa_fragments);
        config_free_proposals(&sa->sa_proposals, 0);
        config_free_childsas(env, &sa->sa_childsas, NULL, NULL);
+       sa_configure_iface(env, sa, 0);
        sa_free_flows(env, &sa->sa_flows);
 
        if (sa->sa_addrpool) {
index b4a1335..5fe91b5 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: iked.c,v 1.53 2021/02/08 16:13:58 tobhe Exp $ */
+/*     $OpenBSD: iked.c,v 1.54 2021/02/13 16:14:12 tobhe Exp $ */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -199,6 +199,8 @@ main(int argc, char *argv[])
 
        proc_listen(ps, procs, nitems(procs));
 
+       vroute_init(env);
+
        if (parent_configure(env) == -1)
                fatalx("configuration failed");
 
@@ -266,9 +268,10 @@ parent_configure(struct iked *env)
         * dns - for reload and ocsp connect.
         * inet - for ocsp connect.
         * route - for using interfaces in iked.conf (SIOCGIFGMEMB)
+        * wroute - for adding and removing addresses (SIOCAIFGMEMB)
         * sendfd - for ocsp sockets.
         */
-       if (pledge("stdio rpath proc dns inet route sendfd", NULL) == -1)
+       if (pledge("stdio rpath proc dns inet route wroute sendfd", NULL) == -1)
                fatal("pledge");
 
        config_setstatic(env);
@@ -454,6 +457,14 @@ parent_dispatch_ikev2(int fd, struct privsep_proc *p, struct imsg *imsg)
        struct iked     *env = p->p_ps->ps_env;
 
        switch (imsg->hdr.type) {
+       case IMSG_IF_ADDADDR:
+       case IMSG_IF_DELADDR:
+               return (vroute_getaddr(env, imsg));
+       case IMSG_VROUTE_ADD:
+       case IMSG_VROUTE_DEL:
+               return (vroute_getroute(env, imsg));
+       case IMSG_VROUTE_CLONE:
+               return (vroute_getcloneroute(env, imsg));
        case IMSG_CTL_EXIT:
                parent_shutdown(env);
        default:
index 695f0ef..529c6aa 100644 (file)
@@ -1,4 +1,4 @@
-.\" $OpenBSD: iked.conf.5,v 1.83 2021/01/24 19:10:19 tobhe Exp $
+.\" $OpenBSD: iked.conf.5,v 1.84 2021/02/13 16:14:12 tobhe Exp $
 .\"
 .\" Copyright (c) 2010 - 2014 Reyk Floeter <reyk@openbsd.org>
 .\" Copyright (c) 2004 Mathieu Sauve-Frankel  All rights reserved.
@@ -15,7 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: January 24 2021 $
+.Dd $Mdocdate: February 13 2021 $
 .Dt IKED.CONF 5
 .Os
 .Sh NAME
@@ -659,6 +659,9 @@ included.
 .It Ic access-server Ar address
 The address of an internal remote access server.
 .El
+.It Ic iface Ar interface
+Configure requested addresses and routes on the specified
+.Ar interface .
 .It Ic tag Ar string
 Add a
 .Xr pf 4
index e748ee6..45658af 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: iked.h,v 1.184 2021/02/04 20:38:26 tobhe Exp $        */
+/*     $OpenBSD: iked.h,v 1.185 2021/02/13 16:14:12 tobhe Exp $        */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -153,6 +153,7 @@ struct iked_flow {
        unsigned int                     flow_dir;      /* in/out */
        int                              flow_rdomain;
        struct iked_addr                 flow_prenat;
+       int                              flow_fixed;
 
        unsigned int                     flow_loaded;   /* pfkey done */
 
@@ -236,6 +237,7 @@ struct iked_lifetime {
 struct iked_policy {
        unsigned int                     pol_id;
        char                             pol_name[IKED_ID_SIZE];
+       unsigned int                     pol_iface;
 
 #define IKED_SKIP_FLAGS                         0
 #define IKED_SKIP_AF                    1
@@ -751,6 +753,7 @@ struct iked {
        struct event                     sc_pfkeyev;
        uint8_t                          sc_certreqtype;
        struct ibuf                     *sc_certreq;
+       void                            *sc_vroute;
 
        struct iked_socket              *sc_sock4[2];
        struct iked_socket              *sc_sock6[2];
@@ -864,6 +867,7 @@ struct iked_sa *
            struct iked_policy *);
 void    sa_free(struct iked *, struct iked_sa *);
 void    sa_free_flows(struct iked *, struct iked_saflows *);
+int     sa_configure_iface(struct iked *, struct iked_sa *, int);
 int     sa_address(struct iked_sa *, struct iked_addr *, struct sockaddr *);
 void    childsa_free(struct iked_childsa *);
 struct iked_childsa *
@@ -937,6 +941,18 @@ int         dsa_update(struct iked_dsa *, const void *, size_t);
 ssize_t         dsa_sign_final(struct iked_dsa *, void *, size_t);
 ssize_t         dsa_verify_final(struct iked_dsa *, void *, size_t);
 
+/* vroute.c */
+void vroute_init(struct iked *);
+int vroute_getaddr(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 *,
+    uint8_t, struct sockaddr *);
+int vroute_setdelroute(struct iked *, uint8_t, struct sockaddr *,
+    uint8_t, struct sockaddr *);
+int vroute_getroute(struct iked *, struct imsg *);
+int vroute_getcloneroute(struct iked *, struct imsg *);
+
 /* ikev2.c */
 pid_t   ikev2(struct privsep *, struct privsep_proc *);
 void    ikev2_recv(struct iked *, struct iked_message *);
index 979d129..9a9bc78 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2.c,v 1.306 2021/02/11 22:02:41 tobhe Exp $       */
+/*     $OpenBSD: ikev2.c,v 1.307 2021/02/13 16:14:12 tobhe Exp $       */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -1616,6 +1616,7 @@ ikev2_init_done(struct iked *env, struct iked_sa *sa)
                ikev2_enable_timer(env, sa);
                ikev2_log_established(sa);
                ikev2_record_dstid(env, sa);
+               sa_configure_iface(env, sa, 1);
        }
 
        if (ret)
index e9a57c9..60dd3f3 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.127 2021/02/09 21:35:48 tobhe Exp $       */
+/*     $OpenBSD: parse.y,v 1.128 2021/02/13 16:14:12 tobhe Exp $       */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -408,7 +408,7 @@ int                  create_ike(char *, int, uint8_t,
                            uint8_t, char *, char *,
                            uint32_t, struct iked_lifetime *,
                            struct iked_auth *, struct ipsec_filters *,
-                           struct ipsec_addr_wrap *);
+                           struct ipsec_addr_wrap *, char *);
 int                     create_user(const char *, const char *);
 int                     get_id_type(char *);
 uint8_t                         x2i(unsigned char *);
@@ -471,7 +471,7 @@ typedef struct {
 %token STICKYADDRESS NOSTICKYADDRESS
 %token TOLERATE MAXAGE DYNAMIC
 %token CERTPARTIALCHAIN
-%token REQUEST
+%token REQUEST IFACE
 %token <v.string>              STRING
 %token <v.number>              NUMBER
 %type  <v.string>              string
@@ -494,7 +494,7 @@ typedef struct {
 %type  <v.mode>                ike_sas child_sas
 %type  <v.lifetime>            lifetime
 %type  <v.number>              byte_spec time_spec ikelifetime
-%type  <v.string>              name
+%type  <v.string>              name iface
 %type  <v.cfg>                 cfg ikecfg ikecfgvals
 %type  <v.string>              transform_esn
 %%
@@ -584,10 +584,10 @@ user              : USER STRING STRING            {
 
 ikev2rule      : IKEV2 name ikeflags satype af proto rdomain hosts_list peers
                    ike_sas child_sas ids ikelifetime lifetime ikeauth ikecfg
-                   filters {
+                   iface filters {
                        if (create_ike($2, $5, $6, $7, $8, &$9, $10, $11, $4,
                            $3, $12.srcid, $12.dstid, $13, &$14, &$15,
-                           $17, $16) == -1) {
+                           $18, $16, $17) == -1) {
                                yyerror("create_ike failed");
                                YYERROR;
                        }
@@ -1238,6 +1238,13 @@ filter           : TAG STRING
                }
                ;
 
+iface          :               {
+                       $$ = NULL;
+               }
+               | IFACE STRING  {
+                       $$ = $2;
+               }
+
 string         : string STRING
                {
                        if (asprintf(&$$, "%s %s", $1, $2) == -1)
@@ -1363,6 +1370,7 @@ lookup(char *s)
                { "fragmentation",      FRAGMENTATION },
                { "from",               FROM },
                { "group",              GROUP },
+               { "iface",              IFACE },
                { "ike",                IKEV1 },
                { "ikelifetime",        IKELIFETIME },
                { "ikesa",              IKESA },
@@ -2451,6 +2459,7 @@ print_policy(struct iked_policy *pol)
        struct iked_cfg         *cfg;
        unsigned int             i, j;
        const struct ipsec_xf   *xfs = NULL;
+       char                     iface[IF_NAMESIZE];
 
        print_verbose("ikev2");
 
@@ -2623,6 +2632,9 @@ print_policy(struct iked_policy *pol)
        if (pol->pol_tag[0] != '\0')
                print_verbose(" tag \"%s\"", pol->pol_tag);
 
+       if (pol->pol_iface != 0 && if_indextoname(pol->pol_iface, iface) != NULL)
+               print_verbose(" iface %s", iface);
+
        if (pol->pol_tap != 0)
                print_verbose(" tap \"enc%u\"", pol->pol_tap);
 
@@ -2677,7 +2689,7 @@ create_ike(char *name, int af, uint8_t ipproto,
     uint8_t flags, char *srcid, char *dstid,
     uint32_t ikelifetime, struct iked_lifetime *lt,
     struct iked_auth *authtype, struct ipsec_filters *filter,
-    struct ipsec_addr_wrap *ikecfg)
+    struct ipsec_addr_wrap *ikecfg, char *iface)
 {
        char                     idstr[IKED_ID_SIZE];
        struct ipsec_addr_wrap  *ipa, *ipb;
@@ -2715,6 +2727,14 @@ create_ike(char *name, int af, uint8_t ipproto,
                    "policy%d", policy_id);
        }
 
+       if (iface != NULL) {
+               pol.pol_iface = if_nametoindex(iface);
+               if (pol.pol_iface == 0) {
+                       yyerror("invalid iface");
+                       return (-1);
+               }
+       }
+
        if (srcid) {
                pol.pol_localid.id_type = get_id_type(srcid);
                pol.pol_localid.id_length = strlen(srcid);
index 8219982..189dd82 100644 (file)
@@ -1,6 +1,7 @@
-/*     $OpenBSD: policy.c,v 1.77 2021/02/12 19:30:34 tobhe Exp $       */
+/*     $OpenBSD: policy.c,v 1.78 2021/02/13 16:14:12 tobhe Exp $       */
 
 /*
+ * Copyright (c) 2020-2021 Tobias Heider <tobhe@openbsd.org>
  * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org>
  * Copyright (c) 2001 Daniel Hartmeier
  *
@@ -22,6 +23,8 @@
 #include <sys/uio.h>
 #include <sys/tree.h>
 
+#include <netinet/in.h>
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -667,6 +670,121 @@ sa_address(struct iked_sa *sa, struct iked_addr *addr, struct sockaddr *peer)
        return (0);
 }
 
+int
+sa_configure_iface(struct iked *env, struct iked_sa *sa, int add)
+{
+       struct iked_flow        *saflow;
+       struct iovec             iov[4];
+       int                      iovcnt;
+       struct sockaddr         *caddr;
+       struct sockaddr_in      *addr;
+       struct sockaddr_in       mask;
+       struct sockaddr_in6     *addr6;
+       struct sockaddr_in6      mask6;
+       int                      rdomain;
+
+       if (sa->sa_policy->pol_iface == 0)
+               return (0);
+
+       if (sa->sa_cp_addr) {
+               iovcnt = 0;
+               addr = (struct sockaddr_in *)&sa->sa_cp_addr->addr;
+               iov[0].iov_base = addr;
+               iov[0].iov_len = sizeof(*addr);
+               iovcnt++;
+
+               bzero(&mask, sizeof(mask));
+               mask.sin_addr.s_addr =
+                   prefixlen2mask(sa->sa_cp_addr->addr_mask ?
+                   sa->sa_cp_addr->addr_mask : 32);
+               mask.sin_family = AF_INET;
+               mask.sin_len = sizeof(mask);
+               iov[1].iov_base = &mask;
+               iov[1].iov_len = sizeof(mask);
+               iovcnt++;
+
+               iov[2].iov_base = &sa->sa_policy->pol_iface;
+               iov[2].iov_len = sizeof(sa->sa_policy->pol_iface);
+               iovcnt++;
+
+               if(proc_composev(&env->sc_ps, PROC_PARENT,
+                   add ? IMSG_IF_ADDADDR : IMSG_IF_DELADDR,
+                   iov, iovcnt))
+                       return (-1);
+       }
+       if (sa->sa_cp_addr6) {
+               iovcnt = 0;
+               addr6 = (struct sockaddr_in6 *)&sa->sa_cp_addr6->addr;
+               iov[0].iov_base = addr6;
+               iov[0].iov_len = sizeof(*addr6);
+               iovcnt++;
+
+               bzero(&mask6, sizeof(mask6));
+               prefixlen2mask6(sa->sa_cp_addr6->addr_mask ?
+                   sa->sa_cp_addr6->addr_mask : 128,
+                   (uint32_t *)&mask6.sin6_addr.s6_addr);
+               mask6.sin6_family = AF_INET6;
+               mask6.sin6_len = sizeof(mask6);
+               iov[1].iov_base = &mask6;
+               iov[1].iov_len = sizeof(mask6);
+               iovcnt++;
+
+               iov[2].iov_base = &sa->sa_policy->pol_iface;
+               iov[2].iov_len = sizeof(sa->sa_policy->pol_iface);
+               iovcnt++;
+
+               if(proc_composev(&env->sc_ps, PROC_PARENT,
+                   add ? IMSG_IF_ADDADDR : IMSG_IF_DELADDR,
+                   iov, iovcnt))
+                       return (-1);
+       }
+
+       if (add) {
+               /* Add direct route to peer */
+               if (vroute_setcloneroute(env, getrtable(),
+                   (struct sockaddr *)&sa->sa_peer.addr, 0, NULL))
+                       return (-1);
+       } else {
+               if (vroute_setdelroute(env, getrtable(),
+                   (struct sockaddr *)&sa->sa_peer.addr,
+                   0, NULL))
+                       return (-1);
+       }
+
+       TAILQ_FOREACH(saflow, &sa->sa_flows, flow_entry) {
+               rdomain = saflow->flow_rdomain == -1 ?
+                   getrtable() : saflow->flow_rdomain;
+
+               switch(saflow->flow_src.addr_af) {
+               case AF_INET:
+                       caddr = (struct sockaddr *)&sa->sa_cp_addr->addr;
+                       break;
+               case AF_INET6:
+                       caddr = (struct sockaddr *)&sa->sa_cp_addr6->addr;
+                       break;
+               default:
+                       return (-1);
+               }
+               if (sockaddr_cmp((struct sockaddr *)&saflow->flow_src.addr,
+                   caddr, -1) != 0)
+                       continue;
+
+               if (add) {
+                       if (vroute_setaddroute(env, rdomain,
+                           (struct sockaddr *)&saflow->flow_dst.addr,
+                           saflow->flow_dst.addr_mask, caddr))
+                               return (-1);
+               } else {
+                       if (vroute_setdelroute(env, rdomain,
+                           (struct sockaddr *)&saflow->flow_dst.addr,
+                           saflow->flow_dst.addr_mask, caddr))
+                               return (-1);
+               }
+       }
+
+       return (0);
+}
+
 void
 childsa_free(struct iked_childsa *csa)
 {
index 61dcfcb..ad599bf 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: types.h,v 1.41 2021/02/08 16:13:58 tobhe Exp $        */
+/*     $OpenBSD: types.h,v 1.42 2021/02/13 16:14:12 tobhe Exp $        */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -115,6 +115,11 @@ enum imsg_type {
        IMSG_CERTVALID,
        IMSG_CERTINVALID,
        IMSG_CERT_PARTIAL_CHAIN,
+       IMSG_IF_ADDADDR,
+       IMSG_IF_DELADDR,
+       IMSG_VROUTE_ADD,
+       IMSG_VROUTE_DEL,
+       IMSG_VROUTE_CLONE,
        IMSG_OCSP_FD,
        IMSG_OCSP_CFG,
        IMSG_AUTH,
diff --git a/sbin/iked/vroute.c b/sbin/iked/vroute.c
new file mode 100644 (file)
index 0000000..ceec85c
--- /dev/null
@@ -0,0 +1,558 @@
+/*     $OpenBSD: vroute.c,v 1.1 2021/02/13 16:14:12 tobhe Exp $        */
+
+/*
+ * Copyright (c) 2021 Tobias Heider <tobhe@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/ioctl.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/nd6.h>
+
+#include <event.h>
+#include <err.h>
+#include <errno.h>
+#include <poll.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#include <iked.h>
+
+#define IKED_VROUTE_PRIO       6
+
+#define ROUNDUP(a)                     \
+    (((a) & (sizeof(long) - 1)) ? (1 + ((a) | (sizeof(long) - 1))) : (a))
+
+int vroute_setroute(struct iked *, uint8_t, struct sockaddr *, uint8_t,
+    struct sockaddr *, int);
+int vroute_doroute(struct iked *, int, int, int, uint8_t, struct sockaddr *,
+    struct sockaddr *, struct sockaddr *);
+int vroute_doaddr(struct iked *, char *, struct sockaddr *, struct sockaddr *, int);
+
+struct iked_vroute_sc {
+       int     ivr_iosock;
+       int     ivr_iosock6;
+       int     ivr_rtsock;
+       int     ivr_rtseq;
+       pid_t   ivr_pid;
+};
+
+struct vroute_msg {
+       struct rt_msghdr         vm_rtm;
+       uint8_t                  vm_space[512];
+};
+
+int vroute_process(struct iked *, int msglen, struct vroute_msg *,
+    struct sockaddr *, struct sockaddr *, struct sockaddr *);
+
+void
+vroute_init(struct iked *env)
+{
+       struct iked_vroute_sc   *ivr;
+
+       ivr = calloc(1, sizeof(*ivr));
+       if (ivr == NULL)
+               fatal("%s: calloc.", __func__);
+
+       if ((ivr->ivr_iosock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+               fatal("%s: failed to create ioctl socket", __func__);
+
+       if ((ivr->ivr_iosock6 = socket(AF_INET6, SOCK_DGRAM, 0)) == -1)
+               fatal("%s: failed to create ioctl socket", __func__);
+
+       if ((ivr->ivr_rtsock = socket(AF_ROUTE, SOCK_RAW, AF_UNSPEC)) == -1)
+               fatal("%s: failed to create routing socket", __func__);
+
+       ivr->ivr_pid = getpid();
+
+       env->sc_vroute = ivr;
+}
+
+int
+vroute_getaddr(struct iked *env, struct imsg *imsg)
+{
+       char                     ifname[IF_NAMESIZE];
+       struct sockaddr *addr, *mask;
+       uint8_t                 *ptr;
+       size_t                   left;
+       int                      af;
+       unsigned int             ifidx;
+
+       ptr = imsg->data;
+       left = IMSG_DATA_SIZE(imsg);
+
+       if (left < sizeof(*addr))
+               fatalx("bad length imsg received");
+
+       addr = (struct sockaddr *) ptr;
+       af = addr->sa_family;
+
+       if (left < addr->sa_len)
+               fatalx("bad length imsg received");
+       ptr += addr->sa_len;
+       left -= addr->sa_len;
+
+       mask = (struct sockaddr *) ptr;
+       if (mask->sa_family != af)
+               return (-1);
+
+       if (left < mask->sa_len)
+               fatalx("bad length imsg received");
+       ptr += mask->sa_len;
+       left -= mask->sa_len;
+
+       if (left != sizeof(ifidx))
+               fatalx("bad length imsg received");
+       memcpy(&ifidx, ptr, sizeof(ifidx));
+       ptr += sizeof(ifidx);
+       left -= sizeof(ifidx);
+
+       if_indextoname(ifidx, ifname);
+
+       return (vroute_doaddr(env, ifname, addr, mask,
+           imsg->hdr.type == IMSG_IF_ADDADDR));
+}
+
+int
+vroute_setaddroute(struct iked *env, uint8_t rdomain, struct sockaddr *dst,
+    uint8_t mask, struct sockaddr *ifa)
+{
+       return (vroute_setroute(env, rdomain, dst, mask, ifa,
+           IMSG_VROUTE_ADD));
+}
+
+int
+vroute_setcloneroute(struct iked *env, uint8_t rdomain, struct sockaddr *dst,
+    uint8_t mask, struct sockaddr *addr)
+{
+       return (vroute_setroute(env, rdomain, dst, mask, addr,
+           IMSG_VROUTE_CLONE));
+}
+
+int
+vroute_setdelroute(struct iked *env, uint8_t rdomain, struct sockaddr *dst,
+    uint8_t mask, struct sockaddr *addr)
+{
+       return (vroute_setroute(env, rdomain, dst, mask, addr,
+           IMSG_VROUTE_DEL));
+}
+
+int
+vroute_setroute(struct iked *env, uint8_t rdomain, struct sockaddr *dst,
+    uint8_t mask, struct sockaddr *addr, int type)
+{
+       struct sockaddr_storage  sa;
+       struct sockaddr_in      *in;
+       struct sockaddr_in6     *in6;
+       struct iovec             iov[5];
+       int                      iovcnt = 0;
+       uint8_t                  af;
+
+       if (addr && dst->sa_family != addr->sa_family)
+               return (-1);
+       af = dst->sa_family;
+
+       iov[iovcnt].iov_base = &af;
+       iov[iovcnt].iov_len = sizeof(af);
+       iovcnt++;
+
+       iov[iovcnt].iov_base = &rdomain;
+       iov[iovcnt].iov_len = sizeof(rdomain);
+       iovcnt++;
+
+       iov[iovcnt].iov_base = dst;
+       iov[iovcnt].iov_len = dst->sa_len;
+       iovcnt++;
+
+       if (type != IMSG_VROUTE_CLONE && addr) {
+               bzero(&sa, sizeof(sa));
+               switch(af) {
+               case AF_INET:
+                       in = (struct sockaddr_in *)&sa;
+                       in->sin_addr.s_addr = prefixlen2mask(mask);
+                       in->sin_family = af;
+                       in->sin_len = sizeof(*in);
+                       iov[iovcnt].iov_base = in;
+                       iov[iovcnt].iov_len = sizeof(*in);
+                       iovcnt++;
+                       break;
+               case AF_INET6:
+                       in6 = (struct sockaddr_in6 *)&sa;
+                       prefixlen2mask6(mask,
+                           (uint32_t *)in6->sin6_addr.s6_addr);
+                       in6->sin6_family = af;
+                       in6->sin6_len = sizeof(*in6);
+                       iov[iovcnt].iov_base = in6;
+                       iov[iovcnt].iov_len = sizeof(*in6);
+                       iovcnt++;
+                       break;
+               }
+
+               iov[iovcnt].iov_base = addr;
+               iov[iovcnt].iov_len = addr->sa_len;
+               iovcnt++;
+       }
+
+       return (proc_composev(&env->sc_ps, PROC_PARENT, type, iov, iovcnt));
+}
+
+int
+vroute_getroute(struct iked *env, struct imsg *imsg)
+{
+       struct sockaddr         *dest, *mask = NULL, *addr = NULL;
+       uint8_t                 *ptr;
+       size_t                   left;
+       int                      addrs = 0;
+       int                      type, flags;
+       uint8_t                  af, rdomain;
+
+       ptr = (uint8_t *)imsg->data;
+       left = IMSG_DATA_SIZE(imsg);
+
+       if (left < sizeof(af))
+               return (-1);
+       af = *ptr;
+       ptr += sizeof(af);
+       left -= sizeof(af);
+
+       if (left < sizeof(rdomain))
+               return (-1);
+       rdomain = *ptr;
+       ptr += sizeof(rdomain);
+       left -= sizeof(rdomain);
+
+       if (left < sizeof(struct sockaddr))
+               return (-1);
+       dest = (struct sockaddr *)ptr;
+       if (left < dest->sa_len)
+               return (-1);
+       socket_setport(dest, 0);
+       ptr += dest->sa_len;
+       left -= dest->sa_len;
+       addrs |= RTA_DST;
+
+       flags = RTF_UP | RTF_STATIC;
+       if (left != 0) {
+               if (left < sizeof(struct sockaddr))
+                       return (-1);
+               mask = (struct sockaddr *)ptr;
+               if (left < mask->sa_len)
+                       return (-1);
+               socket_setport(mask, 0);
+               ptr += mask->sa_len;
+               left -= mask->sa_len;
+               addrs |= RTA_NETMASK;
+
+               if (left < sizeof(struct sockaddr))
+                       return (-1);
+               addr = (struct sockaddr *)ptr;
+               if (left < addr->sa_len)
+                       return (-1);
+               socket_setport(addr, 0);
+               ptr += addr->sa_len;
+               left -= addr->sa_len;
+               addrs |= RTA_GATEWAY;
+       } else {
+               flags |= RTF_HOST;
+       }
+
+       switch(imsg->hdr.type) {
+       case IMSG_VROUTE_ADD:
+               type = RTM_ADD;
+               break;
+       case IMSG_VROUTE_DEL:
+               type = RTM_DELETE;
+               break;
+       }
+
+       return (vroute_doroute(env, flags, addrs, rdomain, type,
+           dest, mask, addr));
+}
+
+int
+vroute_getcloneroute(struct iked *env, struct imsg *imsg)
+{
+       struct sockaddr         *dst;
+       struct sockaddr_storage  dest;
+       struct sockaddr_storage  mask;
+       struct sockaddr_storage  addr;
+       uint8_t                 *ptr;
+       size_t                   left;
+       uint8_t                  af, rdomain;
+       int                      flags;
+       int                      addrs;
+
+       ptr = (uint8_t *)imsg->data;
+       left = IMSG_DATA_SIZE(imsg);
+
+       if (left < sizeof(af))
+               return (-1);
+       af = *ptr;
+       ptr += sizeof(af);
+       left -= sizeof(af);
+
+       if (left < sizeof(rdomain))
+               return (-1);
+       rdomain = *ptr;
+       ptr += sizeof(rdomain);
+       left -= sizeof(rdomain);
+
+       bzero(&dest, sizeof(dest));
+       bzero(&mask, sizeof(mask));
+       bzero(&addr, sizeof(addr));
+
+       switch(af) {
+       case AF_INET:
+               if (left < sizeof(struct sockaddr_in))
+                       return (-1);
+               dst = (struct sockaddr *)ptr;;
+               memcpy(&dest, ptr, sizeof(struct sockaddr_in));;
+               ptr += sizeof(struct sockaddr_in);
+               left -= sizeof(struct sockaddr_in);
+               break;
+       case AF_INET6:
+               if (left < sizeof(struct sockaddr_in6))
+                       return (-1);
+               dst = (struct sockaddr *)ptr;;
+               memcpy(&dest, ptr, sizeof(struct sockaddr_in6));;
+               ptr += sizeof(struct sockaddr_in6);
+               left -= sizeof(struct sockaddr_in6);
+               break;
+       default:
+               return (-1);
+       }
+
+       /* Get route to peer */
+       flags = RTF_UP | RTF_GATEWAY | RTF_HOST | RTF_STATIC;
+       if (vroute_doroute(env, flags, RTA_DST, rdomain, RTM_GET,
+           (struct sockaddr *)&dest, (struct sockaddr *)&mask,
+           (struct sockaddr *)&addr))
+               return (-1);
+
+       /* Set explicit route to peer with gateway addr*/
+       addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
+       return (vroute_doroute(env, flags, addrs, rdomain, RTM_ADD,
+           dst, (struct sockaddr *)&mask, (struct sockaddr *)&addr));
+}
+
+int
+vroute_doroute(struct iked *env, int flags, int addrs, int rdomain, uint8_t type,
+    struct sockaddr *dest, struct sockaddr *mask, struct sockaddr *addr)
+{
+       struct vroute_msg        m_rtmsg;
+       struct iovec             iov[7];
+       struct iked_vroute_sc   *ivr = env->sc_vroute;
+       ssize_t                  len;
+       int                      iovcnt = 0;
+       int                      i;
+       long                     pad = 0;
+       size_t                   padlen;
+
+       bzero(&m_rtmsg, sizeof(m_rtmsg));
+#define rtm m_rtmsg.vm_rtm
+       rtm.rtm_version = RTM_VERSION;
+       rtm.rtm_tableid = rdomain;
+       rtm.rtm_type = type;
+       rtm.rtm_seq = ++ivr->ivr_rtseq;
+       if (type != RTM_GET)
+               rtm.rtm_priority = IKED_VROUTE_PRIO;
+       rtm.rtm_flags = flags;
+       rtm.rtm_addrs = addrs;
+
+       iov[iovcnt].iov_base = &rtm;
+       iov[iovcnt].iov_len = sizeof(rtm);
+       iovcnt++;
+
+       if (rtm.rtm_addrs & RTA_DST) {
+               iov[iovcnt].iov_base = dest;
+               iov[iovcnt].iov_len = dest->sa_len;
+               iovcnt++;
+               padlen = ROUNDUP(dest->sa_len) - dest->sa_len;
+               if (padlen > 0) {
+                       iov[iovcnt].iov_base = &pad;
+                       iov[iovcnt].iov_len = padlen;
+                       iovcnt++;
+               }
+       }
+
+       if (rtm.rtm_addrs & RTA_GATEWAY) {
+               iov[iovcnt].iov_base = addr;
+               iov[iovcnt].iov_len = addr->sa_len;
+               iovcnt++;
+               padlen = ROUNDUP(addr->sa_len) - addr->sa_len;
+               if (padlen > 0) {
+                       iov[iovcnt].iov_base = &pad;
+                       iov[iovcnt].iov_len = padlen;
+                       iovcnt++;
+               }
+       }
+
+       if (rtm.rtm_addrs & RTA_NETMASK) {
+               iov[iovcnt].iov_base = mask;
+               iov[iovcnt].iov_len = mask->sa_len;
+               iovcnt++;
+               padlen = ROUNDUP(mask->sa_len) - mask->sa_len;
+               if (padlen > 0) {
+                       iov[iovcnt].iov_base = &pad;
+                       iov[iovcnt].iov_len = padlen;
+                       iovcnt++;
+               }
+       }
+
+       for (i = 0; i < iovcnt; i++)
+               rtm.rtm_msglen += iov[i].iov_len;
+
+       log_debug("%s: len: %u type: %s rdomain: %d flags %x addrs %x", __func__, rtm.rtm_msglen,
+           type == RTM_ADD ? "RTM_ADD" : type == RTM_DELETE ? "RTM_DELETE" :
+           type == RTM_GET ? "RTM_GET" : "unknown", rdomain, flags,  addrs);
+
+       if (writev(ivr->ivr_rtsock, iov, iovcnt) == -1) {
+               if ((type == RTM_ADD && errno != EEXIST) ||
+                   (type == RTM_DELETE && errno != ESRCH)) {
+                       log_warn("%s: write %d", __func__, rtm.rtm_errno);
+                       return (-1);
+               }
+       }
+
+       if (type == RTM_GET) {
+               do {
+                       len = read(ivr->ivr_rtsock, &m_rtmsg, sizeof(m_rtmsg));
+               } while(len > 0 && (rtm.rtm_version != RTM_VERSION ||
+                   rtm.rtm_seq != ivr->ivr_rtseq || rtm.rtm_pid != ivr->ivr_pid));
+               return (vroute_process(env, len, &m_rtmsg, dest, mask, addr));
+       }
+#undef rtm
+
+       return (0);
+}
+
+int
+vroute_process(struct iked *env, int msglen, struct vroute_msg *m_rtmsg,
+    struct sockaddr *dest, struct sockaddr *mask, struct sockaddr *addr)
+{
+       struct sockaddr *sa;
+       char *cp;
+       int i;
+
+#define rtm m_rtmsg->vm_rtm
+       if (rtm.rtm_version != RTM_VERSION) {
+               warnx("routing message version %u not understood",
+                   rtm.rtm_version);
+               return (-1);
+       }
+       if (rtm.rtm_msglen > msglen) {
+               warnx("message length mismatch, in packet %u, returned %d",
+                   rtm.rtm_msglen, msglen);
+               return (-1);
+       }
+       if (rtm.rtm_errno) {
+               warnx("RTM_GET: %s (errno %d)",
+                   strerror(rtm.rtm_errno), rtm.rtm_errno);
+               return (-1);
+       }
+       cp = m_rtmsg->vm_space;
+       if(rtm.rtm_addrs) {
+               for (i = 1; i; i <<= 1) {
+                       if (i & rtm.rtm_addrs) {
+                               sa = (struct sockaddr *)cp;
+                               switch(i) {
+                               case RTA_DST:
+                                       memcpy(dest, cp, sa->sa_len);
+                                       break;
+                               case RTA_NETMASK:
+                                       memcpy(mask, cp, sa->sa_len);
+                                       break;
+                               case RTA_GATEWAY:
+                                       memcpy(addr, cp, sa->sa_len);
+                                       break;
+                               }
+                               cp += ROUNDUP(sa->sa_len);
+                       }
+               }
+       }
+#undef rtm
+       return (0);
+}
+
+int
+vroute_doaddr(struct iked *env, char *ifname, struct sockaddr *addr,
+    struct sockaddr *mask, int add)
+{
+       struct iked_vroute_sc   *ivr = env->sc_vroute;
+       struct ifaliasreq        req;
+       struct in6_aliasreq      req6;
+       unsigned long            ioreq;
+       int                      af;
+       char                     addr_buf[NI_MAXHOST];
+       char                     mask_buf[NI_MAXHOST];
+
+       af = addr->sa_family;
+       switch (af) {
+       case AF_INET:
+               bzero(&req, sizeof(req));
+               strncpy(req.ifra_name, ifname, sizeof(req.ifra_name));
+               memcpy(&req.ifra_addr, addr, sizeof(req.ifra_addr));
+               if (add)
+                       memcpy(&req.ifra_mask, mask, sizeof(req.ifra_addr));
+
+               inet_ntop(af, &((struct sockaddr_in *)addr)->sin_addr,
+                   addr_buf, sizeof(addr_buf));
+               inet_ntop(af, &((struct sockaddr_in *)mask)->sin_addr,
+                   mask_buf, sizeof(mask_buf));
+               log_debug("%s: %s inet %s netmask %s", __func__,
+                   add ? "add" : "del",addr_buf, mask_buf);
+
+               ioreq = add ? SIOCAIFADDR : SIOCDIFADDR;
+               if (ioctl(ivr->ivr_iosock, ioreq, &req) == -1) {
+                       log_warn("%s: req: %lu", __func__, ioreq);
+                       return (-1);
+               }
+               break;
+       case AF_INET6:
+               bzero(&req6, sizeof(req6));
+               strncpy(req6.ifra_name, ifname, sizeof(req6.ifra_name));
+               req6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
+               req6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
+
+               memcpy(&req6.ifra_addr, addr, sizeof(req6.ifra_addr));
+               if (add)
+                       memcpy(&req6.ifra_prefixmask, mask,
+                           sizeof(req6.ifra_prefixmask));
+
+
+               inet_ntop(af, &((struct sockaddr_in6 *)addr)->sin6_addr,
+                   addr_buf, sizeof(addr_buf));
+               inet_ntop(af, &((struct sockaddr_in6 *)mask)->sin6_addr,
+                   mask_buf, sizeof(mask_buf));
+               log_debug("%s: %s inet6 %s netmask %s", __func__,
+                   add ? "add" : "del",addr_buf, mask_buf);
+
+               ioreq = add ? SIOCAIFADDR_IN6 : SIOCDIFADDR_IN6;
+               if (ioctl(ivr->ivr_iosock6, ioreq, &req6) == -1) {
+                       log_warn("%s: req: %lu", __func__, ioreq);
+                       return (-1);
+               }
+               break;
+       default:
+               return (-1);
+       }
+
+       return (0);
+}