This diff adds support for automatic installation of virtual IPs and
routes for iked roadwarrior clients.

Requesting addresses from the peer has been supported for some time now
but is by itself pretty useless.
This change adds a new 'iface' config option to specify an interface on
which the address should be added.
In addition to addresses, iked will also automatically add routes for
each flow specified with 'from dynamic'.

Below is a client config that combines all of these features:

ikev2 "roadw-client" active \
        from dynamic to any \
        peer $IP \
        request address any \
        iface lo0

After a successful handshake, iked should set the received IP on lo0
and add a new default route from this new IP (matching 'from dynamic to any').
Depending on the server config the addresses and routes may be IPv4, IPv6
or both.  On exit, all added routes and addresses are automatically removed 
(make sure to test with the latest iked commit or this won't work).

Tests and comments greatly appreciated.

diff --git a/sbin/iked/Makefile b/sbin/iked/Makefile
index 6b8e3013aec..01a04b95dc4 100644
--- a/sbin/iked/Makefile
+++ b/sbin/iked/Makefile
@@ -4,7 +4,7 @@ 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
diff --git a/sbin/iked/config.c b/sbin/iked/config.c
index 3bafad89963..eb4edb46bd1 100644
--- a/sbin/iked/config.c
+++ b/sbin/iked/config.c
@@ -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) {
diff --git a/sbin/iked/iked.c b/sbin/iked/iked.c
index c1b5fb7a28c..8052fdc2b39 100644
--- a/sbin/iked/iked.c
+++ b/sbin/iked/iked.c
@@ -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");
 
@@ -265,10 +267,10 @@ parent_configure(struct iked *env)
         * proc - run kill to terminate its children safely.
         * dns - for reload and ocsp connect.
         * inet - for ocsp connect.
-        * route - for using interfaces in iked.conf (SIOCGIFGMEMB)
+        * wroute - for using interfaces in iked.conf (SIOCAIFGMEMB)
         * sendfd - for ocsp sockets.
         */
-       if (pledge("stdio rpath proc dns inet route sendfd", NULL) == -1)
+       if (pledge("stdio rpath proc dns inet wroute sendfd", NULL) == -1)
                fatal("pledge");
 
        config_setstatic(env);
@@ -454,6 +456,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:
diff --git a/sbin/iked/iked.conf.5 b/sbin/iked/iked.conf.5
index 695f0efb618..43f2424b6bc 100644
--- a/sbin/iked/iked.conf.5
+++ b/sbin/iked/iked.conf.5
@@ -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
diff --git a/sbin/iked/iked.h b/sbin/iked/iked.h
index e748ee6020e..d270210ccde 100644
--- a/sbin/iked/iked.h
+++ b/sbin/iked/iked.h
@@ -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 *);
diff --git a/sbin/iked/ikev2.c b/sbin/iked/ikev2.c
index 2ea7e7b23ac..0595cf4dc32 100644
--- a/sbin/iked/ikev2.c
+++ b/sbin/iked/ikev2.c
@@ -1615,6 +1615,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)
diff --git a/sbin/iked/parse.y b/sbin/iked/parse.y
index ff6fa16f094..53ba934e8d0 100644
--- a/sbin/iked/parse.y
+++ b/sbin/iked/parse.y
@@ -405,7 +405,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 *);
@@ -468,7 +468,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
@@ -491,7 +491,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
 %%
@@ -581,10 +581,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;
                        }
@@ -1235,6 +1235,13 @@ filter           : TAG STRING
                }
                ;
 
+iface          :               {
+                       $$ = NULL;
+               }
+               | IFACE STRING  {
+                       $$ = $2;
+               }
+
 string         : string STRING
                {
                        if (asprintf(&$$, "%s %s", $1, $2) == -1)
@@ -1360,6 +1367,7 @@ lookup(char *s)
                { "fragmentation",      FRAGMENTATION },
                { "from",               FROM },
                { "group",              GROUP },
+               { "iface",              IFACE },
                { "ike",                IKEV1 },
                { "ikelifetime",        IKELIFETIME },
                { "ikesa",              IKESA },
@@ -2448,6 +2456,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");
 
@@ -2620,6 +2629,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);
 
@@ -2674,7 +2686,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;
@@ -2712,6 +2724,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);
diff --git a/sbin/iked/policy.c b/sbin/iked/policy.c
index df7f2676dd1..a21099afe39 100644
--- a/sbin/iked/policy.c
+++ b/sbin/iked/policy.c
@@ -1,6 +1,7 @@
 /*     $OpenBSD: policy.c,v 1.75 2021/02/01 16:37:48 tobhe Exp $       */
 
 /*
+ * Copyright (c) 2020-2021 Tobias Heider <to...@openbsd.org>
  * Copyright (c) 2010-2013 Reyk Floeter <r...@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)
 {
diff --git a/sbin/iked/types.h b/sbin/iked/types.h
index 59147ba9f75..9e085562d61 100644
--- a/sbin/iked/types.h
+++ b/sbin/iked/types.h
@@ -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
index 00000000000..356fbee382e
--- /dev/null
+++ b/sbin/iked/vroute.c
@@ -0,0 +1,558 @@
+/*     $OpenBSD:$      */
+
+/*
+ * Copyright (c) 2021 Tobias Heider <to...@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);
+}

Reply via email to