Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next,v3] xfrm: allow to enable udp encapsulation without userspace socket
@ 2026-07-17 11:01 Pablo Neira Ayuso
  0 siblings, 0 replies; only message in thread
From: Pablo Neira Ayuso @ 2026-07-17 11:01 UTC (permalink / raw)
  To: devel; +Cc: netdev, tobias, eyal.birger, antony, steffen.klassert

It is currently not possible to enable UDP encapsulation in xfrm without
a userspace process that listens on the specified UDP listener port in
the SA.

People have work around this by creating dummy userspace daemons such as
the one in the smallish perl program (see the script at the bottom of
this link):

http://techblog.newsnow.co.uk/2011/11/simple-udp-esp-encapsulation-nat-t-for.html

This patch adds XFRM_SA_XFLAG_UDP_ENCAP_SOCK to create the UDP socket
from the kernel.

Use a hole in net->xfrm to place the new encap_socket list.

The following example shows how to enable the standalone UDP
encapsulation:

    ip xfrm state add src 192.168.10.10 dst 192.168.10.11 proto esp spi 1 \
            encap espinudp 9999 9999 0.0.0.0 \
            if_id 0x1 reqid 1 replay-window 0 mode tunnel aead 'rfc4106(gcm(aes))' \
            0x1111111111111111111111111111111111111111 96 \
            sel src 10.141.10.0/24 dst 10.141.11.0/24 dir out

and the receiving side uses 'extra-flag udp-encap-sock':

    ip xfrm state add src 192.168.10.11 dst 192.168.10.10 proto esp spi 2 \
            encap espinudp 9999 0 0.0.0.0 extra-flag udp-encap-sock \
            if_id 0x1 reqid 2 replay-window 10 mode tunnel aead 'rfc4106(gcm(aes))' \
            0x2222222222222222222222222222222222222222 96 dir in

This allows for multiple SAs using the same listener udp port.

This is useful for testing scenarios where UDP encapsulation is
required.

Note this patch exports xfrm6_udp_encap_rcv() just like
xfrm4_udp_encap_rcv() otherwise linker complains due to unreachable
symbol.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
v3: - Use x->id.daddr.a{4,6} instead of encap_oa, as suggested by Eyal and Tobias.
    - Fix several SA using same udp encapsulation configuration.

This is a follow up to:
https://lists.linux-ipsec.org/archives/list/devel@lists.linux-ipsec.org/thread/F4IOY2DKFDX3E45UUFQHUZKTHV7AD6G4/

NOTE for netdev maintainers:
Targetting net-next so sashiko kicks in for review, ipsec-devel still has no such service yet.

 include/net/netns/xfrm.h  |   1 +
 include/net/xfrm.h        |  11 ++++
 include/uapi/linux/xfrm.h |   1 +
 net/ipv6/xfrm6_input.c    |   1 +
 net/xfrm/Kconfig          |   2 +
 net/xfrm/xfrm_state.c     |  16 +++++
 net/xfrm/xfrm_user.c      | 122 +++++++++++++++++++++++++++++++++++++-
 7 files changed, 152 insertions(+), 2 deletions(-)

