]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
kernel-netlink: Read last use time from SA if possible
authorTobias Brunner <tobias@strongswan.org>
Thu, 23 Jun 2022 14:15:45 +0000 (16:15 +0200)
committerTobias Brunner <tobias@strongswan.org>
Wed, 22 Feb 2023 12:20:10 +0000 (13:20 +0100)
Since 6.2 the Linux kernel updates the last use time per SA.  In
previous releases the attribute was only updated and reported for
specific outbound IPv6 SAs.

Using this reduces the number of kernel queries per CHILD_SA: for DPDs
from two policy queries (IN/FWD) to a single query of the inbound SA,
and for status reports the three policy queries (IN/FWD/OUT) can be
omitted and only the two SAs have to be queried.  For NAT keepalives the
number of queries doesn't change but a policy query (OUT) is replaced by
a query for the outbound SA.

While we could use the existence of the attribute as indicator for its
support, we don't know this until we queried an SA.  By using a version
check we can announce the feature from the start.

src/include/linux/xfrm.h
src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c

index 094772dfbceb3edaa416df59d5469029579a85c7..80630dfd4a59ac7f282901d7cb2a496a2a801d12 100644 (file)
@@ -288,7 +288,7 @@ enum xfrm_attr_type_t {
        XFRMA_ETIMER_THRESH,
        XFRMA_SRCADDR,          /* xfrm_address_t */
        XFRMA_COADDR,           /* xfrm_address_t */
-       XFRMA_LASTUSED,         /* unsigned long  */
+       XFRMA_LASTUSED,         /* __u64  */
        XFRMA_POLICY_TYPE,      /* struct xfrm_userpolicy_type */
        XFRMA_MIGRATE,
        XFRMA_ALG_AEAD,         /* struct xfrm_algo_aead */
index 1e7ecef17222072903b35f9b6df65e44b0ae467a..6580c91384e4d348b6a8dc5927198784189580c5 100644 (file)
@@ -44,6 +44,7 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
+#include <sys/utsname.h>
 #include <stdint.h>
 #include <linux/ipsec.h>
 #include <linux/netlink.h>
@@ -341,6 +342,11 @@ struct private_kernel_netlink_ipsec_t {
         */
        netlink_event_socket_t *socket_xfrm_events;
 
+       /**
+        * Whether the kernel reports the last use time on SAs
+        */
+       bool sa_lastused;
+
        /**
         * Whether to install routes along policies
         */
@@ -1157,7 +1163,8 @@ CALLBACK(receive_events, void,
 METHOD(kernel_ipsec_t, get_features, kernel_feature_t,
        private_kernel_netlink_ipsec_t *this)
 {
-       return KERNEL_ESP_V3_TFC | KERNEL_POLICY_SPI;
+       return KERNEL_ESP_V3_TFC | KERNEL_POLICY_SPI |
+                       (this->sa_lastused ? KERNEL_SA_USE_TIME : 0);
 }
 
 /**
@@ -2210,10 +2217,33 @@ static void get_replay_state(private_kernel_netlink_ipsec_t *this,
        free(out);
 }
 
+/**
+ * Get the last used time of an SA if provided by the kernel
+ */
+static bool get_lastused(struct nlmsghdr *hdr, uint64_t *lastused)
+{
+       struct rtattr *rta;
+       size_t rtasize;
+
+       rta = XFRM_RTA(hdr, struct xfrm_usersa_info);
+       rtasize = XFRM_PAYLOAD(hdr, struct xfrm_usersa_info);
+       while (RTA_OK(rta, rtasize))
+       {
+               if (rta->rta_type == XFRMA_LASTUSED &&
+                       RTA_PAYLOAD(rta) == sizeof(*lastused))
+               {
+                       *lastused = *(uint64_t*)RTA_DATA(rta);
+                       return TRUE;
+               }
+               rta = RTA_NEXT(rta, rtasize);
+       }
+       return FALSE;
+}
+
 METHOD(kernel_ipsec_t, query_sa, status_t,
        private_kernel_netlink_ipsec_t *this, kernel_ipsec_sa_id_t *id,
        kernel_ipsec_query_sa_t *data, uint64_t *bytes, uint64_t *packets,
-       time_t *time)
+       time_t *use_time)
 {
        netlink_buf_t request;
        struct nlmsghdr *out = NULL, *hdr;
@@ -2291,11 +2321,20 @@ METHOD(kernel_ipsec_t, query_sa, status_t,
                {
                        *packets = sa->curlft.packets;
                }
-               if (time)
-               {       /* curlft contains an "use" time, but that contains a timestamp
-                        * of the first use, not the last. Last use time must be queried
-                        * on the policy on Linux */
-                       *time = 0;
+               if (use_time)
+               {
+                       uint64_t lastused = 0;
+
+                       /* curlft.use_time contains the timestamp of the SA's first use, not
+                        * the last, but we might get the last use time in an attribute */
+                       if (this->sa_lastused && get_lastused(hdr, &lastused))
+                       {
+                               *use_time = time_monotonic(NULL) - (time(NULL) - lastused);
+                       }
+                       else
+                       {
+                               *use_time = 0;
+                       }
                }
                status = SUCCESS;
        }
@@ -4037,6 +4076,30 @@ static void setup_spd_hash_thresh(private_kernel_netlink_ipsec_t *this,
        }
 }
 
+/**
+ * Check for kernel features (currently only via version number)
+ */
+static void check_kernel_features(private_kernel_netlink_ipsec_t *this)
+{
+       struct utsname utsname;
+       int a, b, c;
+
+       if (uname(&utsname) == 0)
+       {
+               switch(sscanf(utsname.release, "%d.%d.%d", &a, &b, &c))
+               {
+                       case 2:
+                       case 3:
+                               /* before 6.2 the kernel only provided the last used time for
+                                * specific outbound IPv6 SAs */
+                               this->sa_lastused = a > 6 || (a == 6 && b >= 2);
+                               break;
+                       default:
+                               break;
+               }
+       }
+}
+
 /*
  * Described in header.
  */
@@ -4084,6 +4147,8 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create()
                                                "%s.plugins.kernel-netlink.port_bypass", FALSE, lib->ns),
        );
 
+       check_kernel_features(this);
+
        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));