]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
kernel-netlink: Add support for full packet and policy HW offloading
authorTobias Brunner <tobias@strongswan.org>
Thu, 15 Dec 2022 15:41:43 +0000 (16:41 +0100)
committerTobias Brunner <tobias@strongswan.org>
Thu, 16 Feb 2023 12:25:34 +0000 (13:25 +0100)
src/include/linux/xfrm.h
src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c
src/libcharon/plugins/vici/vici_config.c
src/libstrongswan/ipsec/ipsec_types.c
src/libstrongswan/ipsec/ipsec_types.h

index 805071799d2cb51166ea12ad482f75135a3341b6..094772dfbceb3edaa416df59d5469029579a85c7 100644 (file)
@@ -503,6 +503,7 @@ struct xfrm_user_offload {
 };
 #define XFRM_OFFLOAD_IPV6      1
 #define XFRM_OFFLOAD_INBOUND   2
+#define XFRM_OFFLOAD_PACKET    4
 
 #ifndef __KERNEL__
 /* backwards compatibility for userspace */
index 38670c41d77cd0fd81e6d5e5f8a63e8d2521591e..2f7ec9fb3c8d2216cfab3453e5b9caec60a20bbc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2019 Tobias Brunner
+ * Copyright (C) 2006-2022 Tobias Brunner
  * Copyright (C) 2005-2009 Martin Willi
  * Copyright (C) 2008-2016 Andreas Steffen
  * Copyright (C) 2006-2007 Fabian Hartmann, Noah Heusser
@@ -392,6 +392,9 @@ struct ipsec_sa_t {
        /** Optional mark */
        uint32_t if_id;
 
+       /** Optional HW offload */
+       hw_offload_t hw_offload;
+
        /** Description of this SA */
        ipsec_sa_cfg_t cfg;
 
@@ -408,7 +411,8 @@ static u_int ipsec_sa_hash(ipsec_sa_t *sa)
                                                  chunk_hash_inc(sa->dst->get_address(sa->dst),
                                                  chunk_hash_inc(chunk_from_thing(sa->mark),
                                                  chunk_hash_inc(chunk_from_thing(sa->if_id),
-                                                 chunk_hash(chunk_from_thing(sa->cfg))))));
+                                                 chunk_hash_inc(chunk_from_thing(sa->hw_offload),
+                                                 chunk_hash(chunk_from_thing(sa->cfg)))))));
 }
 
 /**
@@ -421,6 +425,7 @@ static bool ipsec_sa_equals(ipsec_sa_t *sa, ipsec_sa_t *other_sa)
                   sa->mark.value == other_sa->mark.value &&
                   sa->mark.mask == other_sa->mark.mask &&
                   sa->if_id == other_sa->if_id &&
+                  sa->hw_offload == other_sa->hw_offload &&
                   ipsec_sa_cfg_equals(&sa->cfg, &other_sa->cfg);
 }
 
@@ -429,7 +434,8 @@ 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,
-                                                                  uint32_t if_id, ipsec_sa_cfg_t *cfg)
+                                                                  uint32_t if_id, hw_offload_t hw_offload,
+                                                                  ipsec_sa_cfg_t *cfg)
 {
        ipsec_sa_t *sa, *found;
        INIT(sa,
@@ -437,6 +443,7 @@ static ipsec_sa_t *ipsec_sa_create(private_kernel_netlink_ipsec_t *this,
                .dst = dst,
                .mark = mark,
                .if_id = if_id,
+               .hw_offload = hw_offload,
                .cfg = *cfg,
        );
        found = this->sas->get(this->sas, sa);
@@ -511,7 +518,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,
-       uint32_t if_id, ipsec_sa_cfg_t *cfg)
+       uint32_t if_id, hw_offload_t hw_offload, ipsec_sa_cfg_t *cfg)
 {
        policy_sa_t *policy;
 
@@ -529,7 +536,7 @@ 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, if_id, cfg);
+       policy->sa = ipsec_sa_create(this, src, dst, mark, if_id, hw_offload, cfg);
        return policy;
 }
 
@@ -1521,53 +1528,60 @@ static bool netlink_detect_offload(const char *ifname)
 #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.
+ * Add a HW offload attribute to the given message, return it if it was added.
+ *
+ * There are 4 HW offload configuration values:
+ * 1. HW_OFFLOAD_NO     : Do not configure HW offload.
+ * 2. HW_OFFLOAD_CRYPTO : Configure crypto HW offload.
+ *                        Fail SA addition if crypto offload is not supported.
+ * 3. HW_OFFLOAD_PACKET : Configure packet HW offload.
+ *                        Fail SA addition if packet offload is not supported.
+ * 4. HW_OFFLOAD_AUTO   : Configure packet HW offload if supported by the kernel
+ *                        and device.  If not, configure crypto HW offload if
+ *                        supported by the kernel and device.
+ *                        Do not fail SA addition if offload is not supported.
  */
