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 = ⁡ + 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); +}