diff --git a/include/net/netns/xfrm.h b/include/net/netns/xfrm.h
index b73983a17e08..5091c07b1e46 100644
--- a/include/net/netns/xfrm.h
+++ b/include/net/netns/xfrm.h
@@ -56,6 +56,7 @@ struct netns_xfrm {
 	unsigned int		policy_count[XFRM_POLICY_MAX * 2];
 	struct work_struct	policy_hash_work;
 	struct xfrm_policy_hthresh policy_hthresh;
+	struct hlist_head	encap_socket;
 	struct list_head	inexact_bins;
 
 
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index a6d69aaa6cd2..99730a2fc8b9 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -25,6 +25,7 @@
 #include <net/ipv6.h>
 #include <net/ip6_fib.h>
 #include <net/flow.h>
+#include <net/udp_tunnel.h>
 #include <net/gro_cells.h>
 
 #include <linux/interrupt.h>
@@ -169,6 +170,13 @@ struct xfrm_dev_offload {
 	u8			flags : 2;
 };
 
+struct xfrm_encap_sock {
+	struct hlist_node	list;
+	struct udp_port_cfg	cfg;
+	struct sock		*sk;
+	refcount_t		refcnt;
+};
+
 struct xfrm_mode {
 	u8 encap;
 	u8 family;
@@ -249,6 +257,7 @@ struct xfrm_state {
 
 	/* Data for encapsulator */
 	struct xfrm_encap_tmpl	*encap;
+	struct xfrm_encap_sock	*encap_sock;
 
 	/* NAT keepalive */
 	u32			nat_keepalive_interval; /* seconds */
@@ -2343,6 +2352,8 @@ static inline bool xfrm6_local_dontfrag(const struct sock *sk)
 }
 #endif
 
+int xfrm4_udp_encap_rcv(struct sock *sk, struct sk_buff *skb);
+
 #if (IS_BUILTIN(CONFIG_XFRM_INTERFACE) && IS_ENABLED(CONFIG_DEBUG_INFO_BTF)) || \
     (IS_MODULE(CONFIG_XFRM_INTERFACE) && IS_ENABLED(CONFIG_DEBUG_INFO_BTF_MODULES))
 
diff --git a/include/uapi/linux/xfrm.h b/include/uapi/linux/xfrm.h
index 051f8066efd1..28aa5c8ca317 100644
--- a/include/uapi/linux/xfrm.h
+++ b/include/uapi/linux/xfrm.h
@@ -413,6 +413,7 @@ struct xfrm_usersa_info {
 
 #define XFRM_SA_XFLAG_DONT_ENCAP_DSCP	1
 #define XFRM_SA_XFLAG_OSEQ_MAY_WRAP	2
+#define XFRM_SA_XFLAG_UDP_ENCAP_SOCK	4
 
 struct xfrm_usersa_id {
 	xfrm_address_t			daddr;
diff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c
index 89d0443b5307..2e3f7b9e5a4f 100644
--- a/net/ipv6/xfrm6_input.c
+++ b/net/ipv6/xfrm6_input.c
@@ -173,6 +173,7 @@ int xfrm6_udp_encap_rcv(struct sock *sk, struct sk_buff *skb)
 
 	return ret;
 }
+EXPORT_SYMBOL(xfrm6_udp_encap_rcv);
 
 struct sk_buff *xfrm6_gro_udp_encap_rcv(struct sock *sk, struct list_head *head,
 					struct sk_buff *skb)
diff --git a/net/xfrm/Kconfig b/net/xfrm/Kconfig
index 4a62817a88f8..fe42cfca9423 100644
--- a/net/xfrm/Kconfig
+++ b/net/xfrm/Kconfig
@@ -7,6 +7,7 @@ config XFRM
 	depends on INET
 	select GRO_CELLS
 	select SKB_EXTENSIONS
+	select NET_UDP_TUNNEL
 
 config XFRM_OFFLOAD
 	bool
@@ -23,6 +24,7 @@ if INET
 config XFRM_USER
 	tristate "Transformation user configuration interface"
 	select XFRM_ALGO
+	select NET_UDP_TUNNEL
 	help
 	  Support for Transformation(XFRM) user configuration interface
 	  like IPsec used by native Linux tools.
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 36a4f6793ede..c849e7ac750a 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -27,6 +27,7 @@
 #include <linux/slab.h>
 #include <linux/interrupt.h>
 #include <linux/kernel.h>
+#include <net/udp_tunnel.h>
 
 #include <crypto/aead.h>
 
@@ -586,6 +587,16 @@ static const struct xfrm_mode_cbs *xfrm_get_mode_cbs(u8 mode)
 	return cbs;
 }
 
+static bool xfrm_socket_put(struct xfrm_encap_sock *encap_sock)
+{
+	if (refcount_dec_and_test(&encap_sock->refcnt)) {
+		udp_tunnel_sock_release(encap_sock->sk);
+		return true;
+	}
+
+	return false;
+}
+
 void xfrm_state_free(struct xfrm_state *x)
 {
 	kmem_cache_free(xfrm_state_cache, x);
@@ -597,6 +608,10 @@ static void xfrm_state_gc_destroy(struct xfrm_state *x)
 {
 	if (x->mode_cbs && x->mode_cbs->destroy_state)
 		x->mode_cbs->destroy_state(x);
+
+	if (x->encap_sock && xfrm_socket_put(x->encap_sock))
+		kfree(x->encap_sock);
+
 	hrtimer_cancel(&x->mtimer);
 	timer_delete_sync(&x->rtimer);
 	kfree_sensitive(x->aead);
@@ -3332,6 +3347,7 @@ int __net_init xfrm_state_init(struct net *net)
 					      SLAB_HWCACHE_ALIGN | SLAB_PANIC);
 
 	INIT_LIST_HEAD(&net->xfrm.state_all);
+	INIT_HLIST_HEAD(&net->xfrm.encap_socket);
 
 	sz = sizeof(struct hlist_head) * 8;
 
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index d6db63304ba6..bc88581e51e8 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -29,6 +29,7 @@
 #include <net/xfrm.h>
 #include <net/netlink.h>
 #include <net/ah.h>
+#include <net/udp_tunnel.h>
 #include <linux/uaccess.h>
 #if IS_ENABLED(CONFIG_IPV6)
 #include <linux/in6.h>
@@ -484,6 +485,17 @@ static int verify_newsa_info(struct xfrm_usersa_info *p,
 			goto out;
 	}
 
+	if (attrs[XFRMA_SA_EXTRA_FLAGS]) {
+		u32 xflags = nla_get_u32(attrs[XFRMA_SA_EXTRA_FLAGS]);
+
+		if (xflags & XFRM_SA_XFLAG_UDP_ENCAP_SOCK &&
+		    (!sa_dir || sa_dir == XFRM_SA_DIR_OUT)) {
+			NL_SET_ERR_MSG(extack, "Flag UDP_ENCAP_SOCK can only be set on input SA");
+			err = -EINVAL;
+			goto out;
+		}
+	}
+
 	if (sa_dir == XFRM_SA_DIR_OUT) {
 		if (p->flags & XFRM_STATE_DECAP_DSCP) {
 			NL_SET_ERR_MSG(extack, "Flag DECAP_DSCP should not be set for output SA");
@@ -556,7 +568,6 @@ static int verify_newsa_info(struct xfrm_usersa_info *p,
 				err = -EINVAL;
 				goto out;
 			}
-
 		}
 
 		if (attrs[XFRMA_IPTFS_DONT_FRAG]) {
@@ -932,9 +943,17 @@ static struct xfrm_state *xfrm_state_construct(struct net *net,
 			goto error;
 	}
 
-	if (attrs[XFRMA_SA_EXTRA_FLAGS])
+	if (attrs[XFRMA_SA_EXTRA_FLAGS]) {
 		x->props.extra_flags = nla_get_u32(attrs[XFRMA_SA_EXTRA_FLAGS]);
 
+		if (x->props.extra_flags & XFRM_SA_XFLAG_UDP_ENCAP_SOCK &&
+		    x->encap && x->encap->encap_type != UDP_ENCAP_ESPINUDP) {
+			NL_SET_ERR_MSG(extack, "XFRM_SA_XFLAG_UDP_ENCAP_SOCK can only be set on UDP_ENCAP_ESPINUDP type");
+			err = -EOPNOTSUPP;
+			goto error;
+		}
+	}
+
 	if ((err = attach_aead(x, attrs[XFRMA_ALG_AEAD], extack)))
 		goto error;
 	if ((err = attach_auth_trunc(&x->aalg, &x->props.aalgo,
@@ -1036,6 +1055,97 @@ static struct xfrm_state *xfrm_state_construct(struct net *net,
 	return NULL;
 }
 
+static struct xfrm_encap_sock *
+xfrm_socket_find_get(struct net *net, const struct udp_port_cfg *udp_conf)
+{
+	struct xfrm_encap_sock *listener;
+
+	hlist_for_each_entry(listener, &net->xfrm.encap_socket, list) {
+		if (!memcmp(&listener->cfg, udp_conf, sizeof(*udp_conf))) {
+			refcount_inc(&listener->refcnt);
+			return listener;
+		}
+	}
+
+	return NULL;
+}
+
+static int xfrm_socket_encap_create(struct net *net, struct xfrm_state *x,
+				    struct udp_port_cfg *udp_conf,
+				    struct udp_tunnel_sock_cfg *tuncfg)
+{
+	struct xfrm_encap_sock *listener;
+	struct socket *sock;
+	int err;
+
+	listener = kzalloc_obj(*listener);
+	if (!listener)
+		return -ENOMEM;
+
+	err = udp_sock_create(net, udp_conf, &sock);
+	if (err) {
+		kfree(listener);
+		return err;
+	}
+	setup_udp_tunnel_sock(net, sock->sk, tuncfg);
+
+	listener->sk = sock->sk;
+	listener->cfg = *udp_conf;
+	refcount_set(&listener->refcnt, 1);
+	hlist_add_head(&listener->list, &net->xfrm.encap_socket);
+
+	x->encap_sock = listener;
+
+	return 0;
+}
+
+static int xfrm_socket_setup(struct net *net, struct xfrm_state *x,
+			     struct netlink_ext_ack *extack)
+{
+	struct udp_tunnel_sock_cfg tuncfg = {};
+	struct xfrm_encap_sock *listener;
+	struct udp_port_cfg udp_conf;
+	int err;
+
+	if (!x->encap)
+		return -EOPNOTSUPP;
+
+	memset(&udp_conf, 0, sizeof(udp_conf));
+	udp_conf.family = x->props.family;
+
+	switch (x->props.family) {
+	case AF_INET:
+		udp_conf.local_ip.s_addr = x->id.daddr.a4;
+		tuncfg.encap_rcv = xfrm4_udp_encap_rcv;
+		break;
+#if IS_ENABLED(CONFIG_IPV6)
+	case AF_INET6:
+		udp_conf.local_ip6 = x->id.daddr.in6;
+		tuncfg.encap_rcv = xfrm6_udp_encap_rcv;
+		break;
+#endif
+	default:
+		return -EOPNOTSUPP;
+	}
+	udp_conf.local_udp_port = x->encap->encap_sport;
+
+	listener = xfrm_socket_find_get(net, &udp_conf);
+	if (listener) {
+		x->encap_sock = listener;
+		return 0;
+	}
+
+	tuncfg.encap_type = UDP_ENCAP_ESPINUDP;
+
+	err = xfrm_socket_encap_create(net, x, &udp_conf, &tuncfg);
+	if (err < 0) {
+		NL_SET_ERR_MSG(extack, "Cannot initialize kernel UDP socket");
+		return err;
+	}
+
+	return 0;
+}
+
 static int xfrm_add_sa(struct sk_buff *skb, struct nlmsghdr *nlh,
 		       struct nlattr **attrs, struct netlink_ext_ack *extack)
 {
@@ -1053,6 +1163,14 @@ static int xfrm_add_sa(struct sk_buff *skb, struct nlmsghdr *nlh,
 	if (!x)
 		return err;
 
+	if (x->props.extra_flags & XFRM_SA_XFLAG_UDP_ENCAP_SOCK) {
+		err = xfrm_socket_setup(net, x, extack);
+		if (err < 0) {
+			x->km.state = XFRM_STATE_DEAD;
+			goto out;
+		}
+	}
+
 	xfrm_state_hold(x);
 	if (nlh->nlmsg_type == XFRM_MSG_NEWSA)
 		err = xfrm_state_add(x);
-- 
2.47.3


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-07-17 11:01 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-17 11:01 [PATCH net-next,v3] xfrm: allow to enable udp encapsulation without userspace socket Pablo Neira Ayuso

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox