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.
This commit is contained in:
Tobias Brunner 2023-01-27 14:08:41 +01:00
parent 77a5c9514c
commit 15c6360145

View File

@ -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;
}