From: Tobias Brunner Date: Fri, 27 Jan 2023 13:08:41 +0000 (+0100) Subject: kernel-netlink: Offload bypass policies for IKE ports on interfaces X-Git-Tag: android-2.4.1~4^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=15c63601457ed4523f56f45a7d1c3881cc09f85c;p=thirdparty%2Fstrongswan.git 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. --- diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 0a22692f1f..1e7ecef172 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; }