]> git.ipfire.org Git - thirdparty/strongswan.git/blobdiff - src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c
kernel-netlink: Extract shared route handling code in net/ipsec
[thirdparty/strongswan.git] / src / libcharon / plugins / kernel_netlink / kernel_netlink_ipsec.c
index 9a40927d2184b5698d67a7f2c80dac1bf55d3bd2..ef0d424bd31dbb53120b7ae2cb10683235d77181 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2017 Tobias Brunner
+ * Copyright (C) 2006-2019 Tobias Brunner
  * Copyright (C) 2005-2009 Martin Willi
  * Copyright (C) 2008-2016 Andreas Steffen
  * Copyright (C) 2006-2007 Fabian Hartmann, Noah Heusser
  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  * for more details.
  */
+/*
+ * Copyright (C) 2018 Mellanox Technologies.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
 
 #define _GNU_SOURCE
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/ioctl.h>
 #include <stdint.h>
 #include <linux/ipsec.h>
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
 #include <linux/xfrm.h>
 #include <linux/udp.h>
+#include <linux/ethtool.h>
+#include <linux/sockios.h>
 #include <net/if.h>
 #include <unistd.h>
 #include <time.h>
 /** Base priority for installed policies */
 #define PRIO_BASE 200000
 
-/** Default lifetime of an acquire XFRM state (in seconds) */
-#define DEFAULT_ACQUIRE_LIFETIME 165
-
 /**
  * Map the limit for bytes and packets to XFRM_INF by default
  */
@@ -146,7 +167,7 @@ ENUM(xfrm_msg_names, XFRM_MSG_NEWSA, XFRM_MSG_MAPPING,
        "XFRM_MSG_MAPPING"
 );
 
