/*
- * 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
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
*/
/**
* 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;
query_socket = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM);
if (query_socket < 0)
{
- return;
+ return FALSE;
}
/* determine number of device features */
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)
{
ret = TRUE;
}
}
-
- if (!ret)
- {
- DBG1(DBG_KNL, "HW offload is not supported by device");
- }
free(cmd);
close(query_socket);
return ret;
#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)
/* 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;
}
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
*/
} 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));
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 */
{
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;
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,
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);
}
(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,
.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,
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;
}