From b05628dd2ddc9026d524f7a4ea4670ab9ecdfd6f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 30 Apr 2024 11:09:41 +0200 Subject: [PATCH 1/5] include: Update XFRM header for SA direction attribute --- src/include/linux/xfrm.h | 47 ++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/include/linux/xfrm.h b/src/include/linux/xfrm.h index 80630dfd4..0c2191bf9 100644 --- a/src/include/linux/xfrm.h +++ b/src/include/linux/xfrm.h @@ -30,7 +30,7 @@ struct xfrm_sec_ctx { __u8 ctx_alg; __u16 ctx_len; __u32 ctx_sid; - char ctx_str[0]; + char ctx_str[]; }; /* Security Context Domains of Interpretation */ @@ -93,27 +93,27 @@ struct xfrm_replay_state_esn { __u32 oseq_hi; __u32 seq_hi; __u32 replay_window; - __u32 bmp[0]; + __u32 bmp[]; }; struct xfrm_algo { char alg_name[64]; unsigned int alg_key_len; /* in bits */ - char alg_key[0]; + char alg_key[]; }; struct xfrm_algo_auth { char alg_name[64]; unsigned int alg_key_len; /* in bits */ unsigned int alg_trunc_len; /* in bits */ - char alg_key[0]; + char alg_key[]; }; struct xfrm_algo_aead { char alg_name[64]; unsigned int alg_key_len; /* in bits */ unsigned int alg_icv_len; /* in bits */ - char alg_key[0]; + char alg_key[]; }; struct xfrm_stats { @@ -137,6 +137,11 @@ enum { XFRM_POLICY_MAX = 3 }; +enum xfrm_sa_dir { + XFRM_SA_DIR_IN = 1, + XFRM_SA_DIR_OUT = 2 +}; + enum { XFRM_SHARE_ANY, /* No limitations */ XFRM_SHARE_SESSION, /* For this session only */ @@ -212,6 +217,11 @@ enum { XFRM_MSG_MAPPING, #define XFRM_MSG_MAPPING XFRM_MSG_MAPPING + + XFRM_MSG_SETDEFAULT, +#define XFRM_MSG_SETDEFAULT XFRM_MSG_SETDEFAULT + XFRM_MSG_GETDEFAULT, +#define XFRM_MSG_GETDEFAULT XFRM_MSG_GETDEFAULT __XFRM_MSG_MAX }; #define XFRM_MSG_MAX (__XFRM_MSG_MAX - 1) @@ -288,7 +298,7 @@ enum xfrm_attr_type_t { XFRMA_ETIMER_THRESH, XFRMA_SRCADDR, /* xfrm_address_t */ XFRMA_COADDR, /* xfrm_address_t */ - XFRMA_LASTUSED, /* __u64 */ + XFRMA_LASTUSED, /* __u64 */ XFRMA_POLICY_TYPE, /* struct xfrm_userpolicy_type */ XFRMA_MIGRATE, XFRMA_ALG_AEAD, /* struct xfrm_algo_aead */ @@ -301,10 +311,12 @@ enum xfrm_attr_type_t { XFRMA_PROTO, /* __u8 */ XFRMA_ADDRESS_FILTER, /* struct xfrm_address_filter */ XFRMA_PAD, - XFRMA_OFFLOAD_DEV, /* struct xfrm_state_offload */ + XFRMA_OFFLOAD_DEV, /* struct xfrm_user_offload */ XFRMA_SET_MARK, /* __u32 */ XFRMA_SET_MARK_MASK, /* __u32 */ XFRMA_IF_ID, /* __u32 */ + XFRMA_MTIMER_THRESH, /* __u32 in seconds for input SA */ + XFRMA_SA_DIR, /* __u8 */ __XFRMA_MAX #define XFRMA_OUTPUT_MARK XFRMA_SET_MARK /* Compatibility */ @@ -384,6 +396,7 @@ struct xfrm_usersa_info { }; #define XFRM_SA_XFLAG_DONT_ENCAP_DSCP 1 +#define XFRM_SA_XFLAG_OSEQ_MAY_WRAP 2 struct xfrm_usersa_id { xfrm_address_t daddr; @@ -501,10 +514,30 @@ struct xfrm_user_offload { int ifindex; __u8 flags; }; +/* This flag was exposed without any kernel code that supports it. + * Unfortunately, strongswan has the code that sets this flag, + * which makes it impossible to reuse this bit. + * + * So leave it here to make sure that it won't be reused by mistake. + */ #define XFRM_OFFLOAD_IPV6 1 #define XFRM_OFFLOAD_INBOUND 2 +/* Two bits above are relevant for state path only, while + * offload is used for both policy and state flows. + * + * In policy offload mode, they are free and can be safely reused. + */ #define XFRM_OFFLOAD_PACKET 4 +struct xfrm_userpolicy_default { +#define XFRM_USERPOLICY_UNSPEC 0 +#define XFRM_USERPOLICY_BLOCK 1 +#define XFRM_USERPOLICY_ACCEPT 2 + __u8 in; + __u8 fwd; + __u8 out; +}; + #ifndef __KERNEL__ /* backwards compatibility for userspace */ #define XFRMGRP_ACQUIRE 1 From dc8fa1b3e820317593161ab6a64ebacb55f386c7 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 May 2024 10:07:17 +0200 Subject: [PATCH 2/5] kernel-netlink: Add missing names for XFRM message types and attributes --- .../plugins/kernel_netlink/kernel_netlink_ipsec.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index db0b2ac37..3fec329b2 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -139,7 +139,7 @@ struct kernel_algorithm_t { const char *name; }; -ENUM(xfrm_msg_names, XFRM_MSG_NEWSA, XFRM_MSG_MAPPING, +ENUM(xfrm_msg_names, XFRM_MSG_NEWSA, __XFRM_MSG_MAX, "XFRM_MSG_NEWSA", "XFRM_MSG_DELSA", "XFRM_MSG_GETSA", @@ -162,10 +162,13 @@ ENUM(xfrm_msg_names, XFRM_MSG_NEWSA, XFRM_MSG_MAPPING, "XFRM_MSG_GETSADINFO", "XFRM_MSG_NEWSPDINFO", "XFRM_MSG_GETSPDINFO", - "XFRM_MSG_MAPPING" + "XFRM_MSG_MAPPING", + "XFRM_MSG_SETDEFAULT", + "XFRM_MSG_GETDEFAULT", + "XFRM_MSG_MAX", ); -ENUM(xfrm_attr_type_names, XFRMA_UNSPEC, XFRMA_OFFLOAD_DEV, +ENUM(xfrm_attr_type_names, XFRMA_UNSPEC, __XFRMA_MAX, "XFRMA_UNSPEC", "XFRMA_ALG_AUTH", "XFRMA_ALG_CRYPT", @@ -195,6 +198,12 @@ ENUM(xfrm_attr_type_names, XFRMA_UNSPEC, XFRMA_OFFLOAD_DEV, "XFRMA_ADDRESS_FILTER", "XFRMA_PAD", "XFRMA_OFFLOAD_DEV", + "XFRMA_SET_MARK", + "XFRMA_SET_MARK_MASK", + "XFRMA_IF_ID", + "XFRMA_MTIMER_THRESH", + "XFRMA_SA_DIR", + "XFRMA_MAX", ); /** From 2601fabbb4e137efd0804f4941887c920680e981 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 30 Apr 2024 15:11:12 +0200 Subject: [PATCH 3/5] kernel-netlink: Only disable DF-flag copying on outbound SAs This will cause errors on inbound SAs if the SA direction attribute is used. --- .../plugins/kernel_netlink/kernel_netlink_ipsec.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 3fec329b2..493a22910 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -1724,11 +1724,6 @@ METHOD(kernel_ipsec_t, add_sa, status_t, 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; @@ -1748,6 +1743,10 @@ METHOD(kernel_ipsec_t, add_sa, status_t, } else { + if (!data->copy_df) + { + sa->flags |= XFRM_STATE_NOPMTUDISC; + } switch (data->copy_dscp) { case DSCP_COPY_IN_ONLY: From 661f6bd0ad9eee61968c30652b4c707c1779b3f5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 30 Apr 2024 11:39:00 +0200 Subject: [PATCH 4/5] kernel-netlink: Add SA direction attribute --- .../kernel_netlink/kernel_netlink_ipsec.c | 244 ++++++++++-------- 1 file changed, 136 insertions(+), 108 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 493a22910..88734b0da 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -1187,114 +1187,6 @@ METHOD(kernel_ipsec_t, get_features, kernel_feature_t, (this->sa_lastused ? KERNEL_SA_USE_TIME : 0); } -/** - * Get an SPI for a specific protocol from the kernel. - */ -static status_t get_spi_internal(private_kernel_netlink_ipsec_t *this, - host_t *src, host_t *dst, uint8_t proto, uint32_t min, uint32_t max, - uint32_t *spi) -{ - netlink_buf_t request; - struct nlmsghdr *hdr, *out; - struct xfrm_userspi_info *userspi; - uint32_t received_spi = 0; - size_t len; - - memset(&request, 0, sizeof(request)); - - hdr = &request.hdr; - hdr->nlmsg_flags = NLM_F_REQUEST; - hdr->nlmsg_type = XFRM_MSG_ALLOCSPI; - hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct xfrm_userspi_info)); - - userspi = NLMSG_DATA(hdr); - host2xfrm(src, &userspi->info.saddr); - host2xfrm(dst, &userspi->info.id.daddr); - userspi->info.id.proto = proto; - userspi->info.mode = XFRM_MODE_TUNNEL; - userspi->info.family = src->get_family(src); - userspi->min = min; - userspi->max = max; - - if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS) - { - hdr = out; - while (NLMSG_OK(hdr, len)) - { - switch (hdr->nlmsg_type) - { - case XFRM_MSG_NEWSA: - { - struct xfrm_usersa_info* usersa = NLMSG_DATA(hdr); - received_spi = usersa->id.spi; - break; - } - case NLMSG_ERROR: - { - netlink_log_error(hdr, "allocating SPI failed"); - break; - } - default: - hdr = NLMSG_NEXT(hdr, len); - continue; - case NLMSG_DONE: - break; - } - break; - } - free(out); - } - - if (received_spi == 0) - { - return FAILED; - } - - *spi = received_spi; - return SUCCESS; -} - -METHOD(kernel_ipsec_t, get_spi, status_t, - private_kernel_netlink_ipsec_t *this, host_t *src, host_t *dst, - uint8_t protocol, uint32_t *spi) -{ - uint32_t spi_min, spi_max; - - spi_min = lib->settings->get_int(lib->settings, "%s.spi_min", - KERNEL_SPI_MIN, lib->ns); - spi_max = lib->settings->get_int(lib->settings, "%s.spi_max", - KERNEL_SPI_MAX, lib->ns); - - if (get_spi_internal(this, src, dst, protocol, min(spi_min, spi_max), - max(spi_min, spi_max), spi) != SUCCESS) - { - DBG1(DBG_KNL, "unable to get SPI"); - return FAILED; - } - - DBG2(DBG_KNL, "got SPI %.8x", ntohl(*spi)); - return SUCCESS; -} - -METHOD(kernel_ipsec_t, get_cpi, status_t, - private_kernel_netlink_ipsec_t *this, host_t *src, host_t *dst, - uint16_t *cpi) -{ - uint32_t received_spi = 0; - - if (get_spi_internal(this, src, dst, IPPROTO_COMP, - 0x100, 0xEFFF, &received_spi) != SUCCESS) - { - DBG1(DBG_KNL, "unable to get CPI"); - return FAILED; - } - - *cpi = htons((uint16_t)ntohl(received_spi)); - - DBG2(DBG_KNL, "got CPI %.4x", ntohs(*cpi)); - return SUCCESS; -} - /** * Format the mark for debug messages */ @@ -1384,6 +1276,136 @@ static bool add_uint32(struct nlmsghdr *hdr, int buflen, return TRUE; } +/** + * Add a uint8 attribute to message + */ +static bool add_uint8(struct nlmsghdr *hdr, int buflen, + enum xfrm_attr_type_t type, uint8_t value) +{ + uint8_t *xvalue; + + xvalue = netlink_reserve(hdr, buflen, type, sizeof(*xvalue)); + if (!xvalue) + { + return FALSE; + } + *xvalue = value; + return TRUE; +} + +/** + * Get an SPI for a specific protocol from the kernel. + */ +static status_t get_spi_internal(private_kernel_netlink_ipsec_t *this, + host_t *src, host_t *dst, uint8_t proto, uint32_t min, uint32_t max, + uint32_t *spi) +{ + netlink_buf_t request; + struct nlmsghdr *hdr, *out; + struct xfrm_userspi_info *userspi; + uint32_t received_spi = 0; + size_t len; + + memset(&request, 0, sizeof(request)); + + hdr = &request.hdr; + hdr->nlmsg_flags = NLM_F_REQUEST; + hdr->nlmsg_type = XFRM_MSG_ALLOCSPI; + hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct xfrm_userspi_info)); + + userspi = NLMSG_DATA(hdr); + host2xfrm(src, &userspi->info.saddr); + host2xfrm(dst, &userspi->info.id.daddr); + userspi->info.id.proto = proto; + userspi->info.mode = XFRM_MODE_TUNNEL; + userspi->info.family = src->get_family(src); + userspi->min = min; + userspi->max = max; + + if (!add_uint8(hdr, sizeof(request), XFRMA_SA_DIR, XFRM_SA_DIR_IN)) + { + return FAILED; + } + + if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS) + { + hdr = out; + while (NLMSG_OK(hdr, len)) + { + switch (hdr->nlmsg_type) + { + case XFRM_MSG_NEWSA: + { + struct xfrm_usersa_info* usersa = NLMSG_DATA(hdr); + received_spi = usersa->id.spi; + break; + } + case NLMSG_ERROR: + { + netlink_log_error(hdr, "allocating SPI failed"); + break; + } + default: + hdr = NLMSG_NEXT(hdr, len); + continue; + case NLMSG_DONE: + break; + } + break; + } + free(out); + } + + if (received_spi == 0) + { + return FAILED; + } + + *spi = received_spi; + return SUCCESS; +} + +METHOD(kernel_ipsec_t, get_spi, status_t, + private_kernel_netlink_ipsec_t *this, host_t *src, host_t *dst, + uint8_t protocol, uint32_t *spi) +{ + uint32_t spi_min, spi_max; + + spi_min = lib->settings->get_int(lib->settings, "%s.spi_min", + KERNEL_SPI_MIN, lib->ns); + spi_max = lib->settings->get_int(lib->settings, "%s.spi_max", + KERNEL_SPI_MAX, lib->ns); + + if (get_spi_internal(this, src, dst, protocol, min(spi_min, spi_max), + max(spi_min, spi_max), spi) != SUCCESS) + { + DBG1(DBG_KNL, "unable to get SPI"); + return FAILED; + } + + DBG2(DBG_KNL, "got SPI %.8x", ntohl(*spi)); + return SUCCESS; +} + +METHOD(kernel_ipsec_t, get_cpi, status_t, + private_kernel_netlink_ipsec_t *this, host_t *src, host_t *dst, + uint16_t *cpi) +{ + uint32_t received_spi = 0; + + if (get_spi_internal(this, src, dst, IPPROTO_COMP, + 0x100, 0xEFFF, &received_spi) != SUCCESS) + { + DBG1(DBG_KNL, "unable to get CPI"); + return FAILED; + } + + *cpi = htons((uint16_t)ntohl(received_spi)); + + DBG2(DBG_KNL, "got CPI %.4x", ntohs(*cpi)); + return SUCCESS; +} + /* 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 @@ -2046,6 +2068,12 @@ METHOD(kernel_ipsec_t, add_sa, status_t, } } + if (!add_uint8(hdr, sizeof(request), XFRMA_SA_DIR, + data->inbound ? XFRM_SA_DIR_IN : XFRM_SA_DIR_OUT)) + { + goto failed; + } + if (id->proto != IPPROTO_COMP) { /* generally, we don't need a replay window for outbound SAs, however, From 22eded1da4c501597c15642e1152930dbd7f61d6 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 30 Apr 2024 15:12:22 +0200 Subject: [PATCH 5/5] kernel-netlink: Set replay window 0 if kernel supports SA direction attribute The kernel now allows a 0 replay window with ESN for SAs that are explicitly tagged as outbound SAs. But not just that, it actually rejects outbound SAs with replay windows > 0. So we add a version check to control the replay window size. Note that adding the attribute unconditionally would be fine even for older kernels, but if somebody backports the direction patches, the installation of outbound SAs might fail if the replay window is not adjusted accordingly. --- .../kernel_netlink/kernel_netlink_ipsec.c | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 88734b0da..c6e50cc05 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -362,6 +362,11 @@ struct private_kernel_netlink_ipsec_t { */ bool sa_lastused; + /** + * Whether the kernel supports setting the SA direction + */ + bool sa_dir; + /** * Whether to install routes along policies */ @@ -1322,7 +1327,8 @@ static status_t get_spi_internal(private_kernel_netlink_ipsec_t *this, userspi->min = min; userspi->max = max; - if (!add_uint8(hdr, sizeof(request), XFRMA_SA_DIR, XFRM_SA_DIR_IN)) + if (this->sa_dir && + !add_uint8(hdr, sizeof(request), XFRMA_SA_DIR, XFRM_SA_DIR_IN)) { return FAILED; } @@ -2068,7 +2074,8 @@ METHOD(kernel_ipsec_t, add_sa, status_t, } } - if (!add_uint8(hdr, sizeof(request), XFRMA_SA_DIR, + if (this->sa_dir && + !add_uint8(hdr, sizeof(request), XFRMA_SA_DIR, data->inbound ? XFRM_SA_DIR_IN : XFRM_SA_DIR_OUT)) { goto failed; @@ -2076,11 +2083,12 @@ METHOD(kernel_ipsec_t, add_sa, status_t, if (id->proto != IPPROTO_COMP) { - /* generally, we don't need a replay window for outbound SAs, however, - * when using ESN the kernel rejects the attribute if it is 0 */ + /* we don't need a replay window for outbound SAs, however, older + * kernels reject the attribute if it is 0 when using ESN, while + * newer kernels reject it if > 0 if the SA's direction is set */ if (!data->inbound && data->replay_window) { - data->replay_window = data->esn ? 1 : 0; + data->replay_window = (data->esn && !this->sa_dir) ? 1 : 0; } if (data->esn || data->replay_window > 32) { @@ -4161,6 +4169,9 @@ static void check_kernel_features(private_kernel_netlink_ipsec_t *this) /* 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); + /* 6.10 added support for SA direction and enforces certain + * flags e.g. 0 replay window for outbound SAs */ + this->sa_dir = a > 6 || (a == 6 && b >= 10); break; default: break;