-ENUM(xfrm_attr_type_names, XFRMA_UNSPEC, XFRMA_REPLAY_ESN_VAL,
+ENUM(xfrm_attr_type_names, XFRMA_UNSPEC, XFRMA_OFFLOAD_DEV,
        "XFRMA_UNSPEC",
        "XFRMA_ALG_AUTH",
        "XFRMA_ALG_CRYPT",
@@ -171,6 +192,11 @@ ENUM(xfrm_attr_type_names, XFRMA_UNSPEC, XFRMA_REPLAY_ESN_VAL,
        "XFRMA_MARK",
        "XFRMA_TFCPAD",
        "XFRMA_REPLAY_ESN_VAL",
+       "XFRMA_SA_EXTRA_FLAGS",
+       "XFRMA_PROTO",
+       "XFRMA_ADDRESS_FILTER",
+       "XFRMA_PAD",
+       "XFRMA_OFFLOAD_DEV",
 );
 
 /**
@@ -344,51 +370,6 @@ struct private_kernel_netlink_ipsec_t {
                                                         kernel_ipsec_manage_policy_t *data);
 };
 
-typedef struct route_entry_t route_entry_t;
-
-/**
- * Installed routing entry
- */
-struct route_entry_t {
-       /** Name of the interface the route is bound to */
-       char *if_name;
-
-       /** Source ip of the route */
-       host_t *src_ip;
-
-       /** Gateway for this route */
-       host_t *gateway;
-
-       /** Destination net */
-       chunk_t dst_net;
-
-       /** Destination net prefixlen */
-       uint8_t prefixlen;
-};
-
-/**
- * Destroy a route_entry_t object
- */
-static void route_entry_destroy(route_entry_t *this)
-{
-       free(this->if_name);
-       this->src_ip->destroy(this->src_ip);
-       DESTROY_IF(this->gateway);
-       chunk_free(&this->dst_net);
-       free(this);
-}
-
-/**
- * Compare two route_entry_t objects
- */
-static bool route_entry_equals(route_entry_t *a, route_entry_t *b)
-{
-       return a->if_name && b->if_name && streq(a->if_name, b->if_name) &&
-                  a->src_ip->ip_equals(a->src_ip, b->src_ip) &&
-                  a->gateway->ip_equals(a->gateway, b->gateway) &&
-                  chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen;
-}
-
 typedef struct ipsec_sa_t ipsec_sa_t;
 
 /**
@@ -404,6 +385,9 @@ struct ipsec_sa_t {
        /** Optional mark */
        mark_t mark;
 
+       /** Optional mark */
+       uint32_t if_id;
+
        /** Description of this SA */
        ipsec_sa_cfg_t cfg;
 
@@ -419,7 +403,8 @@ static u_int ipsec_sa_hash(ipsec_sa_t *sa)
        return chunk_hash_inc(sa->src->get_address(sa->src),
                                                  chunk_hash_inc(sa->dst->get_address(sa->dst),
                                                  chunk_hash_inc(chunk_from_thing(sa->mark),
-                                                 chunk_hash(chunk_from_thing(sa->cfg)))));
+                                                 chunk_hash_inc(chunk_from_thing(sa->if_id),
+                                                 chunk_hash(chunk_from_thing(sa->cfg))))));
 }
 
 /**
@@ -431,6 +416,7 @@ static bool ipsec_sa_equals(ipsec_sa_t *sa, ipsec_sa_t *other_sa)
                   sa->dst->ip_equals(sa->dst, other_sa->dst) &&
                   sa->mark.value == other_sa->mark.value &&
                   sa->mark.mask == other_sa->mark.mask &&
+                  sa->if_id == other_sa->if_id &&
                   ipsec_sa_cfg_equals(&sa->cfg, &other_sa->cfg);
 }
 
@@ -439,13 +425,14 @@ static bool ipsec_sa_equals(ipsec_sa_t *sa, ipsec_sa_t *other_sa)
  */
 static ipsec_sa_t *ipsec_sa_create(private_kernel_netlink_ipsec_t *this,
                                                                   host_t *src, host_t *dst, mark_t mark,
-                                                                  ipsec_sa_cfg_t *cfg)
+                                                                  uint32_t if_id, ipsec_sa_cfg_t *cfg)
 {
        ipsec_sa_t *sa, *found;
        INIT(sa,
                .src = src,
                .dst = dst,
                .mark = mark,
+               .if_id = if_id,
                .cfg = *cfg,
        );
        found = this->sas->get(this->sas, sa);
@@ -520,7 +507,7 @@ struct policy_sa_out_t {
 static policy_sa_t *policy_sa_create(private_kernel_netlink_ipsec_t *this,
        policy_dir_t dir, policy_type_t type, host_t *src, host_t *dst,
        traffic_selector_t *src_ts, traffic_selector_t *dst_ts, mark_t mark,
-       ipsec_sa_cfg_t *cfg)
+       uint32_t if_id, ipsec_sa_cfg_t *cfg)
 {
        policy_sa_t *policy;
 
@@ -538,17 +525,17 @@ static policy_sa_t *policy_sa_create(private_kernel_netlink_ipsec_t *this,
                INIT(policy, .priority = 0);
        }
        policy->type = type;
-       policy->sa = ipsec_sa_create(this, src, dst, mark, cfg);
+       policy->sa = ipsec_sa_create(this, src, dst, mark, if_id, cfg);
        return policy;
 }
 
 /**
  * Destroy a policy_sa(_in)_t object
  */
-static void policy_sa_destroy(policy_sa_t *policy, policy_dir_t *dir,
+static void policy_sa_destroy(policy_sa_t *policy, policy_dir_t dir,
                                                          private_kernel_netlink_ipsec_t *this)
 {
-       if (*dir == POLICY_OUT)
+       if (dir == POLICY_OUT)
        {
                policy_sa_out_t *out = (policy_sa_out_t*)policy;
                out->src_ts->destroy(out->src_ts);
@@ -558,6 +545,16 @@ static void policy_sa_destroy(policy_sa_t *policy, policy_dir_t *dir,
        free(policy);
 }
 
+CALLBACK(policy_sa_destroy_cb, void,
+       policy_sa_t *policy, va_list args)
+{
+       private_kernel_netlink_ipsec_t *this;
+       policy_dir_t dir;
+
+       VA_ARGS_VGET(args, dir, this);
+       policy_sa_destroy(policy, dir, this);
+}
+
 typedef struct policy_entry_t policy_entry_t;
 
 /**
@@ -574,6 +571,9 @@ struct policy_entry_t {
        /** Optional mark */
        uint32_t mark;
 
+       /** Optional interface ID */
+       uint32_t if_id;
+
        /** Associated route installed for this policy */
        route_entry_t *route;
 
@@ -602,9 +602,8 @@ static void policy_entry_destroy(private_kernel_netlink_ipsec_t *this,
        }
        if (policy->used_by)
        {
-               policy->used_by->invoke_function(policy->used_by,
-                                                                               (linked_list_invoke_t)policy_sa_destroy,
-                                                                                &policy->direction, this);
+               policy->used_by->invoke_function(policy->used_by, policy_sa_destroy_cb,
+                                                                                policy->direction, this);
                policy->used_by->destroy(policy->used_by);
        }
        free(policy);
@@ -616,7 +615,8 @@ static void policy_entry_destroy(private_kernel_netlink_ipsec_t *this,
 static u_int policy_hash(policy_entry_t *key)
 {
        chunk_t chunk = chunk_from_thing(key->sel);
-       return chunk_hash_inc(chunk, chunk_hash(chunk_from_thing(key->mark)));
+       return chunk_hash_inc(chunk, chunk_hash_inc(chunk_from_thing(key->mark),
+                                                 chunk_hash(chunk_from_thing(key->if_id))));
 }
 
 /**
@@ -626,6 +626,7 @@ static bool policy_equals(policy_entry_t *key, policy_entry_t *other_key)
 {
        return memeq(&key->sel, &other_key->sel, sizeof(struct xfrm_selector)) &&
                   key->mark == other_key->mark &&
+                  key->if_id == other_key->if_id &&
                   key->direction == other_key->direction;
 }
 
@@ -1075,7 +1076,7 @@ static void process_mapping(private_kernel_netlink_ipsec_t *this,
 static bool receive_events(private_kernel_netlink_ipsec_t *this, int fd,
                                                   watcher_event_t event)
 {
-       char response[1024];
+       char response[netlink_get_buflen()];
        struct nlmsghdr *hdr = (struct nlmsghdr*)response;
        struct sockaddr_nl addr;
        socklen_t addr_len = sizeof(addr);
@@ -1135,7 +1136,7 @@ static bool receive_events(private_kernel_netlink_ipsec_t *this, int fd,
 METHOD(kernel_ipsec_t, get_features, kernel_feature_t,
        private_kernel_netlink_ipsec_t *this)
 {
-       return KERNEL_ESP_V3_TFC;
+       return KERNEL_ESP_V3_TFC | KERNEL_POLICY_SPI;
 }
 
 /**
@@ -1279,6 +1280,233 @@ static bool add_mark(struct nlmsghdr *hdr, int buflen, mark_t mark)
        return TRUE;
 }
 
+/**
+ * Add a uint32 attribute to message
+ */
+static bool add_uint32(struct nlmsghdr *hdr, int buflen,
+                                          enum xfrm_attr_type_t type, uint32_t value)
+{
+       uint32_t *xvalue;
+
+       xvalue = netlink_reserve(hdr, buflen, type, sizeof(*xvalue));
+       if (!xvalue)
+       {
+               return FALSE;
+       }
+       *xvalue = value;
+       return TRUE;
+}
+
+/* ETHTOOL_GSSET_INFO is available since 2.6.34 and ETH_SS_FEATURES (enum) and
+ * ETHTOOL_GFEATURES since 2.6.39, so check for the latter */
+#ifdef ETHTOOL_GFEATURES
+
+/**
+ * Global metadata used for IPsec HW offload
+ */
+static struct {
+       /** determined HW offload support */
+       bool supported;
+       /** bit in feature set */
+       u_int bit;
+       /** total number of device feature blocks */
+       u_int total_blocks;
+} netlink_hw_offload;
+
+/**
+ * Check if kernel supports HW offload and determine feature flag
+ */
+static void netlink_find_offload_feature(const char *ifname)
+{
+       struct ethtool_sset_info *sset_info;
+       struct ethtool_gstrings *cmd = NULL;
+       struct ifreq ifr;
+       uint32_t sset_len, i;
+       char *str;
+       int err, query_socket;
+
+       query_socket = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM);
+       if (query_socket < 0)
+       {
+               return;
+       }
+
+       /* determine number of device features */
+       INIT_EXTRA(sset_info, sizeof(uint32_t),
+               .cmd = ETHTOOL_GSSET_INFO,
+               .sset_mask = 1ULL << ETH_SS_FEATURES,
+       );
+       strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+       ifr.ifr_name[IFNAMSIZ-1] = '\0';
+       ifr.ifr_data = (void*)sset_info;
+
+       err = ioctl(query_socket, SIOCETHTOOL, &ifr);
+       if (err || sset_info->sset_mask != 1ULL << ETH_SS_FEATURES)
+       {
+               goto out;
+       }
+       sset_len = sset_info->data[0];
+
+       /* retrieve names of device features */
+       INIT_EXTRA(cmd, ETH_GSTRING_LEN * sset_len,
+               .cmd = ETHTOOL_GSTRINGS,
+               .string_set = ETH_SS_FEATURES,
+       );
+       strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+       ifr.ifr_name[IFNAMSIZ-1] = '\0';
+       ifr.ifr_data = (void*)cmd;
+
+       err = ioctl(query_socket, SIOCETHTOOL, &ifr);
+       if (err)
+       {
+               goto out;
+       }
+
+       /* look for the ESP_HW feature bit */
+       str = (char*)cmd->data;
+       for (i = 0; i < cmd->len; i++)
+       {
+               if (strneq(str, "esp-hw-offload", ETH_GSTRING_LEN))
+               {
+                       netlink_hw_offload.supported = TRUE;
+                       netlink_hw_offload.bit = i;
+                       netlink_hw_offload.total_blocks = (sset_len + 31) / 32;
+                       break;
+               }
+               str += ETH_GSTRING_LEN;
+       }
+
+out:
+       free(sset_info);
+       free(cmd);
+       close(query_socket);
+}
+
+/**
+ * Check if interface supported HW offload
+ */
+static bool netlink_detect_offload(const char *ifname)
+{
+       struct ethtool_gfeatures *cmd;
+       uint32_t feature_bit;
+       struct ifreq ifr;
+       int query_socket;
+       int block;
+       bool ret = FALSE;
+
+       if (!netlink_hw_offload.supported)
+       {
+               DBG1(DBG_KNL, "HW offload is not supported by kernel");
+               return FALSE;
+       }
+
+       query_socket = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM);
+       if (query_socket < 0)
+       {
+               return FALSE;
+       }
+
+       /* feature is supported by kernel, query device features */
+       INIT_EXTRA(cmd, sizeof(cmd->features[0]) * netlink_hw_offload.total_blocks,
+               .cmd = ETHTOOL_GFEATURES,
+               .size = netlink_hw_offload.total_blocks,
+       );
+       strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+       ifr.ifr_name[IFNAMSIZ-1] = '\0';
+       ifr.ifr_data = (void*)cmd;
+
+       if (!ioctl(query_socket, SIOCETHTOOL, &ifr))
+       {
+               block = netlink_hw_offload.bit / 32;
+               feature_bit = 1U << (netlink_hw_offload.bit % 32);
+               if (cmd->features[block].active & feature_bit)
+               {
+                       ret = TRUE;
+               }
+       }
+
+       if (!ret)
+       {
+               DBG1(DBG_KNL, "HW offload is not supported by device");
+       }
+       free(cmd);
+       close(query_socket);
+       return ret;
+}
+
+#else
+
+static void netlink_find_offload_feature(const char *ifname)
+{
+}
+
+static bool netlink_detect_offload(const char *ifname)
+{
+       return FALSE;
+}
+
+#endif
+
+/**
+ * There are 3 HW offload configuration values:
+ * 1. HW_OFFLOAD_NO   : Do not configure HW offload.
+ * 2. HW_OFFLOAD_YES  : Configure HW offload.
+ *                      Fail SA addition if offload is not supported.
+ * 3. HW_OFFLOAD_AUTO : Configure HW offload if supported by the kernel
+ *                      and device.
+ *                      Do not fail SA addition otherwise.
+ */
+static bool config_hw_offload(kernel_ipsec_sa_id_t *id,
+                                                         kernel_ipsec_add_sa_t *data, struct nlmsghdr *hdr,
+                                                         int buflen)
+{
+       host_t *local = data->inbound ? id->dst : id->src;
+       struct xfrm_user_offload *offload;
+       bool hw_offload_yes, ret = FALSE;
+       char *ifname;
+
+       /* do Ipsec configuration without offload */
+       if (data->hw_offload == HW_OFFLOAD_NO)
+       {
+               return TRUE;
+       }
+
+       hw_offload_yes = (data->hw_offload == HW_OFFLOAD_YES);
+
+       if (!charon->kernel->get_interface(charon->kernel, local, &ifname))
+       {
+               return !hw_offload_yes;
+       }
+
+       /* check if interface supports hw_offload */
+       if (!netlink_detect_offload(ifname))
+       {
+               ret = !hw_offload_yes;
+               goto out;
+       }
+
+       /* activate HW offload */
+       offload = netlink_reserve(hdr, buflen,
+                                                         XFRMA_OFFLOAD_DEV, sizeof(*offload));
+       if (!offload)
+       {
+               ret = !hw_offload_yes;
+               goto out;
+       }
+       offload->ifindex = if_nametoindex(ifname);
+       if (local->get_family(local) == AF_INET6)
+       {
+               offload->flags |= XFRM_OFFLOAD_IPV6;
+       }
+       offload->flags |= data->inbound ? XFRM_OFFLOAD_INBOUND : 0;
+
+       ret = TRUE;
+
+out:
+       free(ifname);
+       return ret;
+}
+
 METHOD(kernel_ipsec_t, add_sa, status_t,
        private_kernel_netlink_ipsec_t *this, kernel_ipsec_sa_id_t *id,
        kernel_ipsec_add_sa_t *data)
@@ -1304,6 +1532,7 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
                        .spi = htonl(ntohs(data->cpi)),
                        .proto = IPPROTO_COMP,
                        .mark = id->mark,
+                       .if_id = id->if_id,
                };
                kernel_ipsec_add_sa_t ipcomp_sa = {
                        .reqid = data->reqid,
@@ -1343,6 +1572,49 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
        sa->id.proto = id->proto;
        sa->family = id->src->get_family(id->src);
        sa->mode = mode2kernel(mode);
+
+       if (!data->copy_df)
+       {
+               sa->flags |= XFRM_STATE_NOPMTUDISC;
+       }
+
+       if (!data->copy_ecn)
+       {
+               sa->flags |= XFRM_STATE_NOECN;
+       }
+
+       if (data->inbound)
+       {
+               switch (data->copy_dscp)
+               {
+                       case DSCP_COPY_YES:
+                       case DSCP_COPY_IN_ONLY:
+                               sa->flags |= XFRM_STATE_DECAP_DSCP;
+                               break;
+                       default:
+                               break;
+               }
+       }
+       else
+       {
+               switch (data->copy_dscp)
+               {
+                       case DSCP_COPY_IN_ONLY:
+                       case DSCP_COPY_NO:
+                       {
+                               /* currently the only extra flag */
+                               if (!add_uint32(hdr, sizeof(request), XFRMA_SA_EXTRA_FLAGS,
+                                                               XFRM_SA_XFLAG_DONT_ENCAP_DSCP))
+                               {
+                                       goto failed;
+                               }
+                               break;
+                       }
+                       default:
+                               break;
+               }
+       }
+
        switch (mode)
        {
                case MODE_TUNNEL:
@@ -1586,17 +1858,28 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
                goto failed;
        }
 
+       if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+       {
+               goto failed;
+       }
+
+       if (ipcomp == IPCOMP_NONE && (data->mark.value | data->mark.mask))
+       {
+               if (!add_uint32(hdr, sizeof(request), XFRMA_SET_MARK,
+                                               data->mark.value) ||
+                       !add_uint32(hdr, sizeof(request), XFRMA_SET_MARK_MASK,
+                                               data->mark.mask))
+               {
+                       goto failed;
+               }
+       }
+
        if (data->tfc && id->proto == IPPROTO_ESP && mode == MODE_TUNNEL)
        {       /* the kernel supports TFC padding only for tunnel mode ESP SAs */
-               uint32_t *tfcpad;
-
-               tfcpad = netlink_reserve(hdr, sizeof(request), XFRMA_TFCPAD,
-                                                                sizeof(*tfcpad));
-               if (!tfcpad)
+               if (!add_uint32(hdr, sizeof(request), XFRMA_TFCPAD, data->tfc))
                {
                        goto failed;
                }
-               *tfcpad = data->tfc;
        }
 
        if (id->proto != IPPROTO_COMP)
@@ -1639,37 +1922,28 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
                                 data->replay_window);
                        sa->replay_window = data->replay_window;
                }
-               if (data->hw_offload)
-               {
-                       host_t *local = data->inbound ? id->dst : id->src;
-                       char *ifname;
-
-                       if (charon->kernel->get_interface(charon->kernel, local, &ifname))
-                       {
-                               struct xfrm_user_offload *offload;
 
-                               offload = netlink_reserve(hdr, sizeof(request),
-                                                                                 XFRMA_OFFLOAD_DEV, sizeof(*offload));
-                               if (!offload)
-                               {
-                                       free(ifname);
-                                       goto failed;
-                               }
-                               offload->ifindex = if_nametoindex(ifname);
-                               if (local->get_family(local) == AF_INET6)
-                               {
-                                       offload->flags |= XFRM_OFFLOAD_IPV6;
-                               }
-                               offload->flags |= data->inbound ? XFRM_OFFLOAD_INBOUND : 0;
-                               free(ifname);
-                       }
+               DBG2(DBG_KNL, "  HW offload: %N", hw_offload_names, data->hw_offload);
+               if (!config_hw_offload(id, data, hdr, sizeof(request)))
+               {
+                       DBG1(DBG_KNL, "failed to configure HW offload");
+                       goto failed;
                }
        }
 
-       if (this->socket_xfrm->send_ack(this->socket_xfrm, hdr) != SUCCESS)
+       status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
+       if (status == NOT_FOUND && data->update)
        {
-               DBG1(DBG_KNL, "unable to add SAD entry with SPI %.8x%s", ntohl(id->spi),
-                        markstr);
+               DBG1(DBG_KNL, "allocated SPI not found anymore, try to add SAD entry");
+               hdr->nlmsg_type = XFRM_MSG_NEWSA;
+               status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
+       }
+
+       if (status != SUCCESS)
+       {
+               DBG1(DBG_KNL, "unable to add SAD entry with SPI %.8x%s (%N)", ntohl(id->spi),
+                        markstr, status_names, status);
+               status = FAILED;
                goto failed;
        }
 
@@ -1721,6 +1995,10 @@ static void get_replay_state(private_kernel_netlink_ipsec_t *this,
        {
                return;
        }
+       if (sa->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, sa->if_id))
+       {
+               return;
+       }
 
        if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS)
        {
@@ -1819,6 +2097,10 @@ METHOD(kernel_ipsec_t, query_sa, status_t,
        {
                return FAILED;
        }
+       if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+       {
+               return FAILED;
+       }
 
        if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS)
        {
@@ -1923,6 +2205,10 @@ METHOD(kernel_ipsec_t, del_sa, status_t,
        {
                return FAILED;
        }
+       if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+       {
+               return FAILED;
+       }
 
        switch (this->socket_xfrm->send_ack(this->socket_xfrm, hdr))
        {
@@ -1957,6 +2243,7 @@ METHOD(kernel_ipsec_t, update_sa, status_t,
        uint32_t replay_esn_len = 0;
        kernel_ipsec_del_sa_t del = { 0 };
        status_t status = FAILED;
+       traffic_selector_t *ts;
        char markstr[32] = "";
 
        /* if IPComp is used, we first update the IPComp SA */
@@ -1968,6 +2255,7 @@ METHOD(kernel_ipsec_t, update_sa, status_t,
                        .spi = htonl(ntohs(data->cpi)),
                        .proto = IPPROTO_COMP,
                        .mark = id->mark,
+                       .if_id = id->if_id,
                };
                kernel_ipsec_update_sa_t ipcomp = {
                        .new_src = data->new_src,
@@ -1998,6 +2286,10 @@ METHOD(kernel_ipsec_t, update_sa, status_t,
        {
                return FAILED;
        }
+       if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+       {
+               return FAILED;
+       }
 
        if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS)
        {
@@ -2060,10 +2352,26 @@ METHOD(kernel_ipsec_t, update_sa, status_t,
        if (!id->src->ip_equals(id->src, data->new_src))
        {
                host2xfrm(data->new_src, &sa->saddr);
+
+               ts = selector2ts(&sa->sel, TRUE);
+               if (ts && ts->is_host(ts, id->src))
+               {
+                       ts->set_address(ts, data->new_src);
+                       ts2subnet(ts, &sa->sel.saddr, &sa->sel.prefixlen_s);
+               }
+               DESTROY_IF(ts);
        }
        if (!id->dst->ip_equals(id->dst, data->new_dst))
        {
                host2xfrm(data->new_dst, &sa->id.daddr);
+
+               ts = selector2ts(&sa->sel, FALSE);
+               if (ts && ts->is_host(ts, id->dst))
+               {
+                       ts->set_address(ts, data->new_dst);
+                       ts2subnet(ts, &sa->sel.daddr, &sa->sel.prefixlen_d);
+               }
+               DESTROY_IF(ts);
        }
 
        rta = XFRM_RTA(out_hdr, struct xfrm_usersa_info);
@@ -2257,89 +2565,97 @@ static void install_route(private_kernel_netlink_ipsec_t *this,
 
        INIT(route,
                .prefixlen = policy->sel.prefixlen_d,
+               .pass = mapping->type == POLICY_PASS,
        );
 
        if (charon->kernel->get_address_by_ts(charon->kernel, out->src_ts,
-                                                                                 &route->src_ip, NULL) == SUCCESS)
+                                                                                 &route->src_ip, NULL) != SUCCESS)
        {
-               if (!ipsec->dst->is_anyaddr(ipsec->dst))
+               if (!route->pass)
                {
-                       route->gateway = charon->kernel->get_nexthop(charon->kernel,
-                                                                                               ipsec->dst, -1, ipsec->src,
-                                                                                               &route->if_name);
+                       free(route);
+                       return;
                }
-               else
-               {       /* for shunt policies */
-                       iface = xfrm2host(policy->sel.family, &policy->sel.daddr, 0);
-                       route->gateway = charon->kernel->get_nexthop(charon->kernel,
-                                                                                               iface, policy->sel.prefixlen_d,
-                                                                                               route->src_ip, &route->if_name);
-                       iface->destroy(iface);
-               }
-               route->dst_net = chunk_alloc(policy->sel.family == AF_INET ? 4 : 16);
-               memcpy(route->dst_net.ptr, &policy->sel.daddr, route->dst_net.len);
-
-               /* get the interface to install the route for, if we haven't one yet.
-                * If we have a local address, use it. Otherwise (for shunt policies)
-                * use the route's source address. */
-               if (!route->if_name)
-               {
-                       iface = ipsec->src;
-                       if (iface->is_anyaddr(iface))
-                       {
-                               iface = route->src_ip;
-                       }
-                       if (!charon->kernel->get_interface(charon->kernel, iface,
-                                                                                          &route->if_name))
-                       {
-                               route_entry_destroy(route);
-                               return;
-                       }
+               /* allow blank source IP for passthrough policies */
+               route->src_ip = host_create_any(policy->sel.family);
+       }
+
+       if (!ipsec->dst->is_anyaddr(ipsec->dst))
+       {
+               route->gateway = charon->kernel->get_nexthop(charon->kernel,
+                                                                                       ipsec->dst, -1, ipsec->src,
+                                                                                       &route->if_name);
+       }
+       else
+       {       /* for shunt policies */
+               iface = xfrm2host(policy->sel.family, &policy->sel.daddr, 0);
+               route->gateway = charon->kernel->get_nexthop(charon->kernel,
+                                                                                       iface, policy->sel.prefixlen_d,
+                                                                                       route->src_ip, &route->if_name);
+               iface->destroy(iface);
+       }
+       route->dst_net = chunk_alloc(policy->sel.family == AF_INET ? 4 : 16);
+       memcpy(route->dst_net.ptr, &policy->sel.daddr, route->dst_net.len);
+
+       /* get the interface to install the route for, if we haven't one yet.
+        * If we have a local address, use it. Otherwise (for shunt policies)
+        * use the route's source address. */
+       if (!route->if_name)
+       {
+               iface = ipsec->src;
+               if (iface->is_anyaddr(iface))
+               {
+                       iface = route->src_ip;
                }
-               if (policy->route)
+               if (!charon->kernel->get_interface(charon->kernel, iface,
+                                                                                  &route->if_name) &&
+                       !route->pass)
+               {       /* don't require an interface for passthrough policies */
+                       route_entry_destroy(route);
+                       return;
+               }
+       }
+       if (policy->route)
+       {
+               route_entry_t *old = policy->route;
+               if (route_entry_equals(old, route))
                {
-                       route_entry_t *old = policy->route;
-                       if (route_entry_equals(old, route))
-                       {
-                               route_entry_destroy(route);
-                               return;
-                       }
-                       /* uninstall previously installed route */
-                       if (charon->kernel->del_route(charon->kernel, old->dst_net,
-                                                                                 old->prefixlen, old->gateway,
-                                                                                 old->src_ip, old->if_name) != SUCCESS)
-                       {
-                               DBG1(DBG_KNL, "error uninstalling route installed with policy "
-                                        "%R === %R %N", out->src_ts, out->dst_ts, policy_dir_names,
-                                        policy->direction);
-                       }
-                       route_entry_destroy(old);
-                       policy->route = NULL;
+                       route_entry_destroy(route);
+                       return;
                }
-
-               DBG2(DBG_KNL, "installing route: %R via %H src %H dev %s", out->dst_ts,
-                        route->gateway, route->src_ip, route->if_name);
-               switch (charon->kernel->add_route(charon->kernel, route->dst_net,
-                                                                                 route->prefixlen, route->gateway,
-                                                                                 route->src_ip, route->if_name))
+               /* uninstall previously installed route */
+               if (charon->kernel->del_route(charon->kernel, old->dst_net,
+                                                                         old->prefixlen, old->gateway,
+                                                                         old->src_ip, old->if_name,
+                                                                         old->pass) != SUCCESS)
                {
-                       default:
-                               DBG1(DBG_KNL, "unable to install source route for %H",
-                                        route->src_ip);
-                               /* FALL */
-                       case ALREADY_DONE:
-                               /* route exists, do not uninstall */
-                               route_entry_destroy(route);
-                               break;
-                       case SUCCESS:
-                               /* cache the installed route */
-                               policy->route = route;
-                               break;
+                       DBG1(DBG_KNL, "error uninstalling route installed with policy "
+                                "%R === %R %N", out->src_ts, out->dst_ts, policy_dir_names,
+                                policy->direction);
                }
+               route_entry_destroy(old);
+               policy->route = NULL;
        }
-       else
+
+       DBG2(DBG_KNL, "installing route: %R via %H src %H dev %s", out->dst_ts,
+                route->gateway, route->src_ip, route->if_name);
+       switch (charon->kernel->add_route(charon->kernel, route->dst_net,
+                                                                         route->prefixlen, route->gateway,
+                                                                         route->src_ip, route->if_name,
+                                                                         route->pass))
        {
-               free(route);
+               default:
+                       DBG1(DBG_KNL, "unable to install source route for %H",
+                                route->src_ip);
+                       /* FALL */
+               case ALREADY_DONE:
+                       /* route exists, do not uninstall */
+                       route_entry_destroy(route);
+                       break;
+               case SUCCESS:
+                       /* cache the installed route */
+                       policy->route = route;
+                       break;
        }
 }
 
@@ -2394,11 +2710,13 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this,
                struct xfrm_user_tmpl *tmpl;
                struct {
                        uint8_t proto;
+                       uint32_t spi;
                        bool use;
                } protos[] = {
-                       { IPPROTO_COMP, ipsec->cfg.ipcomp.transform != IPCOMP_NONE },
-                       { IPPROTO_ESP, ipsec->cfg.esp.use },
-                       { IPPROTO_AH, ipsec->cfg.ah.use },
+                       { IPPROTO_COMP, htonl(ntohs(ipsec->cfg.ipcomp.cpi)),
+                         ipsec->cfg.ipcomp.transform != IPCOMP_NONE },
+                       { IPPROTO_ESP, ipsec->cfg.esp.spi, ipsec->cfg.esp.use },
+                       { IPPROTO_AH, ipsec->cfg.ah.spi, ipsec->cfg.ah.use },
                };
                ipsec_mode_t proto_mode = ipsec->cfg.mode;
                int count = 0;
@@ -2426,6 +2744,10 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this,
                        }
                        tmpl->reqid = ipsec->cfg.reqid;
                        tmpl->id.proto = protos[i].proto;
+                       if (policy->direction == POLICY_OUT)
+                       {
+                               tmpl->id.spi = protos[i].spi;
+                       }
                        tmpl->aalgos = tmpl->ealgos = tmpl->calgos = ~0;
                        tmpl->mode = mode2kernel(proto_mode);
                        tmpl->optional = protos[i].proto == IPPROTO_COMP &&
@@ -2450,6 +2772,12 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this,
                policy_change_done(this, policy);
                return FAILED;
        }
+       if (ipsec->if_id &&
+               !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, ipsec->if_id))
+       {
+               policy_change_done(this, policy);
+               return FAILED;
+       }
        this->mutex->unlock(this->mutex);
 
        status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
@@ -2470,10 +2798,12 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this,
         * - this is an outbound policy (to just get one for each child)
         * - routing is not disabled via strongswan.conf
         * - the selector is not for a specific protocol/port
+        * - no XFRM interface ID is configured
         * - we are in tunnel/BEET mode or install a bypass policy
         */
        if (policy->direction == POLICY_OUT && this->install_routes &&
-               !policy->sel.proto && !policy->sel.dport && !policy->sel.sport)
+               !policy->sel.proto && !policy->sel.dport && !policy->sel.sport &&
+               !policy->if_id)
        {
                if (mapping->type == POLICY_PASS ||
                   (mapping->type == POLICY_IPSEC && ipsec->cfg.mode != MODE_TRANSPORT))
@@ -2501,6 +2831,7 @@ METHOD(kernel_ipsec_t, add_policy, status_t,
        INIT(policy,
                .sel = ts2selector(id->src_ts, id->dst_ts, id->interface),
                .mark = id->mark.value & id->mark.mask,
+               .if_id = id->if_id,
                .direction = id->dir,
                .reqid = data->sa->reqid,
        );
@@ -2546,7 +2877,8 @@ METHOD(kernel_ipsec_t, add_policy, status_t,
 
        /* cache the assigned IPsec SA */
        assigned_sa = policy_sa_create(this, id->dir, data->type, data->src,
-                                               data->dst, id->src_ts, id->dst_ts, id->mark, data->sa);
+                                                                  data->dst, id->src_ts, id->dst_ts, id->mark,
+                                                                  id->if_id, data->sa);
        assigned_sa->auto_priority = get_priority(policy, data->prio, id->interface);
        assigned_sa->priority = this->get_priority ? this->get_priority(id, data)
                                                                                           : data->manual_prio;
@@ -2644,6 +2976,10 @@ METHOD(kernel_ipsec_t, query_policy, status_t,
        {
                return FAILED;
        }
+       if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+       {
+               return FAILED;
+       }
 
        if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS)
        {
@@ -2712,6 +3048,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t,
                .src = data->src,
                .dst = data->dst,
                .mark = id->mark,
+               .if_id = id->if_id,
                .cfg = *data->sa,
        };
        char markstr[32] = "";
@@ -2727,6 +3064,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t,
        memset(&policy, 0, sizeof(policy_entry_t));
        policy.sel = ts2selector(id->src_ts, id->dst_ts, id->interface);
        policy.mark = id->mark.value & id->mark.mask;
+       policy.if_id = id->if_id;
        policy.direction = id->dir;
 
        /* find the policy */
@@ -2762,7 +3100,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t,
                        ipsec_sa_equals(mapping->sa, &assigned_sa))
                {
                        current->used_by->remove_at(current->used_by, enumerator);
-                       policy_sa_destroy(mapping, &id->dir, this);
+                       policy_sa_destroy(mapping, id->dir, this);
                        break;
                }
                if (is_installed)
@@ -2817,13 +3155,19 @@ METHOD(kernel_ipsec_t, del_policy, status_t,
                policy_change_done(this, current);
                return FAILED;
        }
+       if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+       {
+               policy_change_done(this, current);
+               return FAILED;
+       }
 
        if (current->route)
        {
                route_entry_t *route = current->route;
                if (charon->kernel->del_route(charon->kernel, route->dst_net,
                                                                          route->prefixlen, route->gateway,
-                                                                         route->src_ip, route->if_name) != SUCCESS)
+                                                                         route->src_ip, route->if_name,
+                                                                         route->pass) != SUCCESS)
                {
                        DBG1(DBG_KNL, "error uninstalling route installed with policy "
                                 "%R === %R %N%s", id->src_ts, id->dst_ts, policy_dir_names,
@@ -3222,7 +3566,6 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create()
 {
        private_kernel_netlink_ipsec_t *this;
        bool register_for_events = TRUE;
-       FILE *f;
 
        INIT(this,
                .public = {
@@ -3267,15 +3610,6 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create()
                register_for_events = FALSE;
        }
 
-       f = fopen("/proc/sys/net/core/xfrm_acq_expires", "w");
-       if (f)
-       {
-               fprintf(f, "%u", lib->settings->get_int(lib->settings,
-                                                               "%s.plugins.kernel-netlink.xfrm_acq_expires",
-                                                               DEFAULT_ACQUIRE_LIFETIME, lib->ns));
-               fclose(f);
-       }
-
        this->socket_xfrm = netlink_socket_create(NETLINK_XFRM, xfrm_msg_names,
                                lib->settings->get_bool(lib->settings,
                                        "%s.plugins.kernel-netlink.parallel_xfrm", FALSE, lib->ns));
@@ -3317,5 +3651,9 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create()
                                                  (watcher_cb_t)receive_events, this);
        }
 
+       netlink_find_offload_feature(lib->settings->get_str(lib->settings,
+                                       "%s.plugins.kernel-netlink.hw_offload_feature_interface",
+                                       "lo", lib->ns));
+
        return &this->public;
 }