socket-default: Add an option to force the sending interface via IP_PKTINFO
authorMartin Willi <martin@strongswan.org>
Fri, 16 Sep 2016 12:50:07 +0000 (14:50 +0200)
committerTobias Brunner <tobias@strongswan.org>
Tue, 23 May 2017 14:49:39 +0000 (16:49 +0200)
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.

conf/plugins/socket-default.opt
src/libcharon/plugins/socket_default/socket_default_socket.c

index 483a0f0..570bd0e 100644 (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.
 
index ba22b0c..109b3fe 100644 (file)
@@ -142,6 +142,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
         */
        u_int rr_counter;
@@ -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)