src/sbin/iked/vroute.c

970 lines
24 KiB
C

/* $OpenBSD: vroute.c,v 1.19 2023/06/13 12:34:12 tb 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 ROUTE_SOCKET_BUF_SIZE 16384
#define IKED_VROUTE_PRIO 6
#define ROUNDUP(a) (a>0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
int vroute_setroute(struct iked *, uint32_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 *);
int vroute_doaddr(struct iked *, char *, struct sockaddr *, struct sockaddr *, int);
int vroute_dodns(struct iked *, struct sockaddr *, int, unsigned int);
void vroute_cleanup(struct iked *);
void vroute_rtmsg_cb(int, short, void *);
void vroute_insertaddr(struct iked *, int, struct sockaddr *, struct sockaddr *);
void vroute_removeaddr(struct iked *, int, struct sockaddr *, struct sockaddr *);
void vroute_insertdns(struct iked *, int, struct sockaddr *);
void vroute_removedns(struct iked *, int, struct sockaddr *);
void vroute_insertroute(struct iked *, int, struct sockaddr *, struct sockaddr *);
void vroute_removeroute(struct iked *, int, struct sockaddr *, struct sockaddr *);
struct vroute_addr {
int va_ifidx;
struct sockaddr_storage va_addr;
struct sockaddr_storage va_mask;
TAILQ_ENTRY(vroute_addr) va_entry;
};
TAILQ_HEAD(vroute_addrs, vroute_addr);
struct vroute_route {
int vr_rdomain;
int vr_flags;
struct sockaddr_storage vr_dest;
struct sockaddr_storage vr_mask;
TAILQ_ENTRY(vroute_route) vr_entry;
};
TAILQ_HEAD(vroute_routes, vroute_route);
struct vroute_dns {
struct sockaddr_storage vd_addr;
int vd_ifidx;
TAILQ_ENTRY(vroute_dns) vd_entry;
};
TAILQ_HEAD(vroute_dnss, vroute_dns);
struct iked_vroute_sc {
struct vroute_addrs ivr_addrs;
struct vroute_routes ivr_routes;
struct vroute_dnss ivr_dnss;
struct event ivr_routeev;
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 *, int *);
void
vroute_rtmsg_cb(int fd, short events, void *arg)
{
struct iked *env = (struct iked *) arg;
struct iked_vroute_sc *ivr = env->sc_vroute;
struct vroute_dns *dns;
static uint8_t *buf;
struct rt_msghdr *rtm;
ssize_t n;
if (buf == NULL) {
buf = malloc(ROUTE_SOCKET_BUF_SIZE);
if (buf == NULL)
fatal("malloc");
}
rtm = (struct rt_msghdr *)buf;
if ((n = read(fd, buf, ROUTE_SOCKET_BUF_SIZE)) == -1) {
if (errno == EAGAIN || errno == EINTR)
return;
log_warn("%s: read error", __func__);
return;
}
if (n == 0)
fatal("routing socket closed");
if (n < (ssize_t)sizeof(rtm->rtm_msglen) || n < rtm->rtm_msglen) {
log_warnx("partial rtm of %zd in buffer", n);
return;
}
if (rtm->rtm_version != RTM_VERSION)
return;
switch(rtm->rtm_type) {
case RTM_PROPOSAL:
if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) {
TAILQ_FOREACH(dns, &ivr->ivr_dnss, vd_entry) {
log_debug("%s: got solicit", __func__);
vroute_dodns(env, (struct sockaddr *) &dns->vd_addr,
1, dns->vd_ifidx);
}
}
break;
default:
log_debug("%s: unexpected RTM: %d", __func__, rtm->rtm_type);
break;
}
}
void
vroute_init(struct iked *env)
{
struct iked_vroute_sc *ivr;
int rtfilter;
ivr = calloc(1, sizeof(*ivr));
if (ivr == NULL)
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__);
rtfilter = ROUTE_FILTER(RTM_GET) | ROUTE_FILTER(RTM_PROPOSAL);
if (setsockopt(ivr->ivr_rtsock, AF_ROUTE, ROUTE_MSGFILTER, &rtfilter,
sizeof(rtfilter)) == -1)
fatal("%s: setsockopt(ROUTE_MSGFILTER)", __func__);
TAILQ_INIT(&ivr->ivr_addrs);
TAILQ_INIT(&ivr->ivr_dnss);
TAILQ_INIT(&ivr->ivr_routes);
ivr->ivr_pid = getpid();
env->sc_vroute = ivr;
event_set(&ivr->ivr_routeev, ivr->ivr_rtsock, EV_READ | EV_PERSIST,
vroute_rtmsg_cb, env);
event_add(&ivr->ivr_routeev, NULL);
}
void
vroute_cleanup(struct iked *env)
{
char ifname[IF_NAMESIZE];
struct iked_vroute_sc *ivr = env->sc_vroute;
struct vroute_addr *addr;
struct vroute_route *route;
struct vroute_dns *dns;
while ((addr = TAILQ_FIRST(&ivr->ivr_addrs))) {
if_indextoname(addr->va_ifidx, ifname);
vroute_doaddr(env, ifname,
(struct sockaddr *)&addr->va_addr,
(struct sockaddr *)&addr->va_mask, 0);
TAILQ_REMOVE(&ivr->ivr_addrs, addr, va_entry);
free(addr);
}
while ((route = TAILQ_FIRST(&ivr->ivr_routes))) {
vroute_doroute(env, RTF_UP | RTF_GATEWAY | RTF_STATIC,
route->vr_flags, route->vr_rdomain, RTM_DELETE,
(struct sockaddr *)&route->vr_dest,
(struct sockaddr *)&route->vr_mask,
NULL, NULL);
TAILQ_REMOVE(&ivr->ivr_routes, route, vr_entry);
free(route);
}
while ((dns = TAILQ_FIRST(&ivr->ivr_dnss))) {
vroute_dodns(env, (struct sockaddr *)&dns->vd_addr, 0,
dns->vd_ifidx);
TAILQ_REMOVE(&ivr->ivr_dnss, dns, vd_entry);
free(dns);
}
}
int
vroute_setaddr(struct iked *env, int add, struct sockaddr *addr,
int mask, unsigned int ifidx)
{
struct iovec iov[4];
int iovcnt;
struct sockaddr_in mask4;
struct sockaddr_in6 mask6;
iovcnt = 0;
iov[0].iov_base = addr;
iov[0].iov_len = addr->sa_len;
iovcnt++;
switch(addr->sa_family) {
case AF_INET:
bzero(&mask, sizeof(mask));
mask4.sin_addr.s_addr = prefixlen2mask(mask ? mask : 32);
mask4.sin_family = AF_INET;
mask4.sin_len = sizeof(mask4);
iov[1].iov_base = &mask4;
iov[1].iov_len = sizeof(mask4);
iovcnt++;
break;
case AF_INET6:
bzero(&mask6, sizeof(mask6));
prefixlen2mask6(mask ? 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++;
break;
default:
return -1;
}
iov[2].iov_base = &ifidx;
iov[2].iov_len = sizeof(ifidx);
iovcnt++;
return (proc_composev(&env->sc_ps, PROC_PARENT,
add ? IMSG_IF_ADDADDR : IMSG_IF_DELADDR, iov, iovcnt));
}
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, add;
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;
if (left < sizeof(*mask))
fatalx("bad length imsg received");
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);
add = (imsg->hdr.type == IMSG_IF_ADDADDR);
/* Store address for cleanup */
if (add)
vroute_insertaddr(env, ifidx, addr, mask);
else
vroute_removeaddr(env, ifidx, addr, mask);
if_indextoname(ifidx, ifname);
return (vroute_doaddr(env, ifname, addr, mask, add));
}
int
vroute_setdns(struct iked *env, int add, struct sockaddr *addr,
unsigned int ifidx)
{
struct iovec iov[2];
iov[0].iov_base = addr;
iov[0].iov_len = addr->sa_len;
iov[1].iov_base = &ifidx;
iov[1].iov_len = sizeof(ifidx);
return (proc_composev(&env->sc_ps, PROC_PARENT,
add ? IMSG_VDNS_ADD: IMSG_VDNS_DEL, iov, 2));
}
int
vroute_getdns(struct iked *env, struct imsg *imsg)
{
struct sockaddr *dns;
uint8_t *ptr;
size_t left;
int add;
unsigned int ifidx;
ptr = imsg->data;
left = IMSG_DATA_SIZE(imsg);
if (left < sizeof(*dns))
fatalx("bad length imsg received");
dns = (struct sockaddr *) ptr;
if (left < dns->sa_len)
fatalx("bad length imsg received");
ptr += dns->sa_len;
left -= dns->sa_len;
if (left != sizeof(ifidx))
fatalx("bad length imsg received");
memcpy(&ifidx, ptr, sizeof(ifidx));
ptr += sizeof(ifidx);
left -= sizeof(ifidx);
add = (imsg->hdr.type == IMSG_VDNS_ADD);
if (add) {
vroute_insertdns(env, ifidx, dns);
} else {
vroute_removedns(env, ifidx, dns);
}
return (vroute_dodns(env, dns, add, ifidx));
}
void
vroute_insertroute(struct iked *env, int rdomain, struct sockaddr *dest,
struct sockaddr *mask)
{
struct iked_vroute_sc *ivr = env->sc_vroute;
struct vroute_route *route;
route = calloc(1, sizeof(*route));
if (route == NULL)
fatalx("%s: calloc.", __func__);
if (dest != NULL) {
route->vr_flags |= RTA_DST;
memcpy(&route->vr_dest, dest, dest->sa_len);
}
if (mask != NULL) {
route->vr_flags |= RTA_NETMASK;
memcpy(&route->vr_mask, mask, mask->sa_len);
}
route->vr_rdomain = rdomain;
TAILQ_INSERT_TAIL(&ivr->ivr_routes, route, vr_entry);
}
void
vroute_removeroute(struct iked *env, int rdomain, struct sockaddr *dest,
struct sockaddr *mask)
{
struct iked_vroute_sc *ivr = env->sc_vroute;
struct vroute_route *route, *troute;
TAILQ_FOREACH_SAFE(route, &ivr->ivr_routes, vr_entry, troute) {
if (sockaddr_cmp(dest, (struct sockaddr *)&route->vr_dest, -1))
continue;
if (mask && !(route->vr_flags & RTA_NETMASK))
continue;
if (mask &&
sockaddr_cmp(mask, (struct sockaddr *)&route->vr_mask, -1))
continue;
if (rdomain != route->vr_rdomain)
continue;
TAILQ_REMOVE(&ivr->ivr_routes, route, vr_entry);
free(route);
}
}
void
vroute_insertdns(struct iked *env, int ifidx, struct sockaddr *addr)
{
struct iked_vroute_sc *ivr = env->sc_vroute;
struct vroute_dns *dns;
dns = calloc(1, sizeof(*dns));
if (dns == NULL)
fatalx("%s: calloc.", __func__);
memcpy(&dns->vd_addr, addr, addr->sa_len);
dns->vd_ifidx = ifidx;
TAILQ_INSERT_TAIL(&ivr->ivr_dnss, dns, vd_entry);
}
void
vroute_removedns(struct iked *env, int ifidx, struct sockaddr *addr)
{
struct iked_vroute_sc *ivr = env->sc_vroute;
struct vroute_dns *dns, *tdns;
TAILQ_FOREACH_SAFE(dns, &ivr->ivr_dnss, vd_entry, tdns) {
if (ifidx != dns->vd_ifidx)
continue;
if (sockaddr_cmp(addr, (struct sockaddr *) &dns->vd_addr, -1))
continue;
TAILQ_REMOVE(&ivr->ivr_dnss, dns, vd_entry);
free(dns);
}
}
void
vroute_insertaddr(struct iked *env, int ifidx, struct sockaddr *addr,
struct sockaddr *mask)
{
struct iked_vroute_sc *ivr = env->sc_vroute;
struct vroute_addr *vaddr;
vaddr = calloc(1, sizeof(*vaddr));
if (vaddr == NULL)
fatalx("%s: calloc.", __func__);
memcpy(&vaddr->va_addr, addr, addr->sa_len);
memcpy(&vaddr->va_mask, mask, mask->sa_len);
vaddr->va_ifidx = ifidx;
TAILQ_INSERT_TAIL(&ivr->ivr_addrs, vaddr, va_entry);
}
void
vroute_removeaddr(struct iked *env, int ifidx, struct sockaddr *addr,
struct sockaddr *mask)
{
struct iked_vroute_sc *ivr = env->sc_vroute;
struct vroute_addr *vaddr, *tvaddr;
TAILQ_FOREACH_SAFE(vaddr, &ivr->ivr_addrs, va_entry, tvaddr) {
if (sockaddr_cmp(addr, (struct sockaddr *)&vaddr->va_addr, -1))
continue;
if (sockaddr_cmp(mask, (struct sockaddr *)&vaddr->va_mask, -1))
continue;
if (ifidx != vaddr->va_ifidx)
continue;
TAILQ_REMOVE(&ivr->ivr_addrs, vaddr, va_entry);
free(vaddr);
}
}
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, uint32_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 = &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, *gateway = NULL;
uint8_t *ptr;
size_t left;
int addrs = 0;
int type, flags;
uint32_t rdomain;
ptr = (uint8_t *)imsg->data;
left = IMSG_DATA_SIZE(imsg);
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_GATEWAY | 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);
gateway = (struct sockaddr *)ptr;
if (left < gateway->sa_len)
return (-1);
socket_setport(gateway, 0);
ptr += gateway->sa_len;
left -= gateway->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;
}
if (type == RTM_ADD)
vroute_insertroute(env, rdomain, dest, mask);
else
vroute_removeroute(env, rdomain, dest, mask);
return (vroute_doroute(env, flags, addrs, rdomain, type,
dest, mask, gateway, NULL));
}
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;
uint32_t rdomain;
int flags;
int addrs;
int need_gw;
ptr = (uint8_t *)imsg->data;
left = IMSG_DATA_SIZE(imsg);
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));
if (left < sizeof(struct sockaddr))
return (-1);
dst = (struct sockaddr *)ptr;
if (left < dst->sa_len)
return (-1);
memcpy(&dest, dst, dst->sa_len);
ptr += dst->sa_len;
left -= dst->sa_len;
/* Get route to peer */
flags = RTF_UP | RTF_HOST | RTF_STATIC;
if (vroute_doroute(env, flags, RTA_DST, rdomain, RTM_GET,
(struct sockaddr *)&dest, (struct sockaddr *)&mask,
(struct sockaddr *)&addr, &need_gw))
return (-1);
if (need_gw)
flags |= RTF_GATEWAY;
memcpy(&dest, dst, dst->sa_len);
socket_setport((struct sockaddr *)&dest, 0);
vroute_insertroute(env, rdomain, (struct sockaddr *)&dest, NULL);
/* Set explicit route to peer with gateway addr*/
addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
return (vroute_doroute(env, flags, addrs, rdomain, RTM_ADD,
(struct sockaddr *)&dest, (struct sockaddr *)&mask,
(struct sockaddr *)&addr, NULL));
}
int
vroute_dodns(struct iked *env, struct sockaddr *dns, int add,
unsigned int ifidx)
{
struct vroute_msg m_rtmsg;
struct sockaddr_in *in;
struct sockaddr_in6 *in6;
struct sockaddr_rtdns rtdns;
struct iked_vroute_sc *ivr = env->sc_vroute;
struct iovec iov[3];
int i;
long pad = 0;
int iovcnt = 0, padlen;
bzero(&m_rtmsg, sizeof(m_rtmsg));
#define rtm m_rtmsg.vm_rtm
rtm.rtm_version = RTM_VERSION;
rtm.rtm_type = RTM_PROPOSAL;
rtm.rtm_seq = ++ivr->ivr_rtseq;
rtm.rtm_priority = RTP_PROPOSAL_STATIC;
rtm.rtm_flags = RTF_UP;
rtm.rtm_addrs = RTA_DNS;
rtm.rtm_index = ifidx;
iov[iovcnt].iov_base = &rtm;
iov[iovcnt].iov_len = sizeof(rtm);
iovcnt++;
bzero(&rtdns, sizeof(rtdns));
rtdns.sr_family = dns->sa_family;
rtdns.sr_len = 2;
if (add) {
switch(dns->sa_family) {
case AF_INET:
rtdns.sr_family = AF_INET;
rtdns.sr_len += sizeof(struct in_addr);
in = (struct sockaddr_in *)dns;
memcpy(rtdns.sr_dns, &in->sin_addr, sizeof(struct in_addr));
break;
case AF_INET6:
rtdns.sr_family = AF_INET6;
rtdns.sr_len += sizeof(struct in6_addr);
in6 = (struct sockaddr_in6 *)dns;
memcpy(rtdns.sr_dns, &in6->sin6_addr, sizeof(struct in6_addr));
break;
default:
return (-1);
}
}
iov[iovcnt].iov_base = &rtdns;
iov[iovcnt++].iov_len = sizeof(rtdns);
padlen = ROUNDUP(sizeof(rtdns)) - sizeof(rtdns);
if (padlen > 0) {
iov[iovcnt].iov_base = &pad;
iov[iovcnt++].iov_len = padlen;
}
for (i = 0; i < iovcnt; i++)
rtm.rtm_msglen += iov[i].iov_len;
#undef rtm
if (writev(ivr->ivr_rtsock, iov, iovcnt) == -1)
log_warn("failed to send route message");
return (0);
}
int
vroute_doroute(struct iked *env, int flags, int addrs, int rdomain, uint8_t type,
struct sockaddr *dest, struct sockaddr *mask, struct sockaddr *addr, int *need_gw)
{
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 (%s%s)"
" addrs %x (dst %s mask %s gw %s)", __func__, rtm.rtm_msglen,
type == RTM_ADD ? "RTM_ADD" : type == RTM_DELETE ? "RTM_DELETE" :
type == RTM_GET ? "RTM_GET" : "unknown", rdomain,
flags,
flags & RTF_HOST ? "H" : "",
flags & RTF_GATEWAY ? "G" : "",
addrs,
addrs & RTA_DST ? print_addr(dest) : "<>",
addrs & RTA_NETMASK ? print_addr(mask) : "<>",
addrs & RTA_GATEWAY ? print_addr(addr) : "<>");
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 (0);
}
}
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, need_gw));
}
#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, int *need_gw)
{
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;
*need_gw = rtm.rtm_flags & RTF_GATEWAY;
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;
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));
log_debug("%s: %s inet %s netmask %s", __func__,
add ? "add" : "del", print_addr(addr), print_addr(mask));
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));
log_debug("%s: %s inet6 %s netmask %s", __func__,
add ? "add" : "del", print_addr(addr), print_addr(mask));
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);
}