Merge branch 'tun-device-ipv6'

Adds support for IPv6 addresses on TUN devices, in particular, to support
virtual IPv6 addresses on FreeBSD/macOS.

Closes strongswan/strongswan#2465
This commit is contained in:
Tobias Brunner
2024-09-25 16:12:49 +02:00
2 changed files with 196 additions and 65 deletions
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2009-2016 Tobias Brunner
* Copyright (C) 2009-2024 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
@@ -1443,6 +1443,35 @@ static void add_rt_ifname(struct rt_msghdr *hdr, int type, char *name)
}
}
/**
* Append an interface address sockaddr_dl to routing message
*/
static void add_rt_ifaddr(struct rt_msghdr *hdr, int type, char *name)
{
struct ifaddrs *ifap, *ifa;
struct sockaddr_dl *sdl;
if (getifaddrs(&ifap) < 0)
{
return;
}
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next)
{
if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_LINK ||
!streq(name, ifa->ifa_name))
{
continue;
}
sdl = (struct sockaddr_dl*)ifa->ifa_addr;
memcpy((char*)hdr + hdr->rtm_msglen, sdl, sdl->sdl_len);
hdr->rtm_msglen += SA_LEN(sdl->sdl_len);
hdr->rtm_addrs |= type;
break;
}
freeifaddrs(ifap);
}
/**
* Add or remove a route
*/
@@ -1519,6 +1548,10 @@ static status_t manage_route(private_kernel_pfroute_net_t *this, int op,
{
add_rt_addr(&msg.hdr, RTA_GATEWAY, gateway);
}
else if (if_name)
{
add_rt_ifaddr(&msg.hdr, RTA_GATEWAY, if_name);
}
break;
default:
break;
+162 -64
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2012 Tobias Brunner
* Copyright (C) 2012-2014 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* Copyright (C) 2012 Martin Willi
@@ -59,12 +59,11 @@ tun_device_t *tun_device_create(const char *name_tmpl)
#elif defined(__linux__)
#include <linux/types.h>
#include <linux/if_tun.h>
#elif __FreeBSD__ >= 10
#include <linux/ipv6.h>
#else
#include <net/if_tun.h>
#include <net/if_var.h>
#include <netinet/in_var.h>
#else
#include <net/if_tun.h>
#endif
#define TUN_DEFAULT_MTU 1500
@@ -93,6 +92,11 @@ struct private_tun_device_t {
*/
int sock;
/**
* Socket used for ioctl() to set IPv6 interface addr, ...
*/
int sock_v6;
/**
* The current MTU
*/
@@ -109,13 +113,83 @@ struct private_tun_device_t {
uint8_t netmask;
};
/**
* FreeBSD 10 deprecated the SIOCSIFADDR etc. commands.
*/
#if __FreeBSD__ >= 10
#ifdef __linux__
static bool set_address_and_mask(struct in_aliasreq *ifra, host_t *addr,
uint8_t netmask)
/**
* Set an IPv4 address and netmask
*/
static bool set_address_v4(private_tun_device_t *this, host_t *addr,
uint8_t netmask)
{
struct ifreq ifr = {0};
host_t *mask;
strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ);
memcpy(&ifr.ifr_addr, addr->get_sockaddr(addr),
*addr->get_sockaddr_len(addr));
if (ioctl(this->sock, SIOCSIFADDR, &ifr) < 0)
{
DBG1(DBG_LIB, "failed to set address on %s: %s",
this->if_name, strerror(errno));
return FALSE;
}
mask = host_create_netmask(addr->get_family(addr), netmask);
if (!mask)
{
DBG1(DBG_LIB, "invalid netmask: %d", netmask);
return FALSE;
}
memcpy(&ifr.ifr_addr, mask->get_sockaddr(mask),
*mask->get_sockaddr_len(mask));
mask->destroy(mask);
if (ioctl(this->sock, SIOCSIFNETMASK, &ifr) < 0)
{
DBG1(DBG_LIB, "failed to set netmask on %s: %s",
this->if_name, strerror(errno));
return FALSE;
}
return TRUE;
}
/**
* Set an IPv6 address and netmask
*/
static bool set_address_v6(private_tun_device_t *this, host_t *addr,
uint8_t netmask)
{
struct in6_ifreq ifr6 = {0};
struct ifreq ifr = {0};
strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ);
if (ioctl(this->sock_v6, SIOCGIFINDEX, &ifr) < 0)
{
DBG1(DBG_LIB, "failed to determine index of %s: %s",
this->if_name, strerror(errno));
return FALSE;
}
memcpy(&ifr6.ifr6_addr, addr->get_sockaddr(addr),
*addr->get_sockaddr_len(addr));
ifr6.ifr6_prefixlen = netmask;
ifr6.ifr6_ifindex = ifr.ifr_ifindex;
if (ioctl(this->sock_v6, SIOCSIFADDR, &ifr6) < 0)
{
DBG1(DBG_LIB, "failed to set address on %s: %s",
this->if_name, strerror(errno));
return FALSE;
}
return TRUE;
}
#else /* __linux__ */
/**
* Apply address and mask for IPv4
*/
static bool set_address_and_mask_v4(struct in_aliasreq *ifra, host_t *addr,
uint8_t netmask)
{
host_t *mask;
@@ -138,20 +212,18 @@ static bool set_address_and_mask(struct in_aliasreq *ifra, host_t *addr,
}
/**
* Set the address using the more flexible SIOCAIFADDR/SIOCDIFADDR commands
* on FreeBSD 10 an newer.
* Set the address for IPv4
*/
static bool set_address_impl(private_tun_device_t *this, host_t *addr,
uint8_t netmask)
static bool set_address_v4(private_tun_device_t *this, host_t *addr,
uint8_t netmask)
{
struct in_aliasreq ifra;
struct in_aliasreq ifra = {0};
memset(&ifra, 0, sizeof(ifra));
strncpy(ifra.ifra_name, this->if_name, IFNAMSIZ);
if (this->address)
{ /* remove the existing address first */
if (!set_address_and_mask(&ifra, this->address, this->netmask))
if (!set_address_and_mask_v4(&ifra, this->address, this->netmask))
{
return FALSE;
}
@@ -162,7 +234,7 @@ static bool set_address_impl(private_tun_device_t *this, host_t *addr,
return FALSE;
}
}
if (!set_address_and_mask(&ifra, addr, netmask))
if (!set_address_and_mask_v4(&ifra, addr, netmask))
{
return FALSE;
}
@@ -175,36 +247,19 @@ static bool set_address_impl(private_tun_device_t *this, host_t *addr,
return TRUE;
}
#else /* __FreeBSD__ */
/**
* Set the address using the classic SIOCSIFADDR etc. commands on other systems.
* Apply address and mask for IPv6
*/
static bool set_address_impl(private_tun_device_t *this, host_t *addr,
uint8_t netmask)
static bool set_address_and_mask_v6(struct in6_aliasreq *ifra, host_t *addr,
uint8_t netmask)
{
struct ifreq ifr;
host_t *mask;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ);
memcpy(&ifr.ifr_addr, addr->get_sockaddr(addr),
memcpy(&ifra->ifra_addr, addr->get_sockaddr(addr),
*addr->get_sockaddr_len(addr));
/* set the same address as destination address */
memcpy(&ifra->ifra_dstaddr, addr->get_sockaddr(addr),
*addr->get_sockaddr_len(addr));
if (ioctl(this->sock, SIOCSIFADDR, &ifr) < 0)
{
DBG1(DBG_LIB, "failed to set address on %s: %s",
this->if_name, strerror(errno));
return FALSE;
}
#ifdef __APPLE__
if (ioctl(this->sock, SIOCSIFDSTADDR, &ifr) < 0)
{
DBG1(DBG_LIB, "failed to set dest address on %s: %s",
this->if_name, strerror(errno));
return FALSE;
}
#endif /* __APPLE__ */
mask = host_create_netmask(addr->get_family(addr), netmask);
if (!mask)
@@ -212,27 +267,71 @@ static bool set_address_impl(private_tun_device_t *this, host_t *addr,
DBG1(DBG_LIB, "invalid netmask: %d", netmask);
return FALSE;
}
memcpy(&ifr.ifr_addr, mask->get_sockaddr(mask),
memcpy(&ifra->ifra_prefixmask, mask->get_sockaddr(mask),
*mask->get_sockaddr_len(mask));
mask->destroy(mask);
return TRUE;
}
if (ioctl(this->sock, SIOCSIFNETMASK, &ifr) < 0)
/**
* Set the address for IPv6
*/
static bool set_address_v6(private_tun_device_t *this, host_t *addr,
uint8_t netmask)
{
struct in6_aliasreq ifra = {
.ifra_lifetime = { 0, 0, 0xffffffff, 0xffffffff },
};
strncpy(ifra.ifra_name, this->if_name, IFNAMSIZ);
if (this->address)
{ /* remove the existing address first */
if (!set_address_and_mask_v6(&ifra, this->address, this->netmask))
{
return FALSE;
}
if (ioctl(this->sock_v6, SIOCDIFADDR_IN6, &ifra) < 0)
{
DBG1(DBG_LIB, "failed to remove existing address on %s: %s",
this->if_name, strerror(errno));
return FALSE;
}
}
if (!set_address_and_mask_v6(&ifra, addr, netmask))
{
DBG1(DBG_LIB, "failed to set netmask on %s: %s",
return FALSE;
}
if (ioctl(this->sock_v6, SIOCAIFADDR_IN6, &ifra) < 0)
{
DBG1(DBG_LIB, "failed to add address on %s: %s",
this->if_name, strerror(errno));
return FALSE;
}
return TRUE;
}
#endif /* __FreeBSD__ */
#endif /* __linux__ */
METHOD(tun_device_t, set_address, bool,
private_tun_device_t *this, host_t *addr, uint8_t netmask)
{
if (!set_address_impl(this, addr, netmask))
switch (addr->get_family(addr))
{
return FALSE;
case AF_INET:
if (!set_address_v4(this, addr, netmask))
{
return FALSE;
}
break;
case AF_INET6:
if (!set_address_v6(this, addr, netmask))
{
return FALSE;
}
break;
default:
return FALSE;
}
DESTROY_IF(this->address);
this->address = addr->clone(addr);
@@ -253,9 +352,8 @@ METHOD(tun_device_t, get_address, host_t*,
METHOD(tun_device_t, up, bool,
private_tun_device_t *this)
{
struct ifreq ifr;
struct ifreq ifr = {0};
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ);
if (ioctl(this->sock, SIOCGIFFLAGS, &ifr) < 0)
@@ -279,9 +377,8 @@ METHOD(tun_device_t, up, bool,
METHOD(tun_device_t, set_mtu, bool,
private_tun_device_t *this, int mtu)
{
struct ifreq ifr;
struct ifreq ifr = {0};
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ);
ifr.ifr_mtu = mtu;
@@ -298,14 +395,13 @@ METHOD(tun_device_t, set_mtu, bool,
METHOD(tun_device_t, get_mtu, int,
private_tun_device_t *this)
{
struct ifreq ifr;
struct ifreq ifr = {0};
if (this->mtu > 0)
{
return this->mtu;
}
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ);
this->mtu = TUN_DEFAULT_MTU;
@@ -390,9 +486,8 @@ METHOD(tun_device_t, destroy, void,
/* tun(4) says the following: "These network interfaces persist until
* the if_tun.ko module is unloaded, or until removed with the
* ifconfig(8) command." So simply closing the FD is not enough. */
struct ifreq ifr;
struct ifreq ifr = {0};
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ);
if (ioctl(this->sock, SIOCIFDESTROY, &ifr) < 0)
{
@@ -416,13 +511,10 @@ static bool init_tun(private_tun_device_t *this, const char *name_tmpl)
{
#ifdef __APPLE__
struct ctl_info info;
struct sockaddr_ctl addr;
struct ctl_info info = {0};
struct sockaddr_ctl addr = {0};
socklen_t size = IFNAMSIZ;
memset(&info, 0, sizeof(info));
memset(&addr, 0, sizeof(addr));
this->tunfd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
if (this->tunfd < 0)
{
@@ -464,7 +556,7 @@ static bool init_tun(private_tun_device_t *this, const char *name_tmpl)
#elif defined(IFF_TUN)
struct ifreq ifr;
struct ifreq ifr = {0};
strncpy(this->if_name, name_tmpl ?: "tun%d", IFNAMSIZ-1);
this->if_name[IFNAMSIZ-1] = '\0';
@@ -476,8 +568,6 @@ static bool init_tun(private_tun_device_t *this, const char *name_tmpl)
return FALSE;
}
memset(&ifr, 0, sizeof(ifr));
/* TUN device, no packet info */
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
@@ -576,6 +666,14 @@ tun_device_t *tun_device_create(const char *name_tmpl)
destroy(this);
return NULL;
}
/* FIXME: allow this to fail? (just ignore IPv6 addrs later?) */
this->sock_v6 = socket(AF_INET6, SOCK_DGRAM, 0);
if (this->sock_v6 < 0)
{
DBG1(DBG_LIB, "failed to open IPv6 socket to configure TUN device");
destroy(this);
return NULL;
}
return &this->public;
}