Merge branch 'hw-packet-offload'

This adds support for the new "packet" hardware offload feature that's
added to the Linux kernel with 6.2.  In this mode, the device handles
the complete framing of the ESP packet as well as the policy checks,
in addition to the crypto.

For the IKE sockets, port-based bypass policies are automatically
offloaded to devices that support it.

Closes strongswan/strongswan#1462
This commit is contained in:
Tobias Brunner 2023-02-17 13:27:37 +01:00
commit 3cf5653640
18 changed files with 831 additions and 374 deletions

View File

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

View File

@ -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)

View File

@ -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 */

View File

@ -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 */

File diff suppressed because it is too large Load Diff

View File

@ -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",
@ -343,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
@ -1451,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 */
@ -3056,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))
{
@ -3096,7 +3049,7 @@ kernel_netlink_net_t *kernel_netlink_net_create()
{
private_kernel_netlink_net_t *this;
enumerator_t *enumerator;
bool register_for_events = TRUE;
uint32_t groups;
char *exclude;
INIT(this,
@ -3168,11 +3121,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,45 +3142,25 @@ kernel_netlink_net_t *kernel_netlink_net_create()
enumerator->destroy(enumerator);
}
if (register_for_events)
groups = nl_group(RTNLGRP_IPV4_IFADDR) |
nl_group(RTNLGRP_IPV6_IFADDR) |
nl_group(RTNLGRP_LINK);
if (this->process_route)
{
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);
groups |= nl_group(RTNLGRP_IPV4_ROUTE) |
nl_group(RTNLGRP_IPV6_ROUTE);
}
if (this->process_rules)
{
groups |= nl_group(RTNLGRP_IPV4_RULE) |
nl_group(RTNLGRP_IPV6_RULE);
}
this->socket_events = netlink_event_socket_create(NETLINK_ROUTE, groups,
receive_events, this);
if (!this->socket_events)
{
destroy(this);
return NULL;
}
if (init_address_list(this) != SUCCESS)

View File

@ -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
*/

View File

@ -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.
*

View File

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

View File

@ -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");

View File

@ -1040,9 +1040,11 @@ 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 },
{ "crypto", HW_OFFLOAD_CRYPTO },
{ "packet", HW_OFFLOAD_PACKET },
{ "auto", HW_OFFLOAD_AUTO },
};
int d;

View File

@ -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,

View File

@ -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,

View File

@ -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",
);

View File

@ -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,
};
/**

View File

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

View File

@ -24,7 +24,6 @@ AM_CPPFLAGS = \
-DIPSEC_SCRIPT=\"${ipsec_script}\" \
-DDEV_RANDOM=\"${random_device}\" \
-DDEV_URANDOM=\"${urandom_device}\" \
-DPLUGINS=\""${starter_plugins}\"" \
-DDEBUG
AM_CFLAGS = \

View File

@ -1071,10 +1071,12 @@ connections.<conn>.children.<child>.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.<conn>.children.<child>.copy_df = yes
Whether to copy the DF bit to the outer IPv4 header in tunnel mode.