-static bool config_hw_offload(kernel_ipsec_sa_id_t *id,
-                                                         kernel_ipsec_add_sa_t *data, struct nlmsghdr *hdr,
-                                                         int buflen)
+static bool add_hw_offload(struct nlmsghdr *hdr, int buflen, host_t *local,
+                                                  hw_offload_t hw_offload,
+                                                  struct xfrm_user_offload **offload)
 {
-       host_t *local = data->inbound ? id->dst : id->src;
-       struct xfrm_user_offload *offload;
-       bool hw_offload_yes, ret = FALSE;
        char *ifname;
+       bool ret;
 
-       /* do Ipsec configuration without offload */
-       if (data->hw_offload == HW_OFFLOAD_NO)
+       /* do IPsec configuration without offload */
+       if (hw_offload == HW_OFFLOAD_NO)
        {
                return TRUE;
        }
 
-       hw_offload_yes = (data->hw_offload == HW_OFFLOAD_YES);
+       /* unless offloading is forced, we return TRUE even if we fail */
+       ret = (hw_offload == HW_OFFLOAD_AUTO);
 
        if (!charon->kernel->get_interface(charon->kernel, local, &ifname))
        {
-               return !hw_offload_yes;
+               return ret;
        }
 
        /* 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)
+       *offload = netlink_reserve(hdr, buflen,
+                                                          XFRMA_OFFLOAD_DEV, sizeof(**offload));
+       if (!(*offload))
        {
-               ret = !hw_offload_yes;
                goto out;
        }
-       offload->ifindex = if_nametoindex(ifname);
-       offload->flags |= data->inbound ? XFRM_OFFLOAD_INBOUND : 0;
+       (*offload)->ifindex = if_nametoindex(ifname);
+
+       if (hw_offload == HW_OFFLOAD_PACKET ||
+               hw_offload == HW_OFFLOAD_AUTO)
+       {
+               (*offload)->flags |= XFRM_OFFLOAD_PACKET;
+       }
 
        ret = TRUE;
 
@@ -1576,6 +1590,58 @@ out:
        return ret;
 }
 
+/**
+ * Add a HW offload attribute to the given SA-related message.
+ */
+static bool add_hw_offload_sa(struct nlmsghdr *hdr, int buflen,
+                                                         kernel_ipsec_sa_id_t *id,
+                                                         kernel_ipsec_add_sa_t *data,
+                                                         struct xfrm_user_offload **offload)
+{
+       host_t *local = data->inbound ? id->dst : id->src;
+
+       if (!add_hw_offload(hdr, buflen, local, data->hw_offload, offload))
+       {
+               return FALSE;
+       }
+       else if (*offload)
+       {
+               (*offload)->flags |= data->inbound ? XFRM_OFFLOAD_INBOUND : 0;
+       }
+       return TRUE;
+}
+
+/**
+ * Add a HW offload attribuet to the given policy-related message.
+ */
+static bool add_hw_offload_policy(struct nlmsghdr *hdr, int buflen,
+                                                                 policy_entry_t *policy,
+                                                                 policy_sa_t *mapping,
+                                                                 struct xfrm_user_offload **offload)
+{
+       ipsec_sa_t *ipsec = mapping->sa;
+       host_t *local = ipsec->src;
+
+       /* only packet offloading is supported for policies, which we try to use
+        * in automatic mode */
+       if (ipsec->hw_offload != HW_OFFLOAD_PACKET &&
+               ipsec->hw_offload != HW_OFFLOAD_AUTO)
+       {
+               return TRUE;
+       }
+
+       switch (policy->direction)
+       {
+               case POLICY_FWD:
+                       /* FWD policies are not offloaded, they are enforced by the kernel */
+                       return TRUE;
+               case POLICY_IN:
+                       local = ipsec->dst;
+                       break;
+       }
+       return add_hw_offload(hdr, buflen, local, ipsec->hw_offload, offload);
+}
+
 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)
@@ -1585,6 +1651,7 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
        char markstr[32] = "";
        struct nlmsghdr *hdr;
        struct xfrm_usersa_info *sa;
+       struct xfrm_user_offload *offload = NULL;
        uint16_t icv_size = 64, ipcomp = data->ipcomp;
        ipsec_mode_t mode = data->mode, original_mode = data->mode;
        traffic_selector_t *first_src_ts, *first_dst_ts;
@@ -2007,7 +2074,7 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
                }
 
                DBG2(DBG_KNL, "  HW offload: %N", hw_offload_names, data->hw_offload);
