Skip to content

Commit

Permalink
Merge branch 'kernel-netlink-sa-lastused'
Browse files Browse the repository at this point in the history
Adds support for a change in Linux kernel 6.2 that allows retrieving
the last use time of an SA from the SA itself instead of having to query
the policies.
  • Loading branch information
tobiasbrunner committed Feb 22, 2023
2 parents 1efdb0f + 346a050 commit 5a512ff
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 33 deletions.
Expand Up @@ -46,6 +46,13 @@ static void expire(uint8_t protocol, uint32_t spi, host_t *dst, bool hard)
charon->kernel->expire(charon->kernel, protocol, spi, dst, hard);
}

METHOD(kernel_ipsec_t, get_features, kernel_feature_t,
private_kernel_android_ipsec_t *this)
{
return KERNEL_REQUIRE_UDP_ENCAPSULATION | KERNEL_ESP_V3_TFC |
KERNEL_SA_USE_TIME;
}

METHOD(kernel_ipsec_t, get_spi, status_t,
private_kernel_android_ipsec_t *this, host_t *src, host_t *dst,
uint8_t protocol, uint32_t *spi)
Expand Down Expand Up @@ -166,6 +173,7 @@ kernel_android_ipsec_t *kernel_android_ipsec_create()
INIT(this,
.public = {
.interface = {
.get_features = _get_features,
.get_spi = _get_spi,
.get_cpi = _get_cpi,
.add_sa = _add_sa,
Expand Down
2 changes: 1 addition & 1 deletion src/include/linux/xfrm.h
Expand Up @@ -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 */
Expand Down
13 changes: 10 additions & 3 deletions src/libcharon/kernel/kernel_interface.h
Expand Up @@ -79,6 +79,8 @@ enum kernel_feature_t {
KERNEL_NO_POLICY_UPDATES = (1<<3),
/** IPsec backend supports installing SPIs on policies */
KERNEL_POLICY_SPI = (1<<4),
/** IPsec backend reports use time per SA via query_sa() */
KERNEL_SA_USE_TIME = (1<<5),
};

/**
Expand Down Expand Up @@ -202,7 +204,11 @@ struct kernel_interface_t {
kernel_ipsec_update_sa_t *data);

/**
* Query the number of bytes processed by an SA from the SAD.
* Query the number of bytes and packets processed by an SA from the SAD.
*
* Some implementations may also return the last use time (as indicated by
* get_features()). This is a monotonic timestamp as returned by
* time_monotonic().
*
* @param id data identifying this SA
* @param data data to query the SA
Expand Down Expand Up @@ -247,11 +253,12 @@ struct kernel_interface_t {
* Query the use time of a policy.
*
* The use time of a policy is the time the policy was used
* for the last time.
* for the last time. This is a monotonic timestamp as returned by
* time_monotonic().
*
* @param id data identifying this policy
* @param data data to query the policy
* @param[out] use_time the monotonic timestamp of this SA's last use
* @param[out] use_time the monotonic timestamp of this policy's last use
* @return SUCCESS if operation completed
*/
status_t (*query_policy)(kernel_interface_t *this,
Expand Down
11 changes: 7 additions & 4 deletions src/libcharon/kernel/kernel_ipsec.h
Expand Up @@ -269,7 +269,11 @@ struct kernel_ipsec_t {
kernel_ipsec_update_sa_t *data);

/**
* Query the number of bytes processed by an SA from the SAD.
* Query the number of bytes and packets processed by an SA from the SAD.
*
* Some implementations may also return the last use time (as indicated by
* get_features()). This is a monotonic timestamp as returned by
* time_monotonic().
*
* @param id data identifying this SA
* @param data data to query the SA
Expand Down Expand Up @@ -314,12 +318,11 @@ struct kernel_ipsec_t {
* Query the use time of a policy.
*
* The use time of a policy is the time the policy was used for the last
* time. It is not the system time, but a monotonic timestamp as returned
* by time_monotonic.
* time. This is a monotonic timestamp as returned by time_monotonic().
*
* @param id data identifying this policy
* @param data data to query the policy
* @param[out] use_time the monotonic timestamp of this SA's last use
* @param[out] use_time the monotonic timestamp of this policy's last use
* @return SUCCESS if operation completed
*/
status_t (*query_policy)(kernel_ipsec_t *this,
Expand Down
Expand Up @@ -231,7 +231,8 @@ static void expire(uint8_t protocol, uint32_t spi, host_t *dst, bool hard)
METHOD(kernel_ipsec_t, get_features, kernel_feature_t,
private_kernel_libipsec_ipsec_t *this)
{
return KERNEL_REQUIRE_UDP_ENCAPSULATION | KERNEL_ESP_V3_TFC;
return KERNEL_REQUIRE_UDP_ENCAPSULATION | KERNEL_ESP_V3_TFC |
KERNEL_SA_USE_TIME;
}

METHOD(kernel_ipsec_t, get_spi, status_t,
Expand Down
79 changes: 72 additions & 7 deletions src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c
Expand Up @@ -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>
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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));
Expand Down
4 changes: 2 additions & 2 deletions src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c
Expand Up @@ -359,7 +359,7 @@ static status_t send_once(private_netlink_socket_t *this, struct nlmsghdr *in,

if (this->names)
{
DBG3(DBG_KNL, "sending %N %u: %b", this->names, in->nlmsg_type,
DBG4(DBG_KNL, "sending %N %u: %b", this->names, in->nlmsg_type,
(u_int)seq, in, in->nlmsg_len);
}

Expand Down Expand Up @@ -426,7 +426,7 @@ static status_t send_once(private_netlink_socket_t *this, struct nlmsghdr *in,
{
if (this->names)
{
DBG3(DBG_KNL, "received %N %u: %b", this->names, hdr->nlmsg_type,
DBG4(DBG_KNL, "received %N %u: %b", this->names, hdr->nlmsg_type,
hdr->nlmsg_seq, hdr, hdr->nlmsg_len);
}
memcpy(ptr, hdr, hdr->nlmsg_len);
Expand Down
17 changes: 14 additions & 3 deletions src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c
Expand Up @@ -1659,6 +1659,16 @@ static status_t get_spi_internal(private_kernel_pfkey_ipsec_t *this,
return SUCCESS;
}

METHOD(kernel_ipsec_t, get_features, kernel_feature_t,
private_kernel_pfkey_ipsec_t *this)
{
#ifdef __APPLE__
return KERNEL_SA_USE_TIME;
#else
return 0;
#endif
}

METHOD(kernel_ipsec_t, get_spi, status_t,
private_kernel_pfkey_ipsec_t *this, host_t *src, host_t *dst,
uint8_t protocol, uint32_t *spi)
Expand Down Expand Up @@ -2198,9 +2208,9 @@ METHOD(kernel_ipsec_t, query_sa, status_t,
/* OS X uses the "last" time of use in usetime */
*time = response.lft_current->sadb_lifetime_usetime;
#else /* !__APPLE__ */
/* on Linux, sadb_lifetime_usetime is set to the "first" time of use,
* which is actually correct according to PF_KEY. We have to query
* policies for the last usetime. */
/* on Linux and FreeBSD, sadb_lifetime_usetime is set to the "first"
* time of use, which is actually correct according to PF_KEY. We have
* to query policies for the last usetime. */
*time = 0;
#endif /* !__APPLE__ */
}
Expand Down Expand Up @@ -3308,6 +3318,7 @@ kernel_pfkey_ipsec_t *kernel_pfkey_ipsec_create()
INIT(this,
.public = {
.interface = {
.get_features = _get_features,
.get_spi = _get_spi,
.get_cpi = _get_cpi,
.add_sa = _add_sa,
Expand Down
24 changes: 12 additions & 12 deletions src/libcharon/sa/child_sa.c
Expand Up @@ -759,19 +759,19 @@ METHOD(child_sa_t, get_usestats, void,
private_child_sa_t *this, bool inbound,
time_t *time, uint64_t *bytes, uint64_t *packets)
{
if ((!bytes && !packets) || update_usebytes(this, inbound) != FAILED)
status_t status = NOT_SUPPORTED;
bool sa_use_time;

sa_use_time = charon->kernel->get_features(charon->kernel) & KERNEL_SA_USE_TIME;

if (bytes || packets || sa_use_time)
{
/* there was traffic since last update or the kernel interface
* does not support querying the number of usebytes.
*/
if (time)
{
if (!update_usetime(this, inbound) && !bytes && !packets)
{
/* if policy query did not yield a usetime, query SAs instead */
update_usebytes(this, inbound);
}
}
status = update_usebytes(this, inbound);
}
if (time && !sa_use_time && status != FAILED)
{ /* query policies only if last use time is not available from SAs and
* there was either traffic or querying the SA wasn't supported */
update_usetime(this, inbound);
}
if (time)
{
Expand Down

0 comments on commit 5a512ff

Please sign in to comment.