From af1eeda08b97b1732785a19dfa50fecc9a6ce165 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 19 Dec 2022 11:44:48 +0100 Subject: [PATCH 01/13] kernel-ipsec: Add HW offload mode to policies --- src/libcharon/kernel/kernel_ipsec.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libcharon/kernel/kernel_ipsec.h b/src/libcharon/kernel/kernel_ipsec.h index ae8b82a40..3e1c80e70 100644 --- a/src/libcharon/kernel/kernel_ipsec.h +++ b/src/libcharon/kernel/kernel_ipsec.h @@ -95,7 +95,7 @@ struct kernel_ipsec_add_sa_t { uint16_t cpi; /** TRUE to enable UDP encapsulation for NAT traversal */ bool encap; - /** no (disabled), yes (enabled), auto (enabled if supported) */ + /** HW offload mode */ hw_offload_t hw_offload; /** Mark the SA should apply to packets after processing */ mark_t mark; @@ -180,6 +180,8 @@ struct kernel_ipsec_manage_policy_t { policy_priority_t prio; /** Manually-set priority (automatic if set to 0) */ uint32_t manual_prio; + /** HW offload mode */ + hw_offload_t hw_offload; /** Source address of the SA(s) tied to this policy */ host_t *src; /** Destination address of the SA(s) tied to this policy */ From 46cfebe4ab0b0969d74a209b1edd58929db0c7ea Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 19 Dec 2022 12:50:54 +0100 Subject: [PATCH 02/13] child-sa: Pass HW offload mode for policies --- src/libcharon/sa/child_sa.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index a29531ccb..8f101e1ff 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -1094,6 +1094,7 @@ static status_t install_policies_inbound(private_child_sa_t *this, .type = type, .prio = priority, .manual_prio = manual_prio, + .hw_offload = this->config->get_hw_offload(this->config), .src = other_addr, .dst = my_addr, .sa = my_sa, @@ -1131,6 +1132,7 @@ static status_t install_policies_outbound(private_child_sa_t *this, .type = type, .prio = priority, .manual_prio = manual_prio, + .hw_offload = this->config->get_hw_offload(this->config), .src = my_addr, .dst = other_addr, .sa = other_sa, @@ -1206,6 +1208,7 @@ static void del_policies_inbound(private_child_sa_t *this, .type = type, .prio = priority, .manual_prio = manual_prio, + .hw_offload = this->config->get_hw_offload(this->config), .src = other_addr, .dst = my_addr, .sa = my_sa, @@ -1242,6 +1245,7 @@ static void del_policies_outbound(private_child_sa_t *this, .type = type, .prio = priority, .manual_prio = manual_prio, + .hw_offload = this->config->get_hw_offload(this->config), .src = my_addr, .dst = other_addr, .sa = other_sa, From 55719d7de51c71796ad703ecfbd97d022cc028cc Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 15 Dec 2022 16:41:43 +0100 Subject: [PATCH 03/13] kernel-netlink: Add support for full packet and policy HW offloading --- src/include/linux/xfrm.h | 1 + .../kernel_netlink/kernel_netlink_ipsec.c | 158 ++++++++++++++---- src/libcharon/plugins/vici/vici_config.c | 6 +- src/libstrongswan/ipsec/ipsec_types.c | 3 +- src/libstrongswan/ipsec/ipsec_types.h | 5 +- 5 files changed, 136 insertions(+), 37 deletions(-) diff --git a/src/include/linux/xfrm.h b/src/include/linux/xfrm.h index 805071799..094772dfb 100644 --- a/src/include/linux/xfrm.h +++ b/src/include/linux/xfrm.h @@ -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 */ diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 38670c41d..2f7ec9fb3 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -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] = ""; diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index a59d799ca..6f589fe20 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -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; diff --git a/src/libstrongswan/ipsec/ipsec_types.c b/src/libstrongswan/ipsec/ipsec_types.c index 0c5716000..bd8a4a52c 100644 --- a/src/libstrongswan/ipsec/ipsec_types.c +++ b/src/libstrongswan/ipsec/ipsec_types.c @@ -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", ); diff --git a/src/libstrongswan/ipsec/ipsec_types.h b/src/libstrongswan/ipsec/ipsec_types.h index e96c9f27a..5320c46ab 100644 --- a/src/libstrongswan/ipsec/ipsec_types.h +++ b/src/libstrongswan/ipsec/ipsec_types.h @@ -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, }; /** From 2b8f26308fa886acbbf4f82c4920bfea553ec3b7 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 15 Dec 2022 16:41:59 +0100 Subject: [PATCH 04/13] vici: Update offloading configuration for full packet HW offloading --- src/libcharon/plugins/vici/vici_config.c | 2 ++ src/swanctl/swanctl.opt | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 6f589fe20..52eee8c84 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -1042,6 +1042,8 @@ CALLBACK(parse_hw_offload, bool, enum_map_t map[] = { { "no", HW_OFFLOAD_NO }, { "yes", HW_OFFLOAD_CRYPTO }, + { "crypto", HW_OFFLOAD_CRYPTO }, + { "packet", HW_OFFLOAD_PACKET }, { "auto", HW_OFFLOAD_AUTO }, }; int d; diff --git a/src/swanctl/swanctl.opt b/src/swanctl/swanctl.opt index 927744145..70a4542aa 100644 --- a/src/swanctl/swanctl.opt +++ b/src/swanctl/swanctl.opt @@ -1071,10 +1071,12 @@ connections..children..hw_offload = no implementation. Enable hardware offload for this CHILD_SA, if supported by the IPsec - implementation. The value _yes_ enforces offloading and the installation - will fail if it's not supported by either kernel or device. The value _auto_ - enables offloading, if it's supported, but the installation does not fail - otherwise. + implementation. The values _crypto_ or _packet_ enforce crypto or full + packet offloading and the installation will fail if the selected mode is not + supported by either kernel or device. On Linux, _packet_ also offloads + policies, including trap policies. The value _auto_ enables full packet + or crypto offloading, if either is supported, but the installation does not + fail otherwise. connections..children..copy_df = yes Whether to copy the DF bit to the outer IPv4 header in tunnel mode. From 763014c028000c32077e16d6314ef26bf2709bb3 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 25 Jan 2023 14:40:39 +0100 Subject: [PATCH 05/13] shunt-manager: Pass HW offload mode when installing policies --- src/libcharon/sa/shunt_manager.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libcharon/sa/shunt_manager.c b/src/libcharon/sa/shunt_manager.c index 1b21f9e3e..c19b1fa39 100644 --- a/src/libcharon/sa/shunt_manager.c +++ b/src/libcharon/sa/shunt_manager.c @@ -95,6 +95,7 @@ static bool install_shunt_policy(child_cfg_t *child) policy_type_t policy_type; policy_priority_t policy_prio; status_t status = SUCCESS; + hw_offload_t hw_offload; uint32_t manual_prio; char *interface; bool fwd_out; @@ -125,6 +126,7 @@ static bool install_shunt_policy(child_cfg_t *child) hosts->destroy(hosts); manual_prio = child->get_manual_prio(child); + hw_offload = child->get_hw_offload(child); interface = child->get_interface(child); fwd_out = child->has_option(child, OPT_FWD_OUT_POLICIES); @@ -157,6 +159,7 @@ static bool install_shunt_policy(child_cfg_t *child) .type = policy_type, .prio = policy_prio, .manual_prio = manual_prio, + .hw_offload = hw_offload, .src = host_any, .dst = host_any, .sa = &sa, From 68ccb1930cf7268aa80cc161bb4b34fe9e181a66 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 26 Jan 2023 13:25:11 +0100 Subject: [PATCH 06/13] kernel-netlink: Fallback to configured interface when HW offloading policies This allows offloading bypass and drop policies to a specific interface by configuring `interface` and `hw_offload=packet` (`auto` works too). --- .../kernel_netlink/kernel_netlink_ipsec.c | 20 ++++++++++++++----- 1 file changed, 15 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 2f7ec9fb3..e2f5d42ad 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -1542,7 +1542,7 @@ static bool netlink_detect_offload(const char *ifname) * Do not fail SA addition if offload is not supported. */ static bool add_hw_offload(struct nlmsghdr *hdr, int buflen, host_t *local, - hw_offload_t hw_offload, + char *interface, hw_offload_t hw_offload, struct xfrm_user_offload **offload) { char *ifname; @@ -1557,9 +1557,14 @@ static bool add_hw_offload(struct nlmsghdr *hdr, int buflen, host_t *local, /* 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)) + if (!local || local->is_anyaddr(local) || + !charon->kernel->get_interface(charon->kernel, local, &ifname)) { - return ret; + if (!interface || !interface[0]) + { + return ret; + } + ifname = strdup(interface); } /* check if interface supports hw_offload */ @@ -1600,7 +1605,7 @@ static bool add_hw_offload_sa(struct nlmsghdr *hdr, int buflen, { host_t *local = data->inbound ? id->dst : id->src; - if (!add_hw_offload(hdr, buflen, local, data->hw_offload, offload)) + if (!add_hw_offload(hdr, buflen, local, NULL, data->hw_offload, offload)) { return FALSE; } @@ -1621,6 +1626,7 @@ static bool add_hw_offload_policy(struct nlmsghdr *hdr, int buflen, { ipsec_sa_t *ipsec = mapping->sa; host_t *local = ipsec->src; + char ifname[IFNAMSIZ] = ""; /* only packet offloading is supported for policies, which we try to use * in automatic mode */ @@ -1639,7 +1645,11 @@ static bool add_hw_offload_policy(struct nlmsghdr *hdr, int buflen, local = ipsec->dst; break; } - return add_hw_offload(hdr, buflen, local, ipsec->hw_offload, offload); + if (policy->sel.ifindex) + { + if_indextoname(policy->sel.ifindex, ifname); + } + return add_hw_offload(hdr, buflen, local, ifname, ipsec->hw_offload, offload); } METHOD(kernel_ipsec_t, add_sa, status_t, From 4e91ff7d8c5902185a851f3831e92459318377dd Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 26 Jan 2023 16:09:32 +0100 Subject: [PATCH 07/13] starter: Remove starter-specific plugin lists It hasn't loaded any plugins since d8fdd1018e16 ("starter: Don't flush SAs in the kernel"), which was released with 5.3.3. --- Android.mk | 5 +---- configure.ac | 10 ++++------ src/starter/Android.mk | 3 +-- src/starter/Makefile.am | 1 - 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Android.mk b/Android.mk index c8815fbff..cdcf2c8b3 100644 --- a/Android.mk +++ b/Android.mk @@ -17,11 +17,8 @@ strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \ pkcs1 pkcs8 pem xcbc hmac kdf kernel-netlink socket-default android-dns \ stroke eap-identity eap-mschapv2 eap-md5 eap-gtc -strongswan_STARTER_PLUGINS := kernel-netlink - # list of all plugins - used to enable them with the function below -strongswan_PLUGINS := $(sort $(strongswan_CHARON_PLUGINS) \ - $(strongswan_STARTER_PLUGINS)) +strongswan_PLUGINS := $(sort $(strongswan_CHARON_PLUGINS)) include $(LOCAL_PATH)/Android.common.mk diff --git a/configure.ac b/configure.ac index 7bca05a83..a08b0b492 100644 --- a/configure.ac +++ b/configure.ac @@ -1505,7 +1505,6 @@ m4_include(m4/macros/add-plugin.m4) # plugin lists for all components charon_plugins= -starter_plugins= pool_plugins= attest_plugins= pki_plugins= @@ -1593,10 +1592,10 @@ ADD_PLUGIN([load-tester], [c charon]) ADD_PLUGIN([kernel-libipsec], [c charon cmd]) ADD_PLUGIN([kernel-wfp], [c charon]) ADD_PLUGIN([kernel-iph], [c charon]) -ADD_PLUGIN([kernel-pfkey], [c charon starter nm cmd]) -ADD_PLUGIN([kernel-pfroute], [c charon starter nm cmd]) -ADD_PLUGIN([kernel-netlink], [c charon starter nm cmd]) -ADD_PLUGIN([selinux], [c charon starter nm cmd]) +ADD_PLUGIN([kernel-pfkey], [c charon nm cmd]) +ADD_PLUGIN([kernel-pfroute], [c charon nm cmd]) +ADD_PLUGIN([kernel-netlink], [c charon nm cmd]) +ADD_PLUGIN([selinux], [c charon nm cmd]) ADD_PLUGIN([resolve], [c charon cmd]) ADD_PLUGIN([save-keys], [c]) ADD_PLUGIN([socket-default], [c charon nm cmd]) @@ -1666,7 +1665,6 @@ ADD_PLUGIN([unity], [c charon]) ADD_PLUGIN([counters], [c charon]) AC_SUBST(charon_plugins) -AC_SUBST(starter_plugins) AC_SUBST(pool_plugins) AC_SUBST(attest_plugins) AC_SUBST(pki_plugins) diff --git a/src/starter/Android.mk b/src/starter/Android.mk index a399f6cd2..6b76d5977 100644 --- a/src/starter/Android.mk +++ b/src/starter/Android.mk @@ -18,8 +18,7 @@ LOCAL_C_INCLUDES += \ $(strongswan_PATH)/src/stroke LOCAL_CFLAGS := $(strongswan_CFLAGS) \ - -DIPSEC_SCRIPT='"ipsec"' \ - -DPLUGINS='"$(strongswan_STARTER_PLUGINS)"' + -DIPSEC_SCRIPT='"ipsec"' LOCAL_MODULE := starter diff --git a/src/starter/Makefile.am b/src/starter/Makefile.am index 298a1fb57..c098b5572 100644 --- a/src/starter/Makefile.am +++ b/src/starter/Makefile.am @@ -24,7 +24,6 @@ AM_CPPFLAGS = \ -DIPSEC_SCRIPT=\"${ipsec_script}\" \ -DDEV_RANDOM=\"${random_device}\" \ -DDEV_URANDOM=\"${urandom_device}\" \ - -DPLUGINS=\""${starter_plugins}\"" \ -DDEBUG AM_CFLAGS = \ From d7ccb443541784a525301a2deae825593c81add1 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 26 Jan 2023 16:17:15 +0100 Subject: [PATCH 08/13] kernel-netlink: Always register for events The starter-specific code path isn't necessary anymore since d8fdd1018e16 ("starter: Don't flush SAs in the kernel"). --- .../kernel_netlink/kernel_netlink_ipsec.c | 54 ++++++------- .../kernel_netlink/kernel_netlink_net.c | 77 ++++++++----------- 2 files changed, 55 insertions(+), 76 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index e2f5d42ad..ddc7b1c2a 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -3764,7 +3764,7 @@ static void setup_spd_hash_thresh(private_kernel_netlink_ipsec_t *this, kernel_netlink_ipsec_t *kernel_netlink_ipsec_create() { private_kernel_netlink_ipsec_t *this; - bool register_for_events = TRUE; + struct sockaddr_nl addr; INIT(this, .public = { @@ -3804,11 +3804,6 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create() FALSE, lib->ns), ); - if (streq(lib->ns, "starter")) - { /* starter has no threads, so we do not register for kernel events */ - register_for_events = FALSE; - } - 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)); @@ -3821,34 +3816,29 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create() setup_spd_hash_thresh(this, "ipv4", XFRMA_SPD_IPV4_HTHRESH, 32); setup_spd_hash_thresh(this, "ipv6", XFRMA_SPD_IPV6_HTHRESH, 128); - if (register_for_events) + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + + /* create and bind XFRM socket for ACQUIRE, EXPIRE, MIGRATE & MAPPING */ + this->socket_xfrm_events = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM); + if (this->socket_xfrm_events <= 0) { - struct sockaddr_nl addr; - - memset(&addr, 0, sizeof(addr)); - addr.nl_family = AF_NETLINK; - - /* create and bind XFRM socket for ACQUIRE, EXPIRE, MIGRATE & MAPPING */ - this->socket_xfrm_events = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM); - if (this->socket_xfrm_events <= 0) - { - DBG1(DBG_KNL, "unable to create XFRM event socket: %s (%d)", - strerror(errno), errno); - destroy(this); - return NULL; - } - addr.nl_groups = XFRMNLGRP(ACQUIRE) | XFRMNLGRP(EXPIRE) | - XFRMNLGRP(MIGRATE) | XFRMNLGRP(MAPPING); - if (bind(this->socket_xfrm_events, (struct sockaddr*)&addr, sizeof(addr))) - { - DBG1(DBG_KNL, "unable to bind XFRM event socket: %s (%d)", - strerror(errno), errno); - destroy(this); - return NULL; - } - lib->watcher->add(lib->watcher, this->socket_xfrm_events, WATCHER_READ, - (watcher_cb_t)receive_events, this); + DBG1(DBG_KNL, "unable to create XFRM event socket: %s (%d)", + strerror(errno), errno); + destroy(this); + return NULL; } + addr.nl_groups = XFRMNLGRP(ACQUIRE) | XFRMNLGRP(EXPIRE) | + XFRMNLGRP(MIGRATE) | XFRMNLGRP(MAPPING); + if (bind(this->socket_xfrm_events, (struct sockaddr*)&addr, sizeof(addr))) + { + DBG1(DBG_KNL, "unable to bind XFRM event socket: %s (%d)", + strerror(errno), errno); + destroy(this); + return NULL; + } + lib->watcher->add(lib->watcher, this->socket_xfrm_events, WATCHER_READ, + (watcher_cb_t)receive_events, this); netlink_find_offload_feature(lib->settings->get_str(lib->settings, "%s.plugins.kernel-netlink.hw_offload_feature_interface", diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c index 7dcb15676..8af3016f6 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c @@ -3096,7 +3096,7 @@ kernel_netlink_net_t *kernel_netlink_net_create() { private_kernel_netlink_net_t *this; enumerator_t *enumerator; - bool register_for_events = TRUE; + struct sockaddr_nl addr; char *exclude; INIT(this, @@ -3168,11 +3168,6 @@ kernel_netlink_net_t *kernel_netlink_net_create() return NULL; } - if (streq(lib->ns, "starter")) - { /* starter has no threads, so we do not register for kernel events */ - register_for_events = FALSE; - } - exclude = lib->settings->get_str(lib->settings, "%s.ignore_routing_tables", NULL, lib->ns); if (exclude) @@ -3194,46 +3189,40 @@ kernel_netlink_net_t *kernel_netlink_net_create() enumerator->destroy(enumerator); } - if (register_for_events) + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + + /* create and bind RT socket for events (address/interface/route changes) */ + this->socket_events = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (this->socket_events < 0) { - struct sockaddr_nl addr; - - memset(&addr, 0, sizeof(addr)); - addr.nl_family = AF_NETLINK; - - /* create and bind RT socket for events (address/interface/route changes) */ - this->socket_events = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (this->socket_events < 0) - { - DBG1(DBG_KNL, "unable to create RT event socket: %s (%d)", - strerror(errno), errno); - destroy(this); - return NULL; - } - addr.nl_groups = nl_group(RTNLGRP_IPV4_IFADDR) | - nl_group(RTNLGRP_IPV6_IFADDR) | - nl_group(RTNLGRP_LINK); - if (this->process_route) - { - addr.nl_groups |= nl_group(RTNLGRP_IPV4_ROUTE) | - nl_group(RTNLGRP_IPV6_ROUTE); - } - if (this->process_rules) - { - addr.nl_groups |= nl_group(RTNLGRP_IPV4_RULE) | - nl_group(RTNLGRP_IPV6_RULE); - } - if (bind(this->socket_events, (struct sockaddr*)&addr, sizeof(addr))) - { - DBG1(DBG_KNL, "unable to bind RT event socket: %s (%d)", - strerror(errno), errno); - destroy(this); - return NULL; - } - - lib->watcher->add(lib->watcher, this->socket_events, WATCHER_READ, - (watcher_cb_t)receive_events, this); + DBG1(DBG_KNL, "unable to create RT event socket: %s (%d)", + strerror(errno), errno); + destroy(this); + return NULL; } + addr.nl_groups = nl_group(RTNLGRP_IPV4_IFADDR) | + nl_group(RTNLGRP_IPV6_IFADDR) | + nl_group(RTNLGRP_LINK); + if (this->process_route) + { + addr.nl_groups |= nl_group(RTNLGRP_IPV4_ROUTE) | + nl_group(RTNLGRP_IPV6_ROUTE); + } + if (this->process_rules) + { + addr.nl_groups |= nl_group(RTNLGRP_IPV4_RULE) | + nl_group(RTNLGRP_IPV6_RULE); + } + if (bind(this->socket_events, (struct sockaddr*)&addr, sizeof(addr))) + { + DBG1(DBG_KNL, "unable to bind RT event socket: %s (%d)", + strerror(errno), errno); + destroy(this); + return NULL; + } + lib->watcher->add(lib->watcher, this->socket_events, WATCHER_READ, + (watcher_cb_t)receive_events, this); if (init_address_list(this) != SUCCESS) { From 30cb3bd4d56ab4c932ee20dbef3fe7ee52d32e49 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 26 Jan 2023 16:19:22 +0100 Subject: [PATCH 09/13] kernel-pfroute: Always listen for events The starter-specific code path isn't necessary anymore since d8fdd1018e16 ("starter: Don't flush SAs in the kernel"). --- .../plugins/kernel_pfroute/kernel_pfroute_net.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c index acc22902c..b933b7c92 100644 --- a/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -2114,21 +2114,9 @@ kernel_pfroute_net_t *kernel_pfroute_net_create() destroy(this); return NULL; } + lib->watcher->add(lib->watcher, this->socket, WATCHER_READ, + (watcher_cb_t)receive_events, this); - if (streq(lib->ns, "starter")) - { - /* starter has no threads, so we do not register for kernel events */ - if (shutdown(this->socket, SHUT_RD) != 0) - { - DBG1(DBG_KNL, "closing read end of PF_ROUTE socket failed: %s", - strerror(errno)); - } - } - else - { - lib->watcher->add(lib->watcher, this->socket, WATCHER_READ, - (watcher_cb_t)receive_events, this); - } if (init_address_list(this) != SUCCESS) { DBG1(DBG_KNL, "unable to get interface list"); From e323539428d7beddf63cb21130fcae9809ef1ef9 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 26 Jan 2023 16:21:07 +0100 Subject: [PATCH 10/13] kernel-pfkey: Always register for events The starter-specific code path isn't necessary anymore since d8fdd1018e16 ("starter: Don't flush SAs in the kernel"). --- .../plugins/kernel_pfkey/kernel_pfkey_ipsec.c | 69 ++++++++----------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c index e73767d58..e7046a704 100644 --- a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c +++ b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c @@ -3303,7 +3303,6 @@ METHOD(kernel_ipsec_t, destroy, void, kernel_pfkey_ipsec_t *kernel_pfkey_ipsec_create() { private_kernel_pfkey_ipsec_t *this; - bool register_for_events = TRUE; int rcv_buffer; INIT(this, @@ -3339,11 +3338,6 @@ kernel_pfkey_ipsec_t *kernel_pfkey_ipsec_create() FALSE, lib->ns), ); - if (streq(lib->ns, "starter")) - { /* starter has no threads, so we do not register for kernel events */ - register_for_events = FALSE; - } - /* create a PF_KEY socket to communicate with the kernel */ this->socket = socket(PF_KEY, SOCK_RAW, PF_KEY_V2); if (this->socket <= 0) @@ -3353,41 +3347,38 @@ kernel_pfkey_ipsec_t *kernel_pfkey_ipsec_create() return NULL; } - if (register_for_events) + /* create a PF_KEY socket for ACQUIRE & EXPIRE */ + this->socket_events = socket(PF_KEY, SOCK_RAW, PF_KEY_V2); + if (this->socket_events <= 0) { - /* create a PF_KEY socket for ACQUIRE & EXPIRE */ - this->socket_events = socket(PF_KEY, SOCK_RAW, PF_KEY_V2); - if (this->socket_events <= 0) - { - DBG1(DBG_KNL, "unable to create PF_KEY event socket"); - destroy(this); - return NULL; - } - - rcv_buffer = lib->settings->get_int(lib->settings, - "%s.plugins.kernel-pfkey.events_buffer_size", 0, lib->ns); - if (rcv_buffer > 0) - { - if (setsockopt(this->socket_events, SOL_SOCKET, SO_RCVBUF, - &rcv_buffer, sizeof(rcv_buffer)) == -1) - { - DBG1(DBG_KNL, "unable to set receive buffer size on PF_KEY " - "event socket: %s", strerror(errno)); - } - } - - /* register the event socket */ - if (register_pfkey_socket(this, SADB_SATYPE_ESP) != SUCCESS || - register_pfkey_socket(this, SADB_SATYPE_AH) != SUCCESS) - { - DBG1(DBG_KNL, "unable to register PF_KEY event socket"); - destroy(this); - return NULL; - } - - lib->watcher->add(lib->watcher, this->socket_events, WATCHER_READ, - (watcher_cb_t)receive_events, this); + DBG1(DBG_KNL, "unable to create PF_KEY event socket"); + destroy(this); + return NULL; } + rcv_buffer = lib->settings->get_int(lib->settings, + "%s.plugins.kernel-pfkey.events_buffer_size", 0, lib->ns); + if (rcv_buffer > 0) + { + if (setsockopt(this->socket_events, SOL_SOCKET, SO_RCVBUF, + &rcv_buffer, sizeof(rcv_buffer)) == -1) + { + DBG1(DBG_KNL, "unable to set receive buffer size on PF_KEY " + "event socket: %s", strerror(errno)); + } + } + + /* register the event socket */ + if (register_pfkey_socket(this, SADB_SATYPE_ESP) != SUCCESS || + register_pfkey_socket(this, SADB_SATYPE_AH) != SUCCESS) + { + DBG1(DBG_KNL, "unable to register PF_KEY event socket"); + destroy(this); + return NULL; + } + + lib->watcher->add(lib->watcher, this->socket_events, WATCHER_READ, + (watcher_cb_t)receive_events, this); + return &this->public; } From cb0bdb847d83f0335c4eef8ca6d12e4ef56e8eee Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 26 Jan 2023 17:43:18 +0100 Subject: [PATCH 11/13] kernel-netlink: Add simple wrapper for Netlink event sockets --- .../kernel_netlink/kernel_netlink_net.c | 3 - .../kernel_netlink/kernel_netlink_shared.c | 120 +++++++++++++++++- .../kernel_netlink/kernel_netlink_shared.h | 50 +++++++- 3 files changed, 168 insertions(+), 5 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c index 8af3016f6..9292b5ec2 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c @@ -79,9 +79,6 @@ #define ROUTING_TABLE_PRIO 0 #endif -/** multicast groups (for groups > 31 setsockopt has to be used) */ -#define nl_group(group) (1 << (group - 1)) - ENUM(rt_msg_names, RTM_NEWLINK, RTM_GETRULE, "RTM_NEWLINK", "RTM_DELLINK", diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c index abb9d16f8..71905e3c9 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2022 Tobias Brunner + * Copyright (C) 2008-2023 Tobias Brunner * Copyright (C) 2014 Martin Willi * * Copyright (C) secunet Security Networks AG @@ -58,6 +58,7 @@ #endif typedef struct private_netlink_socket_t private_netlink_socket_t; +typedef struct private_netlink_event_socket_t private_netlink_event_socket_t; /** * Private variables and functions of netlink_socket_t class. @@ -125,6 +126,37 @@ struct private_netlink_socket_t { bool ignore_retransmit_errors; }; +/** + * Private data of netlink_event_socket_t class + */ +struct private_netlink_event_socket_t { + + /** + * Public interface + */ + netlink_event_socket_t public; + + /** + * Registered callback + */ + netlink_event_cb_t cb; + + /** + * User data to pass to callback + */ + void *user; + + /** + * Netlink socket + */ + int socket; + + /** + * Buffer size for received Netlink messages + */ + u_int buflen; +}; + /** * #definable hook to simulate request message loss */ @@ -700,6 +732,92 @@ netlink_socket_t *netlink_socket_create(int protocol, enum_name_t *names, return &this->public; } +CALLBACK(watch_event, bool, + private_netlink_event_socket_t *this, int fd, watcher_event_t event) +{ + char buf[this->buflen]; + struct nlmsghdr *hdr = (struct nlmsghdr*)buf; + struct sockaddr_nl addr; + socklen_t addr_len = sizeof(addr); + int len; + + len = recvfrom(this->socket, buf, sizeof(buf), MSG_DONTWAIT, + (struct sockaddr*)&addr, &addr_len); + if (len < 0) + { + if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) + { + DBG1(DBG_KNL, "netlink event read error: %s", strerror(errno)); + } + return TRUE; + } + else if (addr.nl_pid != 0) + { /* ignore non-kernel messages */ + return TRUE; + } + + while (NLMSG_OK(hdr, len)) + { + this->cb(this->user, hdr); + hdr = NLMSG_NEXT(hdr, len); + } + return TRUE; +} + +METHOD(netlink_event_socket_t, destroy_event, void, + private_netlink_event_socket_t *this) +{ + if (this->socket != -1) + { + lib->watcher->remove(lib->watcher, this->socket); + close(this->socket); + } + free(this); +} + +/* + * Described in header + */ +netlink_event_socket_t *netlink_event_socket_create(int protocol, uint32_t groups, + netlink_event_cb_t cb, void *user) +{ + private_netlink_event_socket_t *this; + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + .nl_groups = groups, + }; + + INIT(this, + .public = { + .destroy = _destroy_event, + }, + .cb = cb, + .user = user, + .buflen = netlink_get_buflen(), + ); + + this->socket = socket(AF_NETLINK, SOCK_RAW, protocol); + if (this->socket == -1) + { + DBG1(DBG_KNL, "unable to create netlink event socket: %s (%d)", + strerror(errno), errno); + destroy_event(this); + return NULL; + } + + if (bind(this->socket, (struct sockaddr*)&addr, sizeof(addr))) + { + DBG1(DBG_KNL, "unable to bind netlink event socket: %s (%d)", + strerror(errno), errno); + destroy_event(this); + return NULL; + } + + lib->watcher->add(lib->watcher, this->socket, WATCHER_READ, watch_event, this); + + return &this->public; +} + /* * Described in header */ diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h index cec3e5e5b..963812970 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2022 Tobias Brunner + * Copyright (C) 2008-2023 Tobias Brunner * * Copyright (C) secunet Security Networks AG * @@ -40,7 +40,16 @@ typedef union { u_char bytes[KERNEL_NETLINK_BUFSIZE]; } netlink_buf_t __attribute__((aligned(RTA_ALIGNTO))); +/** + * Callback function for netlink events. + * + * @param user user data, as passed to constructor + * @param hdr received netlink message + */ +typedef void (*netlink_event_cb_t)(void *user, struct nlmsghdr *hdr); + typedef struct netlink_socket_t netlink_socket_t; +typedef struct netlink_event_socket_t netlink_event_socket_t; /** * Wrapper around a netlink socket. @@ -80,6 +89,45 @@ struct netlink_socket_t { netlink_socket_t *netlink_socket_create(int protocol, enum_name_t *names, bool parallel); +/** + * Wrapper around a bound netlink event socket. + */ +struct netlink_event_socket_t { + + /** + * Destroy the event socket. + */ + void (*destroy)(netlink_event_socket_t *this); +}; + +/** + * Create a netlink_event_socket_t object. + * + * @param protocol protocol type (e.g. NETLINK_XFRM or NETLINK_ROUTE) + * @param groups event groups to bind (use nl_group()) + * @param cb callback to invoke for each event + * @param user user data passed to callback + */ +netlink_event_socket_t *netlink_event_socket_create(int protocol, uint32_t groups, + netlink_event_cb_t cb, void *user); + +/** + * Helper to create bitmask for Netlink multicast groups. + * + * For groups > 31, setsockopt() with NETLINK_ADD_MEMBERSHIP has to be used, + * which is currently not supported by the event socket. + */ +static inline uint32_t nl_group(uint32_t group) +{ + if (group > 31) + { + DBG1(DBG_KNL, "netlink multicast group %d currently not supported", + group); + return 0; + } + return group ? (1 << (group - 1)) : 0; +} + /** * Creates an rtattr and adds it to the given netlink message. * From 77a5c9514c343b7d2513cd182855257abaf92df0 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 26 Jan 2023 17:50:51 +0100 Subject: [PATCH 12/13] kernel-netlink: Use event socket wrapper for XFRM and networking events --- .../kernel_netlink/kernel_netlink_ipsec.c | 116 ++++----------- .../kernel_netlink/kernel_netlink_net.c | 140 +++++------------- 2 files changed, 68 insertions(+), 188 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index ddc7b1c2a..0a22692f1 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -108,11 +108,6 @@ */ #define XFRM_LIMIT(x) ((x) == 0 ? XFRM_INF : (x)) -/** - * Create ORable bitfield of XFRM NL groups - */ -#define XFRMNLGRP(x) (1<<(XFRMNLGRP_##x-1)) - /** * Returns a pointer to the first rtattr following the nlmsghdr *nlh and the * 'usual' netlink data x like 'struct xfrm_usersa_info' @@ -344,7 +339,7 @@ struct private_kernel_netlink_ipsec_t { /** * Netlink xfrm socket to receive acquire and expire events */ - int socket_xfrm_events; + netlink_event_socket_t *socket_xfrm_events; /** * Whether to install routes along policies @@ -1111,67 +1106,28 @@ static void process_mapping(private_kernel_netlink_ipsec_t *this, } } -/** - * Receives events from kernel - */ -static bool receive_events(private_kernel_netlink_ipsec_t *this, int fd, - watcher_event_t event) +CALLBACK(receive_events, void, + private_kernel_netlink_ipsec_t *this, struct nlmsghdr *hdr) { - char response[netlink_get_buflen()]; - struct nlmsghdr *hdr = (struct nlmsghdr*)response; - struct sockaddr_nl addr; - socklen_t addr_len = sizeof(addr); - int len; - - len = recvfrom(this->socket_xfrm_events, response, sizeof(response), - MSG_DONTWAIT, (struct sockaddr*)&addr, &addr_len); - if (len < 0) + switch (hdr->nlmsg_type) { - switch (errno) - { - case EINTR: - /* interrupted, try again */ - return TRUE; - case EAGAIN: - /* no data ready, select again */ - return TRUE; - default: - DBG1(DBG_KNL, "unable to receive from XFRM event socket: %s " - "(%d)", strerror(errno), errno); - sleep(1); - return TRUE; - } + case XFRM_MSG_ACQUIRE: + process_acquire(this, hdr); + break; + case XFRM_MSG_EXPIRE: + process_expire(this, hdr); + break; + case XFRM_MSG_MIGRATE: + process_migrate(this, hdr); + break; + case XFRM_MSG_MAPPING: + process_mapping(this, hdr); + break; + default: + DBG1(DBG_KNL, "received unknown event from XFRM event " + "socket: %d", hdr->nlmsg_type); + break; } - - if (addr.nl_pid != 0) - { /* not from kernel. not interested, try another one */ - return TRUE; - } - - while (NLMSG_OK(hdr, len)) - { - switch (hdr->nlmsg_type) - { - case XFRM_MSG_ACQUIRE: - process_acquire(this, hdr); - break; - case XFRM_MSG_EXPIRE: - process_expire(this, hdr); - break; - case XFRM_MSG_MIGRATE: - process_migrate(this, hdr); - break; - case XFRM_MSG_MAPPING: - process_mapping(this, hdr); - break; - default: - DBG1(DBG_KNL, "received unknown event from XFRM event " - "socket: %d", hdr->nlmsg_type); - break; - } - hdr = NLMSG_NEXT(hdr, len); - } - return TRUE; } METHOD(kernel_ipsec_t, get_features, kernel_feature_t, @@ -3637,11 +3593,7 @@ METHOD(kernel_ipsec_t, destroy, void, array_destroy_function(this->bypass, (array_callback_t)remove_port_bypass, this); - if (this->socket_xfrm_events > 0) - { - lib->watcher->remove(lib->watcher, this->socket_xfrm_events); - close(this->socket_xfrm_events); - } + DESTROY_IF(this->socket_xfrm_events); DESTROY_IF(this->socket_xfrm); enumerator = this->policies->create_enumerator(this->policies); while (enumerator->enumerate(enumerator, &policy, &policy)) @@ -3764,7 +3716,7 @@ static void setup_spd_hash_thresh(private_kernel_netlink_ipsec_t *this, kernel_netlink_ipsec_t *kernel_netlink_ipsec_create() { private_kernel_netlink_ipsec_t *this; - struct sockaddr_nl addr; + uint32_t groups; INIT(this, .public = { @@ -3816,29 +3768,15 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create() setup_spd_hash_thresh(this, "ipv4", XFRMA_SPD_IPV4_HTHRESH, 32); setup_spd_hash_thresh(this, "ipv6", XFRMA_SPD_IPV6_HTHRESH, 128); - memset(&addr, 0, sizeof(addr)); - addr.nl_family = AF_NETLINK; - - /* create and bind XFRM socket for ACQUIRE, EXPIRE, MIGRATE & MAPPING */ - this->socket_xfrm_events = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM); - if (this->socket_xfrm_events <= 0) + groups = nl_group(XFRMNLGRP_ACQUIRE) | nl_group(XFRMNLGRP_EXPIRE) | + nl_group(XFRMNLGRP_MIGRATE) | nl_group(XFRMNLGRP_MAPPING); + this->socket_xfrm_events = netlink_event_socket_create(NETLINK_XFRM, groups, + receive_events, this); + if (!this->socket_xfrm_events) { - DBG1(DBG_KNL, "unable to create XFRM event socket: %s (%d)", - strerror(errno), errno); destroy(this); return NULL; } - addr.nl_groups = XFRMNLGRP(ACQUIRE) | XFRMNLGRP(EXPIRE) | - XFRMNLGRP(MIGRATE) | XFRMNLGRP(MAPPING); - if (bind(this->socket_xfrm_events, (struct sockaddr*)&addr, sizeof(addr))) - { - DBG1(DBG_KNL, "unable to bind XFRM event socket: %s (%d)", - strerror(errno), errno); - destroy(this); - return NULL; - } - lib->watcher->add(lib->watcher, this->socket_xfrm_events, WATCHER_READ, - (watcher_cb_t)receive_events, this); netlink_find_offload_feature(lib->settings->get_str(lib->settings, "%s.plugins.kernel-netlink.hw_offload_feature_interface", diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c index 9292b5ec2..8040d1917 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c @@ -340,9 +340,9 @@ struct private_kernel_netlink_net_t { netlink_socket_t *socket; /** - * Netlink rt socket to receive address change events + * Netlink rt event socket */ - int socket_events; + netlink_event_socket_t *socket_events; /** * earliest time of the next roam event @@ -1448,76 +1448,36 @@ static void process_rule(private_kernel_netlink_net_t *this, #endif } -/** - * Receives events from kernel - */ -static bool receive_events(private_kernel_netlink_net_t *this, int fd, - watcher_event_t event) +CALLBACK(receive_events, void, + private_kernel_netlink_net_t *this, struct nlmsghdr *hdr) { - char response[netlink_get_buflen()]; - struct nlmsghdr *hdr = (struct nlmsghdr*)response; - struct sockaddr_nl addr; - socklen_t addr_len = sizeof(addr); - int len; - - len = recvfrom(this->socket_events, response, sizeof(response), - MSG_DONTWAIT, (struct sockaddr*)&addr, &addr_len); - if (len < 0) + switch (hdr->nlmsg_type) { - switch (errno) - { - case EINTR: - /* interrupted, try again */ - return TRUE; - case EAGAIN: - /* no data ready, select again */ - return TRUE; - default: - DBG1(DBG_KNL, "unable to receive from RT event socket %s (%d)", - strerror(errno), errno); - sleep(1); - return TRUE; - } + case RTM_NEWADDR: + case RTM_DELADDR: + process_addr(this, hdr, TRUE); + break; + case RTM_NEWLINK: + case RTM_DELLINK: + process_link(this, hdr, TRUE); + break; + case RTM_NEWROUTE: + case RTM_DELROUTE: + if (this->process_route) + { + process_route(this, hdr); + } + break; + case RTM_NEWRULE: + case RTM_DELRULE: + if (this->process_rules) + { + process_rule(this, hdr); + } + break; + default: + break; } - - if (addr.nl_pid != 0) - { /* not from kernel. not interested, try another one */ - return TRUE; - } - - while (NLMSG_OK(hdr, len)) - { - /* looks good so far, dispatch netlink message */ - switch (hdr->nlmsg_type) - { - case RTM_NEWADDR: - case RTM_DELADDR: - process_addr(this, hdr, TRUE); - break; - case RTM_NEWLINK: - case RTM_DELLINK: - process_link(this, hdr, TRUE); - break; - case RTM_NEWROUTE: - case RTM_DELROUTE: - if (this->process_route) - { - process_route(this, hdr); - } - break; - case RTM_NEWRULE: - case RTM_DELRULE: - if (this->process_rules) - { - process_rule(this, hdr); - } - break; - default: - break; - } - hdr = NLMSG_NEXT(hdr, len); - } - return TRUE; } /** enumerator over addresses */ @@ -3053,11 +3013,7 @@ METHOD(kernel_net_t, destroy, void, manage_rule(this, RTM_DELRULE, AF_INET6, this->routing_table, this->routing_table_prio); } - if (this->socket_events > 0) - { - lib->watcher->remove(lib->watcher, this->socket_events); - close(this->socket_events); - } + DESTROY_IF(this->socket_events); enumerator = this->routes->ht.create_enumerator(&this->routes->ht); while (enumerator->enumerate(enumerator, NULL, (void**)&route)) { @@ -3093,7 +3049,7 @@ kernel_netlink_net_t *kernel_netlink_net_create() { private_kernel_netlink_net_t *this; enumerator_t *enumerator; - struct sockaddr_nl addr; + uint32_t groups; char *exclude; INIT(this, @@ -3186,40 +3142,26 @@ kernel_netlink_net_t *kernel_netlink_net_create() enumerator->destroy(enumerator); } - memset(&addr, 0, sizeof(addr)); - addr.nl_family = AF_NETLINK; - - /* create and bind RT socket for events (address/interface/route changes) */ - this->socket_events = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (this->socket_events < 0) - { - DBG1(DBG_KNL, "unable to create RT event socket: %s (%d)", - strerror(errno), errno); - destroy(this); - return NULL; - } - addr.nl_groups = nl_group(RTNLGRP_IPV4_IFADDR) | - nl_group(RTNLGRP_IPV6_IFADDR) | - nl_group(RTNLGRP_LINK); + groups = nl_group(RTNLGRP_IPV4_IFADDR) | + nl_group(RTNLGRP_IPV6_IFADDR) | + nl_group(RTNLGRP_LINK); if (this->process_route) { - addr.nl_groups |= nl_group(RTNLGRP_IPV4_ROUTE) | - nl_group(RTNLGRP_IPV6_ROUTE); + groups |= nl_group(RTNLGRP_IPV4_ROUTE) | + nl_group(RTNLGRP_IPV6_ROUTE); } if (this->process_rules) { - addr.nl_groups |= nl_group(RTNLGRP_IPV4_RULE) | - nl_group(RTNLGRP_IPV6_RULE); + groups |= nl_group(RTNLGRP_IPV4_RULE) | + nl_group(RTNLGRP_IPV6_RULE); } - if (bind(this->socket_events, (struct sockaddr*)&addr, sizeof(addr))) + this->socket_events = netlink_event_socket_create(NETLINK_ROUTE, groups, + receive_events, this); + if (!this->socket_events) { - DBG1(DBG_KNL, "unable to bind RT event socket: %s (%d)", - strerror(errno), errno); destroy(this); return NULL; } - lib->watcher->add(lib->watcher, this->socket_events, WATCHER_READ, - (watcher_cb_t)receive_events, this); if (init_address_list(this) != SUCCESS) { From 15c63601457ed4523f56f45a7d1c3881cc09f85c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 27 Jan 2023 14:08:41 +0100 Subject: [PATCH 13/13] kernel-netlink: Offload bypass policies for IKE ports on interfaces While this uses the same mechanism, it's not necessary to explicitly enable port_bypass, the regular socket policies work fine to bypass any software policies. --- .../kernel_netlink/kernel_netlink_ipsec.c | 425 ++++++++++++++++-- 1 file changed, 383 insertions(+), 42 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 0a22692f1..1e7ecef17 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2022 Tobias Brunner + * Copyright (C) 2006-2023 Tobias Brunner * Copyright (C) 2005-2009 Martin Willi * Copyright (C) 2008-2016 Andreas Steffen * Copyright (C) 2006-2007 Fabian Hartmann, Noah Heusser @@ -358,10 +358,34 @@ struct private_kernel_netlink_ipsec_t { bool policy_update; /** - * Installed port based IKE bypass policies, as bypass_t + * Whether to use port-based policies instead of socket policies for the + * IKE sockets/ports + */ + bool port_bypass; + + /** + * Installed port-based IKE bypass policies, as bypass_t + * + * If they are potentially offloaded, the offload mutex has to be locked + * when modifying it */ array_t *bypass; + /** + * Interfaces that potentially support HW offloading, as offload_iface_t + */ + hashtable_t *offload_interfaces; + + /** + * Mutex to safely access the interfaces and bypasses + */ + mutex_t *offload_mutex; + + /** + * Netlink routing socket to receive link events + */ + netlink_event_socket_t *socket_link_events; + /** * Custom priority calculation function */ @@ -1352,7 +1376,7 @@ static struct { /** * Check if kernel supports HW offload and determine feature flag */ -static void netlink_find_offload_feature(const char *ifname) +static bool netlink_find_offload_feature(const char *ifname) { struct ethtool_sset_info *sset_info; struct ethtool_gstrings *cmd = NULL; @@ -1364,7 +1388,7 @@ static void netlink_find_offload_feature(const char *ifname) query_socket = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM); if (query_socket < 0) { - return; + return FALSE; } /* determine number of device features */ @@ -1416,10 +1440,11 @@ out: free(sset_info); free(cmd); close(query_socket); + return netlink_hw_offload.supported; } /** - * Check if interface supported HW offload + * Check if interface supports HW offload */ static bool netlink_detect_offload(const char *ifname) { @@ -1460,11 +1485,6 @@ static bool netlink_detect_offload(const char *ifname) ret = TRUE; } } - - if (!ret) - { - DBG1(DBG_KNL, "HW offload is not supported by device"); - } free(cmd); close(query_socket); return ret; @@ -1472,8 +1492,9 @@ static bool netlink_detect_offload(const char *ifname) #else -static void netlink_find_offload_feature(const char *ifname) +static bool netlink_find_offload_feature(const char *ifname) { + return FALSE; } static bool netlink_detect_offload(const char *ifname) @@ -1526,6 +1547,7 @@ static bool add_hw_offload(struct nlmsghdr *hdr, int buflen, host_t *local, /* check if interface supports hw_offload */ if (!netlink_detect_offload(ifname)) { + DBG1(DBG_KNL, "HW offload is not supported by device %s", ifname); goto out; } @@ -3424,6 +3446,44 @@ static bool add_socket_bypass(private_kernel_netlink_ipsec_t *this, return TRUE; } +/** + * Keep track of interface and its offload support + */ +typedef struct { + + /** + * Interface index + */ + int ifindex; + + /** + * Name of the interface + */ + char ifname[IFNAMSIZ]; + + /** + * Interface flags + */ + u_int flags; + + /** + * Offload state + */ + enum { + /** Offload support unknown */ + IFACE_OFFLOAD_UNKNOWN, + /** No offload supported */ + IFACE_OFFLOAD_NONE, + /** Interface supports at least crypto offload */ + IFACE_OFFLOAD_DETECTED, + /** Interface supports crypto offload, but no packet and policy offload */ + IFACE_OFFLOAD_CRYPTO, + /** Packet and policy offload supported */ + IFACE_OFFLOAD_PACKET, + } offload; + +} offload_iface_t; + /** * Port based IKE bypass policy */ @@ -3437,13 +3497,16 @@ typedef struct { } bypass_t; /** - * Add or remove a bypass policy from/to kernel + * Add or remove a bypass policy from/to kernel. If an interface is given, + * the policy is tried to be offloaded to that interface. */ static bool manage_bypass(private_kernel_netlink_ipsec_t *this, - int type, policy_dir_t dir, bypass_t *bypass) + int type, policy_dir_t dir, bypass_t *bypass, + char *ifname) { netlink_buf_t request; struct xfrm_selector *sel; + struct xfrm_user_offload *offload = NULL; struct nlmsghdr *hdr; memset(&request, 0, sizeof(request)); @@ -3469,6 +3532,13 @@ static bool manage_bypass(private_kernel_netlink_ipsec_t *this, policy->lft.hard_packet_limit = XFRM_INF; sel = &policy->sel; + + if (ifname && + !add_hw_offload(hdr, sizeof(request), NULL, ifname, + HW_OFFLOAD_PACKET, &offload)) + { + return FALSE; + } } else /* XFRM_MSG_DELPOLICY */ { @@ -3494,14 +3564,137 @@ static bool manage_bypass(private_kernel_netlink_ipsec_t *this, sel->sport = bypass->port; sel->sport_mask = 0xffff; } + if (ifname) + { + sel->ifindex = if_nametoindex(ifname); + } return this->socket_xfrm->send_ack(this->socket_xfrm, hdr) == SUCCESS; } +CALLBACK(remove_port_bypass, void, + bypass_t *bypass, int idx, void *user) +{ + private_kernel_netlink_ipsec_t *this = user; + enumerator_t *enumerator; + offload_iface_t *iface; + + if (this->port_bypass) + { + manage_bypass(this, XFRM_MSG_DELPOLICY, POLICY_OUT, bypass, NULL); + manage_bypass(this, XFRM_MSG_DELPOLICY, POLICY_IN, bypass, NULL); + } + if (this->offload_interfaces) + { + enumerator = this->offload_interfaces->create_enumerator(this->offload_interfaces); + while (enumerator->enumerate(enumerator, NULL, &iface)) + { + if (iface->offload == IFACE_OFFLOAD_PACKET && + iface->flags & IFF_UP) + { + manage_bypass(this, XFRM_MSG_DELPOLICY, POLICY_OUT, bypass, + iface->ifname); + manage_bypass(this, XFRM_MSG_DELPOLICY, POLICY_IN, bypass, + iface->ifname); + } + } + enumerator->destroy(enumerator); + } +} + /** - * Bypass socket using a port-based bypass policy + * Bypass socket using a port-based bypass policy, optionally offloaded to a + * given interface */ static bool add_port_bypass(private_kernel_netlink_ipsec_t *this, - int fd, int family) + bypass_t *bypass, char *ifname) +{ + if (!manage_bypass(this, XFRM_MSG_NEWPOLICY, POLICY_IN, bypass, ifname)) + { + return FALSE; + } + if (!manage_bypass(this, XFRM_MSG_NEWPOLICY, POLICY_OUT, bypass, ifname)) + { + manage_bypass(this, XFRM_MSG_DELPOLICY, POLICY_IN, bypass, ifname); + return FALSE; + } + return TRUE; +} + +/** + * Offload the given port-based bypass policy to the given interface if possible. + * + * offload_mutex is assumed to be locked. + */ +static bool offload_bypass_iface(private_kernel_netlink_ipsec_t *this, + bypass_t *bypass, offload_iface_t *iface) +{ + if ((iface->offload == IFACE_OFFLOAD_DETECTED || + iface->offload == IFACE_OFFLOAD_PACKET)) + { + if (add_port_bypass(this, bypass, iface->ifname)) + { + iface->offload = IFACE_OFFLOAD_PACKET; + return TRUE; + } + else if (iface->offload == IFACE_OFFLOAD_DETECTED) + { + iface->offload = IFACE_OFFLOAD_CRYPTO; + } + } + return FALSE; +} + +/** + * Offload all known port-based bypass policies to the given interface. + * + * offload_mutex is assumed to be locked. + */ +static void offload_bypasses(private_kernel_netlink_ipsec_t *this, + offload_iface_t *iface) +{ + enumerator_t *enumerator; + bypass_t *bypass; + + enumerator = array_create_enumerator(this->bypass); + while (enumerator->enumerate(enumerator, &bypass)) + { + if (!offload_bypass_iface(this, bypass, iface)) + { /* could indicate a failure but generally means that the interface + * does not support offloading */ + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Offload a new port-based bypass policy to all known interfaces. + * + * offload_mutex is assumed to be locked. + */ +static void offload_bypass(private_kernel_netlink_ipsec_t *this, + bypass_t *bypass) +{ + enumerator_t *enumerator; + offload_iface_t *iface; + + enumerator = this->offload_interfaces->create_enumerator(this->offload_interfaces); + while (enumerator->enumerate(enumerator, NULL, &iface)) + { + if (iface->flags & IFF_UP) + { + offload_bypass_iface(this, bypass, iface); + } + } + enumerator->destroy(enumerator); +} + +/** + * Offload a bypass policy on supported hardware if the kernel supports it and + * optionally install a port-based bypass policy in software. + */ +static bool add_and_offload_port_bypass(private_kernel_netlink_ipsec_t *this, + int fd, int family) { union { struct sockaddr sa; @@ -3537,39 +3730,38 @@ static bool add_port_bypass(private_kernel_netlink_ipsec_t *this, return FALSE; } - if (!manage_bypass(this, XFRM_MSG_NEWPOLICY, POLICY_IN, &bypass)) + if (this->port_bypass && + !add_port_bypass(this, &bypass, NULL)) { return FALSE; } - if (!manage_bypass(this, XFRM_MSG_NEWPOLICY, POLICY_OUT, &bypass)) + if (this->offload_interfaces) { - manage_bypass(this, XFRM_MSG_DELPOLICY, POLICY_IN, &bypass); - return FALSE; + this->offload_mutex->lock(this->offload_mutex); + offload_bypass(this, &bypass); + /* store it even if no policy was offloaded because an interface that + * supports offloading might get activated later */ + array_insert_create_value(&this->bypass, sizeof(bypass_t), + ARRAY_TAIL, &bypass); + this->offload_mutex->unlock(this->offload_mutex); + } + else + { + array_insert_create_value(&this->bypass, sizeof(bypass_t), + ARRAY_TAIL, &bypass); } - array_insert(this->bypass, ARRAY_TAIL, &bypass); - return TRUE; } -/** - * Remove installed port based bypass policy - */ -static void remove_port_bypass(bypass_t *bypass, int idx, - private_kernel_netlink_ipsec_t *this) -{ - manage_bypass(this, XFRM_MSG_DELPOLICY, POLICY_OUT, bypass); - manage_bypass(this, XFRM_MSG_DELPOLICY, POLICY_IN, bypass); -} - METHOD(kernel_ipsec_t, bypass_socket, bool, private_kernel_netlink_ipsec_t *this, int fd, int family) { - if (lib->settings->get_bool(lib->settings, - "%s.plugins.kernel-netlink.port_bypass", FALSE, lib->ns)) + if ((this->offload_interfaces || this->port_bypass) && + !add_and_offload_port_bypass(this, fd, family)) { - return add_port_bypass(this, fd, family); + return FALSE; } - return add_socket_bypass(this, fd, family); + return this->port_bypass || add_socket_bypass(this, fd, family); } METHOD(kernel_ipsec_t, enable_udp_decap, bool, @@ -3585,26 +3777,161 @@ METHOD(kernel_ipsec_t, enable_udp_decap, bool, return TRUE; } +CALLBACK(receive_link_events, void, + private_kernel_netlink_ipsec_t *this, struct nlmsghdr *hdr) +{ + struct ifinfomsg *msg = NLMSG_DATA(hdr); + struct rtattr *rta = IFLA_RTA(msg); + size_t rtasize = IFLA_PAYLOAD (hdr); + offload_iface_t *iface = NULL; + char *name = NULL; + + if (hdr->nlmsg_type != RTM_NEWLINK && + hdr->nlmsg_type != RTM_DELLINK) + { + return; + } + + while (RTA_OK(rta, rtasize)) + { + switch (rta->rta_type) + { + case IFLA_IFNAME: + name = RTA_DATA(rta); + break; + } + rta = RTA_NEXT(rta, rtasize); + } + if (!name) + { + return; + } + + this->offload_mutex->lock(this->offload_mutex); + if (hdr->nlmsg_type == RTM_NEWLINK) + { + iface = this->offload_interfaces->get(this->offload_interfaces, + (void*)(uintptr_t)msg->ifi_index); + if (!iface) + { + INIT(iface, + .ifindex = msg->ifi_index + ); + this->offload_interfaces->put(this->offload_interfaces, + (void*)(uintptr_t)msg->ifi_index, + iface); + } + /* update name in case an interface is renamed */ + strncpy(iface->ifname, name, IFNAMSIZ-1); + iface->ifname[IFNAMSIZ-1] = '\0'; + + if (iface->offload == IFACE_OFFLOAD_UNKNOWN) + { + if (netlink_detect_offload(iface->ifname)) + { + iface->offload = IFACE_OFFLOAD_DETECTED; + } + else + { + iface->offload = IFACE_OFFLOAD_NONE; + } + } + + /* if an interface is activated or newly detected, try to offload known + * IKE bypass policies. we don't have to do anything if the interface + * goes down as the kernel automatically removes the state it has for + * offloaded policies */ + if (!(iface->flags & IFF_UP) && (msg->ifi_flags & IFF_UP)) + { + offload_bypasses(this, iface); + } + iface->flags = msg->ifi_flags; + } + else + { + iface = this->offload_interfaces->remove(this->offload_interfaces, + (void*)(uintptr_t)msg->ifi_index); + free(iface); + } + this->offload_mutex->unlock(this->offload_mutex); +} + +/** + * Enumerate all interfaces and check if they support offloading + */ +static bool init_offload_interfaces(private_kernel_netlink_ipsec_t *this) +{ + netlink_buf_t request; + netlink_socket_t *socket; + struct nlmsghdr *out, *current, *in; + struct rtgenmsg *msg; + size_t len; + + socket = netlink_socket_create(NETLINK_ROUTE, NULL, FALSE); + if (!socket) + { + return FALSE; + } + + memset(&request, 0, sizeof(request)); + + in = &request.hdr; + in->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + in->nlmsg_type = RTM_GETLINK; + in->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)); + + msg = NLMSG_DATA(in); + msg->rtgen_family = AF_UNSPEC; + + if (socket->send(socket, in, &out, &len) != SUCCESS) + { + socket->destroy(socket); + return FALSE; + } + + current = out; + while (NLMSG_OK(current, len)) + { + receive_link_events(this, current); + current = NLMSG_NEXT(current, len); + } + free(out); + socket->destroy(socket); + return TRUE; +} + METHOD(kernel_ipsec_t, destroy, void, private_kernel_netlink_ipsec_t *this) { enumerator_t *enumerator; policy_entry_t *policy; + offload_iface_t *iface; - array_destroy_function(this->bypass, - (array_callback_t)remove_port_bypass, this); + DESTROY_IF(this->socket_link_events); DESTROY_IF(this->socket_xfrm_events); + array_destroy_function(this->bypass, remove_port_bypass, this); DESTROY_IF(this->socket_xfrm); enumerator = this->policies->create_enumerator(this->policies); - while (enumerator->enumerate(enumerator, &policy, &policy)) + while (enumerator->enumerate(enumerator, NULL, &policy)) { policy_entry_destroy(this, policy); } enumerator->destroy(enumerator); this->policies->destroy(this->policies); this->sas->destroy(this->sas); + if (this->offload_interfaces) + { + enumerator = this->offload_interfaces->create_enumerator(this->offload_interfaces); + while (enumerator->enumerate(enumerator, NULL, &iface)) + { + free(iface); + } + enumerator->destroy(enumerator); + this->offload_interfaces->destroy(this->offload_interfaces); + } this->condvar->destroy(this->condvar); this->mutex->destroy(this->mutex); + DESTROY_IF(this->offload_mutex); free(this); } @@ -3742,7 +4069,6 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create() (hashtable_equals_t)policy_equals, 32), .sas = hashtable_create((hashtable_hash_t)ipsec_sa_hash, (hashtable_equals_t)ipsec_sa_equals, 32), - .bypass = array_create(sizeof(bypass_t), 0), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .condvar = condvar_create(CONDVAR_TYPE_DEFAULT), .get_priority = dlsym(RTLD_DEFAULT, @@ -3754,6 +4080,8 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create() .proto_port_transport = lib->settings->get_bool(lib->settings, "%s.plugins.kernel-netlink.set_proto_port_transport_sa", FALSE, lib->ns), + .port_bypass = lib->settings->get_bool(lib->settings, + "%s.plugins.kernel-netlink.port_bypass", FALSE, lib->ns), ); this->socket_xfrm = netlink_socket_create(NETLINK_XFRM, xfrm_msg_names, @@ -3778,9 +4106,22 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create() return NULL; } - netlink_find_offload_feature(lib->settings->get_str(lib->settings, + if (netlink_find_offload_feature(lib->settings->get_str(lib->settings, "%s.plugins.kernel-netlink.hw_offload_feature_interface", - "lo", lib->ns)); - + "lo", lib->ns))) + { + this->offload_interfaces = hashtable_create(hashtable_hash_ptr, + hashtable_equals_ptr, 8); + this->offload_mutex = mutex_create(MUTEX_TYPE_DEFAULT); + this->socket_link_events = netlink_event_socket_create(NETLINK_ROUTE, + nl_group(RTNLGRP_LINK), + receive_link_events, this); + if (!this->socket_link_events || + !init_offload_interfaces(this)) + { + destroy(this); + return NULL; + } + } return &this->public; }