socket-default: Add an option to force the sending interface via IP_PKTINFO

On Linux, setting the source address is insufficient to force a packet to be
sent over a certain path. The kernel uses the best route to select the outgoing
interface, even if we set a source address of a lower priority interface. This
is not only true for interfaces attaching to the same subnet, but also for
unrelated interfaces; the kernel (at least on 4.7) sends out the packet on
whatever interface it sees fit, even if that network does not expect packets
from the source address we force to.

When a better interface becomes available, strongSwan sends its MOBIKE address
list update using the old source address. But the kernel sends that packet over
the new best interface. If that network drops packets having the unexpected
source address from the old path, the MOBIKE update fails and the SA finally
times out.

To enforce a specific interface for our packet, we explicitly set the interface
index from the interface where the source address is installed. According to
ip(7), this overrules the specified source address to the primary interface
address. As this could have side effects to installations using multiple
addresses on a single interface, we disable the option by default for now.

This also allows using IPv6 link-local addresses, which won't work if
the outbound interface is not set explicitly.
This commit is contained in:
Martin Willi 2016-09-16 14:50:07 +02:00 committed by Tobias Brunner
parent 46a3f92a76
commit 9b29003cd9
2 changed files with 54 additions and 7 deletions

View File

@ -4,6 +4,12 @@ charon.plugins.socket-default.fwmark =
charon.plugins.socket-default.set_source = yes
Set source address on outbound packets, if possible.
charon.plugins.socket-default.set_sourceif = no
Force sending interface on outbound packets, if possible.
Force sending interface on outbound packets, if possible. This allows
using IPv6 link-local addresses as tunnel endpoints.
charon.plugins.socket-default.use_ipv4 = yes
Listen on IPv4, if possible.

View File

@ -141,6 +141,11 @@ struct private_socket_default_socket_t {
*/
bool set_source;
/**
* TRUE to force sending source interface on outbound packetrs
*/
bool set_sourceif;
/**
* A counter to implement round-robin selection of read sockets
*/
@ -362,12 +367,33 @@ static ssize_t send_msg_generic(int skt, struct msghdr *msg)
return sendmsg(skt, msg, 0);
}
#if defined(IP_PKTINFO) || defined(HAVE_IN6_PKTINFO)
/**
* Find the interface index a source address is installed on
*/
static int find_srcif(host_t *src)
{
char *ifname;
int idx = 0;
if (charon->kernel->get_interface(charon->kernel, src, &ifname))
{
idx = if_nametoindex(ifname);
free(ifname);
}
return idx;
}
#endif /* IP_PKTINFO || HAVE_IN6_PKTINFO */
/**
* Send a message with the IPv4 source address set, if possible.
*/
#ifdef IP_PKTINFO
static ssize_t send_msg_v4(int skt, struct msghdr *msg, host_t *src)
static ssize_t send_msg_v4(private_socket_default_socket_t *this, int skt,
struct msghdr *msg, host_t *src)
{
char buf[CMSG_SPACE(sizeof(struct in_pktinfo))] = {};
struct cmsghdr *cmsg;
@ -383,6 +409,10 @@ static ssize_t send_msg_v4(int skt, struct msghdr *msg, host_t *src)
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsg);
if (this->set_sourceif)
{
pktinfo->ipi_ifindex = find_srcif(src);
}
addr = &pktinfo->ipi_spec_dst;
sin = (struct sockaddr_in*)src->get_sockaddr(src);
@ -392,7 +422,8 @@ static ssize_t send_msg_v4(int skt, struct msghdr *msg, host_t *src)
#elif defined(IP_SENDSRCADDR)
static ssize_t send_msg_v4(int skt, struct msghdr *msg, host_t *src)
static ssize_t send_msg_v4(private_socket_default_socket_t *this, int skt,
struct msghdr *msg, host_t *src)
{
char buf[CMSG_SPACE(sizeof(struct in_addr))] = {};
struct cmsghdr *cmsg;
@ -415,7 +446,8 @@ static ssize_t send_msg_v4(int skt, struct msghdr *msg, host_t *src)
#else /* IP_PKTINFO || IP_RECVDSTADDR */
static ssize_t send_msg_v4(int skt, struct msghdr *msg, host_t *src)
static ssize_t send_msg_v4(private_socket_default_socket_t *this,
int skt, struct msghdr *msg, host_t *src)
{
return send_msg_generic(skt, msg);
}
@ -427,7 +459,8 @@ static ssize_t send_msg_v4(int skt, struct msghdr *msg, host_t *src)
*/
#ifdef HAVE_IN6_PKTINFO
static ssize_t send_msg_v6(int skt, struct msghdr *msg, host_t *src)
static ssize_t send_msg_v6(private_socket_default_socket_t *this, int skt,
struct msghdr *msg, host_t *src)
{
char buf[CMSG_SPACE(sizeof(struct in6_pktinfo))] = {};
struct cmsghdr *cmsg;
@ -441,6 +474,10 @@ static ssize_t send_msg_v6(int skt, struct msghdr *msg, host_t *src)
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
pktinfo = (struct in6_pktinfo*)CMSG_DATA(cmsg);
if (this->set_sourceif)
{
pktinfo->ipi6_ifindex = find_srcif(src);
}
sin = (struct sockaddr_in6*)src->get_sockaddr(src);
memcpy(&pktinfo->ipi6_addr, &sin->sin6_addr, sizeof(struct in6_addr));
return send_msg_generic(skt, msg);
@ -448,7 +485,8 @@ static ssize_t send_msg_v6(int skt, struct msghdr *msg, host_t *src)
#else /* HAVE_IN6_PKTINFO */
static ssize_t send_msg_v6(int skt, struct msghdr *msg, host_t *src)
static ssize_t send_msg_v6(private_socket_default_socket_t *this,
int skt, struct msghdr *msg, host_t *src)
{
return send_msg_generic(skt, msg);
}
@ -564,11 +602,11 @@ METHOD(socket_t, sender, status_t,
{
if (family == AF_INET)
{
bytes_sent = send_msg_v4(skt, &msg, src);
bytes_sent = send_msg_v4(this, skt, &msg, src);
}
else
{
bytes_sent = send_msg_v6(skt, &msg, src);
bytes_sent = send_msg_v6(this, skt, &msg, src);
}
}
else
@ -831,6 +869,9 @@ socket_default_socket_t *socket_default_socket_create()
.set_source = lib->settings->get_bool(lib->settings,
"%s.plugins.socket-default.set_source", TRUE,
lib->ns),
.set_sourceif = lib->settings->get_bool(lib->settings,
"%s.plugins.socket-default.set_sourceif", FALSE,
lib->ns),
);
if (this->port && this->port == this->natt)