-               if (!config_hw_offload(id, data, hdr, sizeof(request)))
+               if (!add_hw_offload_sa(hdr, sizeof(request), id, data, &offload))
                {
                        DBG1(DBG_KNL, "failed to configure HW offload");
                        goto failed;
@@ -2015,6 +2082,16 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
        }
 
        status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
+
+       if (status != SUCCESS && offload && data->hw_offload == HW_OFFLOAD_AUTO)
+       {
+               DBG1(DBG_KNL, "failed to install SA with %N HW offload, trying with "
+                        "%N HW offload", hw_offload_names, HW_OFFLOAD_PACKET,
+                        hw_offload_names, HW_OFFLOAD_CRYPTO);
+               offload->flags &= ~XFRM_OFFLOAD_PACKET;
+               status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
+       }
+
        if (status == NOT_FOUND && data->update)
        {
                DBG1(DBG_KNL, "allocated SPI not found anymore, try to add SAD entry");
@@ -2745,6 +2822,7 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this,
        policy_entry_t clone;
        ipsec_sa_t *ipsec = mapping->sa;
        struct xfrm_userpolicy_info *policy_info;
+       struct xfrm_user_offload *offload = NULL;
        struct nlmsghdr *hdr;
        status_t status;
        int i;
@@ -2858,9 +2936,26 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this,
                policy_change_done(this, policy);
                return FAILED;
        }
+       /* make sure this is the last attribute added to the message */
+       if (!add_hw_offload_policy(hdr, sizeof(request), policy, mapping, &offload))
+       {
+               policy_change_done(this, policy);
+               return FAILED;
+       }
        this->mutex->unlock(this->mutex);
 
        status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
+
+       if (status != SUCCESS && offload && mapping->sa->hw_offload == HW_OFFLOAD_AUTO)
+       {
+               DBG1(DBG_KNL, "failed to install SA with %N HW offload, trying without "
+                        "offload", hw_offload_names, HW_OFFLOAD_PACKET);
+               /* the kernel only allows offloading with packet offload and rejects
+                * the attribute if that flag is not set, so remove it again */
+               hdr->nlmsg_len -= RTA_ALIGN(RTA_LENGTH(sizeof(*offload)));
+               status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
+       }
+
        if (status == ALREADY_DONE && !update)
        {
                DBG1(DBG_KNL, "policy already exists, try to update it");
@@ -2948,7 +3043,7 @@ 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,
-                                                                  id->if_id, data->sa);
+                                                                  id->if_id, data->hw_offload, 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;
@@ -3129,6 +3224,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t,
                .dst = data->dst,
                .mark = id->mark,
                .if_id = id->if_id,
+               .hw_offload = data->hw_offload,
                .cfg = *data->sa,
        };
        char markstr[32] = "", labelstr[128] = "";
index a59d799caf64a5fb526bcf6efa35495c4d8a7f2b..6f589fe20718e45ac8c16cd5dde0931867667cfe 100644 (file)
@@ -1040,9 +1040,9 @@ CALLBACK(parse_hw_offload, bool,
        action_t *out, chunk_t v)
 {
        enum_map_t map[] = {
-               { "no",         HW_OFFLOAD_NO   },
-               { "yes",        HW_OFFLOAD_YES  },
-               { "auto",       HW_OFFLOAD_AUTO },
+               { "no",         HW_OFFLOAD_NO           },
+               { "yes",        HW_OFFLOAD_CRYPTO       },
+               { "auto",       HW_OFFLOAD_AUTO         },
        };
        int d;
 
index 0c57160001a50e45cc5daa1430b54201df8be7f9..bd8a4a52cb9f51f235d488435a24e6caa49a27a1 100644 (file)
@@ -40,7 +40,8 @@ ENUM(ipcomp_transform_names, IPCOMP_NONE, IPCOMP_LZJH,
 
 ENUM(hw_offload_names, HW_OFFLOAD_NO, HW_OFFLOAD_AUTO,
        "no",
-       "yes",
+       "crypto",
+       "packet",
        "auto",
 );
 
index e96c9f27aafa9e7e77bb2d877ccf003ce1d2ee1b..5320c46abc5ee20f1996bff1e1e7364937ededc8 100644 (file)
@@ -125,8 +125,9 @@ extern enum_name_t *ipcomp_transform_names;
  */
 enum hw_offload_t {
        HW_OFFLOAD_NO = 0,
-       HW_OFFLOAD_YES = 1,
-       HW_OFFLOAD_AUTO = 2,
+       HW_OFFLOAD_CRYPTO = 1,
+       HW_OFFLOAD_PACKET = 2,
+       HW_OFFLOAD_AUTO = 3,
 };
 
 /**