* [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device
@ 2026-03-22 0:05 Andrea Mayer
2026-03-22 0:05 ` [RFC PATCH net-next 1/3] seg6: add support for the SRv6 End.DT2U behavior Andrea Mayer
` (4 more replies)
0 siblings, 5 replies; 15+ messages in thread
From: Andrea Mayer @ 2026-03-22 0:05 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Stefano Salsano, Paolo Lungaroni,
Ahmed Abdelsalam, Justin Iurman, linux-kernel, Andrea Mayer
Hi all,
this RFC series adds support for the SRv6 End.DT2U behavior and
introduces the srl2 Ethernet pseudowire device, together with
corresponding selftests.
Motivation
==========
The main goal is to enable L2 service delivery through an internal
SRv6 L2 endpoint, in a way similar to how VXLAN provides L2 services
through a VXLAN netdevice.
Today, Linux supports SRv6 L2 decapsulation only through an
End.DX2-style model, where the exposed Ethernet frame is forwarded
to a configured egress interface. The current implementation does
not provide a native netdevice representing the SRv6 L2 service
endpoint. This is a significant limitation compared to VXLAN, where
the tunnel endpoint is represented by a netdevice that can be
attached to a bridge and integrated naturally into the local L2
data plane.
On the encapsulation side, the existing H.L2.Encaps mode is
implemented as a route-based lwtunnel, which only intercepts packets
entering the IP routing path. Non-IP protocols such as ARP are
never routed and therefore cannot be encapsulated, making it
unsuitable for full L2 service delivery.
Design
======
This series addresses that limitation by introducing srl2, a native
SRv6 L2 endpoint interface. The intent is to make SRv6 L2 delivery
usable in the same deployment model already familiar from VXLAN: an
internal interface that represents the L2 service endpoint and can
participate naturally in local L2 forwarding.
On top of that, the series adds support for End.DT2U so that Ethernet
payloads carried over SRv6 can be decapsulated and delivered into an
L2 domain through either a bridge port or an srl2 interface.
Relation to RFC 8986
====================
RFC 8986 describes End.DT2U in terms of an abstract L2 table
associated with the SID. That abstraction specifies the externally
observable forwarding behavior of the node, but it does not mandate a
specific internal implementation. What matters is functional
equivalence.
This series enforces that property by restricting valid End.DT2U
decapsulation targets to endpoints that provide the required L2
forwarding semantics by construction.
When the target device is a bridge port, the Linux bridge provides
the expected behavior directly: source MAC learning, destination MAC
lookup, and unknown-unicast flooding within the corresponding L2
domain.
When the target device is an srl2 interface, the same model is
realized in a degenerate but valid form, corresponding to a minimal
two-port L2 domain in which one endpoint is srl2. In that case,
forwarding is trivial: frames are delivered to srl2 only when
addressed to its MAC address, or when they are broadcast or multicast.
No general FDB is required in this case because there are no
additional L2 destinations to learn.
Any other type of decapsulation target is rejected at configuration
time, since it would not guarantee the forwarding behavior required by
End.DT2U.
The srl2 device is the minimal baseline implementation providing
point-to-point L2 encapsulation with a single segment list.
Additional features can be added incrementally based on community
feedback and use cases.
Usage
=====
ip link add srl2-0 type srl2 segs fc00::a,fc00::b
ip link set srl2-0 master br0
ip -6 route add fc00::100/128 encap seg6local action End.DT2U \
l2dev srl2-0 dev dum0
# encapsulating traffic from veth-hs into srl2-0
ip link set veth-hs master br0
Selftest topology
=================
cafe::1 cafe::2
10.0.0.1 10.0.0.2
+--------+ +--------+
| hs-1 | | hs-2 |
+---+----+ +----+---+
| |
+-----+------+ +------+-----+
| veth-hs | | veth-hs |
| | | fcf0:0:1:2::/64 | | |
| br0 +-------------------------+ br0 |
| | | | | |
| srl2-0 | | srl2-0 |
| rt-1 | | rt-2 |
+------------+ +------------+
The test verifies IPv4/IPv6 host-to-host and host-to-gateway
connectivity over the SRv6 L2 VPN, including ARP resolution
through the tunnel.
A companion iproute2 series provides userspace support:
[RFC PATCH iproute2-next 0/4] seg6: add SRv6 End.DT2U and srl2 support
This is an RFC posting to collect feedback on both the overall design
and the userspace API before moving forward with a formal submission.
Comments are very welcome, especially on:
- the introduction of srl2 as an internal SRv6 L2 endpoint
- the choice of constraining End.DT2U targets to bridge ports
and srl2 devices
- the netlink/uAPI exposure
- the selftest coverage and topology
Thanks,
Andrea and Stefano
Andrea Mayer (3):
seg6: add support for the SRv6 End.DT2U behavior
seg6: add SRv6 L2 tunnel device (srl2)
selftests: seg6: add SRv6 srl2 + End.DT2U L2 VPN test
include/linux/srl2.h | 7 +
include/uapi/linux/seg6_local.h | 3 +
include/uapi/linux/srl2.h | 20 +
net/ipv6/Kconfig | 16 +
net/ipv6/Makefile | 1 +
net/ipv6/seg6.c | 1 +
net/ipv6/seg6_local.c | 160 ++++-
net/ipv6/srl2.c | 269 ++++++++
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/config | 1 +
.../selftests/net/srv6_srl2_l2vpn_test.sh | 621 ++++++++++++++++++
11 files changed, 1094 insertions(+), 6 deletions(-)
create mode 100644 include/linux/srl2.h
create mode 100644 include/uapi/linux/srl2.h
create mode 100644 net/ipv6/srl2.c
create mode 100755 tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh
--
2.20.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [RFC PATCH net-next 1/3] seg6: add support for the SRv6 End.DT2U behavior
2026-03-22 0:05 [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device Andrea Mayer
@ 2026-03-22 0:05 ` Andrea Mayer
2026-03-22 0:05 ` [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2) Andrea Mayer
` (3 subsequent siblings)
4 siblings, 0 replies; 15+ messages in thread
From: Andrea Mayer @ 2026-03-22 0:05 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Stefano Salsano, Paolo Lungaroni,
Ahmed Abdelsalam, Justin Iurman, linux-kernel, Andrea Mayer
Implement the SRv6 End.DT2U (Endpoint with decapsulation and unicast
MAC L2 table lookup) behavior as defined in RFC 8986, Section 4.11.
End.DT2U decapsulates an SRv6 packet carrying an inner Ethernet frame
and delivers it on a user-specified device (l2dev). The l2dev is
expected to be a bridge port, where the Linux bridge provides MAC
learning and L2 forwarding as required by the RFC, or a srl2
Ethernet pseudowire device.
A new netlink attribute SEG6_LOCAL_L2DEV identifies the target device.
Usage:
ip -6 route add fc00::100/128 encap seg6local action End.DT2U \
l2dev veth0 dev dum0
where veth0 is a bridge port.
Co-developed-by: Stefano Salsano <stefano.salsano@uniroma2.it>
Signed-off-by: Stefano Salsano <stefano.salsano@uniroma2.it>
Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
include/uapi/linux/seg6_local.h | 3 +
net/ipv6/seg6_local.c | 160 ++++++++++++++++++++++++++++++--
2 files changed, 157 insertions(+), 6 deletions(-)
diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
index 4fdc424c9cb3..f9aa627be1fd 100644
--- a/include/uapi/linux/seg6_local.h
+++ b/include/uapi/linux/seg6_local.h
@@ -29,6 +29,7 @@ enum {
SEG6_LOCAL_VRFTABLE,
SEG6_LOCAL_COUNTERS,
SEG6_LOCAL_FLAVORS,
+ SEG6_LOCAL_L2DEV,
__SEG6_LOCAL_MAX,
};
#define SEG6_LOCAL_MAX (__SEG6_LOCAL_MAX - 1)
@@ -67,6 +68,8 @@ enum {
SEG6_LOCAL_ACTION_END_BPF = 15,
/* decap and lookup of DA in v4 or v6 table */
SEG6_LOCAL_ACTION_END_DT46 = 16,
+ /* decap and unicast MAC L2 table lookup */
+ SEG6_LOCAL_ACTION_END_DT2U = 17,
__SEG6_LOCAL_ACTION_MAX,
};
diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
index 2b41e4c0dddd..a766f235c532 100644
--- a/net/ipv6/seg6_local.c
+++ b/net/ipv6/seg6_local.c
@@ -192,6 +192,7 @@ struct seg6_local_lwt {
struct in6_addr nh6;
int iif;
int oif;
+ int l2dev;
struct bpf_lwt_prog bpf;
#ifdef CONFIG_NET_L3_MASTER_DEV
struct seg6_end_dt_info dt_info;
@@ -902,6 +903,118 @@ static int input_action_end_dx2(struct sk_buff *skb,
return -EINVAL;
}
+static inline bool seg6_is_valid_l2dev(const struct net_device *dev)
+{
+ if (netif_is_bridge_port(dev))
+ return true;
+
+ /* A netif_is_srl2() helper, similar to netif_is_vxlan(), could wrap
+ * this check.
+ */
+ if (dev->rtnl_link_ops &&
+ !strcmp(dev->rtnl_link_ops->kind, "srl2"))
+ return true;
+
+ return false;
+}
+
+/* decapsulate and deliver inner L2 frame locally on specified device.
+ * Implements End.DT2U (RFC 8986, Section 4.11) by delivering the
+ * decapsulated Ethernet frame on a device that provides L2 table
+ * semantics (bridge port) or L2 delivery semantics (srl2 device).
+ */
+static int input_action_end_dt2u(struct sk_buff *skb,
+ struct seg6_local_lwt *slwt)
+{
+ struct net *net = dev_net(skb->dev);
+ struct net_device *l2dev;
+
+ if (!decap_and_validate(skb, IPPROTO_ETHERNET))
+ goto drop;
+
+ if (!pskb_may_pull(skb, ETH_HLEN))
+ goto drop;
+
+ l2dev = dev_get_by_index_rcu(net, slwt->l2dev);
+ if (!l2dev)
+ goto drop;
+
+ if (l2dev->type != ARPHRD_ETHER)
+ goto drop;
+
+ /* Consistent with the carrier check in input_action_end_dx2(). */
+ if (!(l2dev->flags & IFF_UP) || !netif_carrier_ok(l2dev))
+ goto drop;
+
+ /* RFC 8986 requires L2 forwarding semantics. Only bridge ports
+ * and srl2 devices satisfy this requirement.
+ */
+ if (!seg6_is_valid_l2dev(l2dev))
+ goto drop;
+
+ skb_orphan(skb);
+
+ if (skb_warn_if_lro(skb))
+ goto drop;
+
+ /* eth_type_trans() sets skb->dev, skb->pkt_type, pulls ETH_HLEN,
+ * and returns the protocol. This is required for proper L2
+ * processing when the device is a bridge port.
+ */
+ skb->protocol = eth_type_trans(skb, l2dev);
+
+ /* Reset network_header to point to the L3 header (past ethernet).
+ * eth_type_trans pulled ETH_HLEN, so skb->data is at the L3 header
+ * now. Without this, network_header still points to the ethernet
+ * header (set by decap_and_validate), and ip_rcv would read garbage
+ * when the bridge delivers the frame to the local stack.
+ */
+ skb_reset_network_header(skb);
+
+ /* Drop the dst inherited from the outer SRv6 packet. Without
+ * this, when the bridge delivers locally (e.g. to a bridge IP),
+ * ip_rcv would skip route lookup and use the stale IPv6 lwtunnel
+ * dst for an IPv4 packet, causing a silent drop.
+ */
+ skb_dst_drop(skb);
+
+ netif_rx(skb);
+
+ return 0;
+
+drop:
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+static struct net *fib6_config_get_net(const struct fib6_config *fib6_cfg)
+{
+ const struct nl_info *nli = &fib6_cfg->fc_nlinfo;
+
+ return nli->nl_net;
+}
+
+static int seg6_end_dt2u_build(struct seg6_local_lwt *slwt, const void *cfg,
+ struct netlink_ext_ack *extack)
+{
+ struct net *net = fib6_config_get_net(cfg);
+ struct net_device *dev;
+
+ dev = __dev_get_by_index(net, slwt->l2dev);
+ if (!dev) {
+ NL_SET_ERR_MSG(extack, "l2dev device not found");
+ return -ENODEV;
+ }
+
+ if (!seg6_is_valid_l2dev(dev)) {
+ NL_SET_ERR_MSG(extack,
+ "l2dev must be a bridge port or srl2 device");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int input_action_end_dx6_finish(struct net *net, struct sock *sk,
struct sk_buff *skb)
{
@@ -1004,12 +1117,6 @@ static int input_action_end_dx4(struct sk_buff *skb,
}
#ifdef CONFIG_NET_L3_MASTER_DEV
-static struct net *fib6_config_get_net(const struct fib6_config *fib6_cfg)
-{
- const struct nl_info *nli = &fib6_cfg->fc_nlinfo;
-
- return nli->nl_net;
-}
static int __seg6_end_dt_vrf_build(struct seg6_local_lwt *slwt, const void *cfg,
u16 family, struct netlink_ext_ack *extack)
@@ -1565,6 +1672,15 @@ static struct seg6_action_desc seg6_action_table[] = {
.optattrs = SEG6_F_LOCAL_COUNTERS,
.input = input_action_end_bpf,
},
+ {
+ .action = SEG6_LOCAL_ACTION_END_DT2U,
+ .attrs = SEG6_F_ATTR(SEG6_LOCAL_L2DEV),
+ .optattrs = SEG6_F_LOCAL_COUNTERS,
+ .input = input_action_end_dt2u,
+ .slwt_ops = {
+ .build_state = seg6_end_dt2u_build,
+ },
+ },
};
@@ -1655,6 +1771,7 @@ static const struct nla_policy seg6_local_policy[SEG6_LOCAL_MAX + 1] = {
[SEG6_LOCAL_BPF] = { .type = NLA_NESTED },
[SEG6_LOCAL_COUNTERS] = { .type = NLA_NESTED },
[SEG6_LOCAL_FLAVORS] = { .type = NLA_NESTED },
+ [SEG6_LOCAL_L2DEV] = { .type = NLA_U32 },
};
static int parse_nla_srh(struct nlattr **attrs, struct seg6_local_lwt *slwt,
@@ -1889,6 +2006,30 @@ static int cmp_nla_oif(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
return 0;
}
+static int parse_nla_l2dev(struct nlattr **attrs, struct seg6_local_lwt *slwt,
+ struct netlink_ext_ack *extack)
+{
+ slwt->l2dev = nla_get_u32(attrs[SEG6_LOCAL_L2DEV]);
+
+ return 0;
+}
+
+static int put_nla_l2dev(struct sk_buff *skb, struct seg6_local_lwt *slwt)
+{
+ if (nla_put_u32(skb, SEG6_LOCAL_L2DEV, slwt->l2dev))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int cmp_nla_l2dev(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
+{
+ if (a->l2dev != b->l2dev)
+ return 1;
+
+ return 0;
+}
+
#define MAX_PROG_NAME 256
static const struct nla_policy bpf_prog_policy[SEG6_LOCAL_BPF_PROG_MAX + 1] = {
[SEG6_LOCAL_BPF_PROG] = { .type = NLA_U32, },
@@ -2318,6 +2459,10 @@ static struct seg6_action_param seg6_action_params[SEG6_LOCAL_MAX + 1] = {
[SEG6_LOCAL_FLAVORS] = { .parse = parse_nla_flavors,
.put = put_nla_flavors,
.cmp = cmp_nla_flavors },
+
+ [SEG6_LOCAL_L2DEV] = { .parse = parse_nla_l2dev,
+ .put = put_nla_l2dev,
+ .cmp = cmp_nla_l2dev },
};
/* call the destroy() callback (if available) for each set attribute in
@@ -2634,6 +2779,9 @@ static int seg6_local_get_encap_size(struct lwtunnel_state *lwt)
if (attrs & SEG6_F_ATTR(SEG6_LOCAL_FLAVORS))
nlsize += encap_size_flavors(slwt);
+ if (attrs & SEG6_F_ATTR(SEG6_LOCAL_L2DEV))
+ nlsize += nla_total_size(4);
+
return nlsize;
}
--
2.20.1
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2)
2026-03-22 0:05 [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device Andrea Mayer
2026-03-22 0:05 ` [RFC PATCH net-next 1/3] seg6: add support for the SRv6 End.DT2U behavior Andrea Mayer
@ 2026-03-22 0:05 ` Andrea Mayer
2026-03-24 16:08 ` Justin Iurman
` (2 more replies)
2026-03-22 0:05 ` [RFC PATCH net-next 3/3] selftests: seg6: add SRv6 srl2 + End.DT2U L2 VPN test Andrea Mayer
` (2 subsequent siblings)
4 siblings, 3 replies; 15+ messages in thread
From: Andrea Mayer @ 2026-03-22 0:05 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Stefano Salsano, Paolo Lungaroni,
Ahmed Abdelsalam, Justin Iurman, linux-kernel, Andrea Mayer
Introduce srl2, an Ethernet pseudowire device over SRv6. It
encapsulates L2 frames in IPv6 with a Segment Routing Header for
transmission across an SRv6 network.
The encapsulation logic reuses seg6_do_srh_encap() with
IPPROTO_ETHERNET. The transmit path uses the standard IPv6 tunnel
infrastructure (dst_cache, ip6_route_output, ip6tunnel_xmit).
The device is configured with a segment list for point-to-point
L2 encapsulation.
Usage:
ip link add srl2-0 type srl2 segs fc00::a,fc00::b
Co-developed-by: Stefano Salsano <stefano.salsano@uniroma2.it>
Signed-off-by: Stefano Salsano <stefano.salsano@uniroma2.it>
Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
include/linux/srl2.h | 7 +
include/uapi/linux/srl2.h | 20 +++
net/ipv6/Kconfig | 16 +++
net/ipv6/Makefile | 1 +
net/ipv6/seg6.c | 1 +
net/ipv6/srl2.c | 269 ++++++++++++++++++++++++++++++++++++++
6 files changed, 314 insertions(+)
create mode 100644 include/linux/srl2.h
create mode 100644 include/uapi/linux/srl2.h
create mode 100644 net/ipv6/srl2.c
diff --git a/include/linux/srl2.h b/include/linux/srl2.h
new file mode 100644
index 000000000000..c1342b979402
--- /dev/null
+++ b/include/linux/srl2.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef _LINUX_SRL2_H
+#define _LINUX_SRL2_H
+
+#include <uapi/linux/srl2.h>
+
+#endif
diff --git a/include/uapi/linux/srl2.h b/include/uapi/linux/srl2.h
new file mode 100644
index 000000000000..e7c8f6fc0791
--- /dev/null
+++ b/include/uapi/linux/srl2.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */
+/*
+ * SRv6 L2 tunnel device
+ *
+ * Author:
+ * Andrea Mayer <andrea.mayer@uniroma2.it>
+ */
+
+#ifndef _UAPI_LINUX_SRL2_H
+#define _UAPI_LINUX_SRL2_H
+
+enum {
+ IFLA_SRL2_UNSPEC,
+ IFLA_SRL2_SRH, /* binary: struct ipv6_sr_hdr + segments */
+ __IFLA_SRL2_MAX,
+};
+
+#define IFLA_SRL2_MAX (__IFLA_SRL2_MAX - 1)
+
+#endif
diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
index b8f9a8c0302e..9c8f7e254435 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -318,6 +318,22 @@ config IPV6_SEG6_BPF
depends on IPV6_SEG6_LWTUNNEL
depends on IPV6 = y
+config IPV6_SRL2
+ tristate "IPv6: SRv6 L2 tunnel device"
+ depends on IPV6_SEG6_LWTUNNEL
+ select DST_CACHE
+ help
+ SRv6 virtual Ethernet device that encapsulates L2 frames in
+ IPv6 with a Segment Routing Header (SRH) for transmission
+ over an SRv6 network.
+ Intended for use with a remote seg6local L2 decapsulation
+ behavior, such as End.DT2U or End.DX2.
+
+ To compile this as a module, choose M here: the module will
+ be called srl2.
+
+ If unsure, say N.
+
config IPV6_RPL_LWTUNNEL
bool "IPv6: RPL Source Routing Header support"
depends on IPV6
diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile
index 2c9ce2ccbde1..a7e81d0293ca 100644
--- a/net/ipv6/Makefile
+++ b/net/ipv6/Makefile
@@ -24,6 +24,7 @@ ipv6-$(CONFIG_SYN_COOKIES) += syncookies.o
ipv6-$(CONFIG_NETLABEL) += calipso.o
ipv6-$(CONFIG_IPV6_SEG6_LWTUNNEL) += seg6_iptunnel.o seg6_local.o
ipv6-$(CONFIG_IPV6_SEG6_HMAC) += seg6_hmac.o
+obj-$(CONFIG_IPV6_SRL2) += srl2.o
ipv6-$(CONFIG_IPV6_RPL_LWTUNNEL) += rpl_iptunnel.o
ipv6-$(CONFIG_IPV6_IOAM6_LWTUNNEL) += ioam6_iptunnel.o
diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c
index 1c3ad25700c4..23213ab4fefd 100644
--- a/net/ipv6/seg6.c
+++ b/net/ipv6/seg6.c
@@ -72,6 +72,7 @@ bool seg6_validate_srh(struct ipv6_sr_hdr *srh, int len, bool reduced)
return true;
}
+EXPORT_SYMBOL_GPL(seg6_validate_srh);
struct ipv6_sr_hdr *seg6_get_srh(struct sk_buff *skb, int flags)
{
diff --git a/net/ipv6/srl2.c b/net/ipv6/srl2.c
new file mode 100644
index 000000000000..66aa5375d218
--- /dev/null
+++ b/net/ipv6/srl2.c
@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SRv6 L2 tunnel device (srl2)
+ *
+ * A virtual Ethernet device that encapsulates L2 frames in IPv6 with a
+ * Segment Routing Header (SRH) for transmission over an SRv6 network.
+ * On the remote side, a seg6_local behavior such as End.DT2U or End.DX2
+ * decapsulates the inner Ethernet frame for L2 delivery.
+ *
+ * The encapsulation logic reuses seg6_do_srh_encap() from seg6_iptunnel.c
+ * with IPPROTO_ETHERNET (143). The transmit path uses the standard IPv6
+ * tunnel infrastructure (dst_cache, ip6_route_output, ip6tunnel_xmit).
+ *
+ * Authors:
+ * Andrea Mayer <andrea.mayer@uniroma2.it>
+ * Stefano Salsano <stefano.salsano@uniroma2.it>
+ */
+
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/dst_cache.h>
+#include <net/ip6_route.h>
+#include <net/ip_tunnels.h>
+#include <net/ip6_tunnel.h>
+#include <net/seg6.h>
+#include <linux/seg6.h>
+#include <linux/srl2.h>
+
+/* Conservative initial estimate for SRH size before newlink provides
+ * the actual value. 256 bytes accommodates up to 15 SIDs.
+ */
+#define SRL2_SRH_HEADROOM_EST 256
+
+struct srl2_priv {
+ struct ipv6_sr_hdr *srh;
+ struct dst_cache dst_cache;
+};
+
+/*
+ * srl2_xmit - encapsulate an L2 frame in IPv6+SRH and transmit
+ *
+ * When the bridge (or local stack) sends a frame through this device,
+ * skb->data points to the inner Ethernet header. We look up a route
+ * towards the first SID, prepend the outer IPv6+SRH via
+ * seg6_do_srh_encap(), and transmit via ip6tunnel_xmit().
+ *
+ * The route lookup result is cached per-cpu in dst_cache. Since the
+ * first SID is constant for the lifetime of the device, the cache
+ * avoids repeated route lookups in the common case.
+ */
+static netdev_tx_t srl2_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct srl2_priv *priv = netdev_priv(dev);
+ struct net *net = dev_net(dev);
+ struct dst_entry *dst;
+ struct flowi6 fl6;
+ int err;
+
+ local_bh_disable();
+ dst = dst_cache_get(&priv->dst_cache);
+ local_bh_enable();
+
+ if (unlikely(!dst)) {
+ memset(&fl6, 0, sizeof(fl6));
+ fl6.daddr = priv->srh->segments[priv->srh->first_segment];
+
+ dst = ip6_route_output(net, NULL, &fl6);
+ if (dst->error) {
+ dst_release(dst);
+ DEV_STATS_INC(dev, tx_carrier_errors);
+ goto drop;
+ }
+
+ if (dst_dev(dst) == dev) {
+ dst_release(dst);
+ DEV_STATS_INC(dev, collisions);
+ goto drop;
+ }
+
+ local_bh_disable();
+ /* saddr is unused */
+ dst_cache_set_ip6(&priv->dst_cache, dst, &fl6.saddr);
+ local_bh_enable();
+ }
+
+ skb_scrub_packet(skb, false);
+
+ skb_dst_set(skb, dst);
+
+ err = seg6_do_srh_encap(skb, priv->srh, IPPROTO_ETHERNET);
+ if (unlikely(err)) {
+ DEV_STATS_INC(dev, tx_errors);
+ kfree_skb(skb);
+ return NETDEV_TX_OK;
+ }
+
+ skb->protocol = htons(ETH_P_IPV6);
+
+ ip6tunnel_xmit(NULL, skb, dev, 0);
+
+ return NETDEV_TX_OK;
+
+drop:
+ DEV_STATS_INC(dev, tx_dropped);
+ kfree_skb(skb);
+ return NETDEV_TX_OK;
+}
+
+static int srl2_dev_init(struct net_device *dev)
+{
+ struct srl2_priv *priv = netdev_priv(dev);
+
+ return dst_cache_init(&priv->dst_cache, GFP_KERNEL);
+}
+
+static void srl2_dev_uninit(struct net_device *dev)
+{
+ struct srl2_priv *priv = netdev_priv(dev);
+
+ dst_cache_destroy(&priv->dst_cache);
+}
+
+static void srl2_dev_free(struct net_device *dev)
+{
+ struct srl2_priv *priv = netdev_priv(dev);
+
+ kfree(priv->srh);
+}
+
+static const struct net_device_ops srl2_netdev_ops = {
+ .ndo_init = srl2_dev_init,
+ .ndo_uninit = srl2_dev_uninit,
+ .ndo_start_xmit = srl2_xmit,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+};
+
+static void srl2_setup(struct net_device *dev)
+{
+ ether_setup(dev);
+
+ dev->netdev_ops = &srl2_netdev_ops;
+ dev->needs_free_netdev = true;
+ dev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;
+ dev->needed_headroom = LL_MAX_HEADER + sizeof(struct ipv6hdr) +
+ SRL2_SRH_HEADROOM_EST;
+
+ dev->priv_flags &= ~IFF_TX_SKB_SHARING;
+ dev->priv_flags |= IFF_LIVE_ADDR_CHANGE | IFF_NO_QUEUE;
+ dev->lltx = true;
+
+ eth_hw_addr_random(dev);
+}
+
+static const struct nla_policy srl2_policy[IFLA_SRL2_MAX + 1] = {
+ [IFLA_SRL2_SRH] = { .type = NLA_BINARY },
+};
+
+static int srl2_validate(struct nlattr *tb[], struct nlattr *data[],
+ struct netlink_ext_ack *extack)
+{
+ if (!data || !data[IFLA_SRL2_SRH]) {
+ NL_SET_ERR_MSG(extack, "SRH with segment list is required");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int srl2_newlink(struct net_device *dev,
+ struct rtnl_newlink_params *params,
+ struct netlink_ext_ack *extack)
+{
+ struct srl2_priv *priv = netdev_priv(dev);
+ struct nlattr **data = params->data;
+ struct ipv6_sr_hdr *srh;
+ int srhlen;
+ int len;
+
+ srh = nla_data(data[IFLA_SRL2_SRH]);
+ len = nla_len(data[IFLA_SRL2_SRH]);
+
+ if (len < sizeof(*srh) + sizeof(struct in6_addr)) {
+ NL_SET_ERR_MSG(extack, "SRH too short");
+ return -EINVAL;
+ }
+
+ if (!seg6_validate_srh(srh, len, false)) {
+ NL_SET_ERR_MSG(extack, "Invalid SRH");
+ return -EINVAL;
+ }
+
+ priv->srh = kmemdup(srh, len, GFP_KERNEL);
+ if (!priv->srh)
+ return -ENOMEM;
+
+ srhlen = ipv6_optlen(srh);
+
+ dev->needed_headroom = LL_MAX_HEADER + sizeof(struct ipv6hdr) + srhlen;
+
+ /* dev->mtu is the inner L3 payload size. Since SRv6 encapsulation
+ * carries the full inner Ethernet frame, subtract both the outer
+ * IPv6+SRH overhead and ETH_HLEN from ETH_DATA_LEN.
+ */
+ dev->mtu = ETH_DATA_LEN - sizeof(struct ipv6hdr) - srhlen - ETH_HLEN;
+ dev->min_mtu = ETH_MIN_MTU;
+ dev->max_mtu = IP_MAX_MTU - sizeof(struct ipv6hdr) - srhlen - ETH_HLEN;
+
+ dev->priv_destructor = srl2_dev_free;
+
+ return register_netdevice(dev);
+}
+
+static void srl2_dellink(struct net_device *dev, struct list_head *head)
+{
+ unregister_netdevice_queue(dev, head);
+}
+
+static size_t srl2_get_size(const struct net_device *dev)
+{
+ const struct srl2_priv *priv = netdev_priv(dev);
+ int srhlen = ipv6_optlen(priv->srh);
+
+ return nla_total_size(srhlen);
+}
+
+static int srl2_fill_info(struct sk_buff *skb, const struct net_device *dev)
+{
+ const struct srl2_priv *priv = netdev_priv(dev);
+ int srhlen = ipv6_optlen(priv->srh);
+
+ if (nla_put(skb, IFLA_SRL2_SRH, srhlen, priv->srh))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static struct rtnl_link_ops srl2_link_ops __read_mostly = {
+ .kind = "srl2",
+ .maxtype = IFLA_SRL2_MAX,
+ .policy = srl2_policy,
+ .priv_size = sizeof(struct srl2_priv),
+ .setup = srl2_setup,
+ .validate = srl2_validate,
+ .newlink = srl2_newlink,
+ .dellink = srl2_dellink,
+ .get_size = srl2_get_size,
+ .fill_info = srl2_fill_info,
+};
+
+static int __init srl2_init(void)
+{
+ return rtnl_link_register(&srl2_link_ops);
+}
+
+static void __exit srl2_exit(void)
+{
+ rtnl_link_unregister(&srl2_link_ops);
+}
+
+module_init(srl2_init);
+module_exit(srl2_exit);
+
+MODULE_AUTHOR("Andrea Mayer <andrea.mayer@uniroma2.it>");
+MODULE_AUTHOR("Stefano Salsano <stefano.salsano@uniroma2.it>");
+MODULE_DESCRIPTION("SRv6 L2 tunnel device");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_RTNL_LINK("srl2");
--
2.20.1
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH net-next 3/3] selftests: seg6: add SRv6 srl2 + End.DT2U L2 VPN test
2026-03-22 0:05 [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device Andrea Mayer
2026-03-22 0:05 ` [RFC PATCH net-next 1/3] seg6: add support for the SRv6 End.DT2U behavior Andrea Mayer
2026-03-22 0:05 ` [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2) Andrea Mayer
@ 2026-03-22 0:05 ` Andrea Mayer
2026-03-24 16:00 ` [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device Justin Iurman
2026-03-26 16:32 ` Nicolas Dichtel
4 siblings, 0 replies; 15+ messages in thread
From: Andrea Mayer @ 2026-03-22 0:05 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Stefano Salsano, Paolo Lungaroni,
Ahmed Abdelsalam, Justin Iurman, linux-kernel, Andrea Mayer,
Shuah Khan, linux-kselftest
Add a selftest for the srl2 Ethernet pseudowire device exercising the
full L2 VPN data path: srl2 for encapsulation and End.DT2U for
decapsulation, connected through a Linux bridge.
The test verifies IPv4/IPv6 host-to-host and host-to-gateway
connectivity over a two-router topology.
Cc: Shuah Khan <shuah@kernel.org>
Cc: linux-kselftest@vger.kernel.org
Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/config | 1 +
.../selftests/net/srv6_srl2_l2vpn_test.sh | 621 ++++++++++++++++++
3 files changed, 623 insertions(+)
create mode 100755 tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 6bced3ed798b..d2301387b21c 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -92,6 +92,7 @@ TEST_PROGS := \
srv6_end_x_next_csid_l3vpn_test.sh \
srv6_hencap_red_l3vpn_test.sh \
srv6_hl2encap_red_l2vpn_test.sh \
+ srv6_srl2_l2vpn_test.sh \
stress_reuseport_listen.sh \
tcp_fastopen_backup_key.sh \
test_bpf.sh \
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index 2a390cae41bf..77d5c7941969 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -48,6 +48,7 @@ CONFIG_IPV6_ROUTER_PREF=y
CONFIG_IPV6_RPL_LWTUNNEL=y
CONFIG_IPV6_SEG6_LWTUNNEL=y
CONFIG_IPV6_SIT=y
+CONFIG_IPV6_SRL2=m
CONFIG_IPV6_VTI=y
CONFIG_IPVLAN=m
CONFIG_IPVTAP=m
diff --git a/tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh b/tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh
new file mode 100755
index 000000000000..eefc1274e139
--- /dev/null
+++ b/tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh
@@ -0,0 +1,621 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# author: Andrea Mayer <andrea.mayer@uniroma2.it>
+#
+# This script tests the full SRv6 L2 VPN data path using the srl2
+# virtual Ethernet device for L2 frame encapsulation and the End.DT2U
+# behavior (RFC 8986, Section 4.11) for decapsulation.
+#
+# This test exercises the full SRv6 L2 VPN data path using the srl2
+# device as the TX-side encapsulator. Each SRv6 router uses a bridge
+# (br0) with two ports:
+# - veth-hs: connects to the host
+# - srl2-0: SRv6 L2 tunnel device for encap/decap
+#
+# TX path: the host sends an L2 frame via veth-hs. The bridge forwards
+# the frame to srl2-0 (L2 forwarding based on dst MAC). srl2-0
+# encapsulates the frame in IPv6+SRH and transmits.
+#
+# RX path: an SRv6 packet arrives carrying an inner Ethernet frame.
+# End.DT2U decapsulates and delivers the frame on srl2-0 via
+# netif_rx(). Since srl2-0 is a bridge port, the bridge performs MAC
+# learning and L2 forwarding to deliver the frame to veth-hs.
+#
+# Note: no static MAC addresses or neighbor entries are needed here.
+# ARP works naturally through the
+# bridge and the SRv6 tunnel: ARP requests are broadcast, flooded by
+# the bridge to srl2-0, encapsulated, decapsulated by End.DT2U on the
+# remote side, and flooded to the destination host.
+#
+# Topology:
+#
+# cafe::1 cafe::2
+# 10.0.0.1 10.0.0.2
+# +--------+ +--------+
+# | | | |
+# | hs-1 | | hs-2 |
+# | | | |
+# +---+----+ +----+---+
+# cafe::/64 | | cafe::/64
+# 10.0.0.0/24 | | 10.0.0.0/24
+# +-----+------+ +------+-----+
+# | veth-hs | | veth-hs |
+# | | | fcf0:0:1:2::/64 | | |
+# | br0 +-------------------------+ br0 |
+# | | | | | |
+# | srl2-0 | | srl2-0 |
+# | rt-1 | | rt-2 |
+# +------------+ +------------+
+#
+#
+# Every fcf0:0:x:y::/64 network interconnects the SRv6 routers rt-x with
+# rt-y in the IPv6 operator network.
+#
+# Local SID table
+# ===============
+#
+# Each SRv6 router is configured with a Local SID table in which SIDs are
+# stored. Considering the given SRv6 router rt-x, the following SID is
+# configured in the Local SID table:
+#
+# Local SID table for SRv6 router rt-x
+# +-----------------------------------------------------------+
+# |fcff:x::d20 is associated with the SRv6 End.DT2U behavior |
+# +-----------------------------------------------------------+
+#
+# SRv6 L2 encapsulation
+# =====================
+#
+# Each router's srl2-0 device is configured with a SID list pointing to
+# the remote router's End.DT2U SID:
+#
+# rt-1 srl2-0: segs fcff:2::d20 (encap towards rt-2)
+# rt-2 srl2-0: segs fcff:1::d20 (encap towards rt-1)
+#
+# Each SID list consists of only one SID. The srl2 device encapsulates
+# the L2 frame in an outer IPv6 header with an SRH containing the
+# segment list.
+#
+
+source lib.sh
+
+readonly DUMMY_DEVNAME="dum0"
+readonly SRL2_DEVNAME="srl2-0"
+readonly RT2HS_DEVNAME="veth-hs"
+readonly BRIDGE_DEVNAME="br0"
+readonly HS_VETH_NAME="veth0"
+readonly LOCALSID_TABLE_ID=90
+readonly IPv6_RT_NETWORK=fcf0:0
+readonly IPv6_HS_NETWORK=cafe
+readonly IPv4_HS_NETWORK=10.0.0
+readonly VPN_LOCATOR_SERVICE=fcff
+readonly DT2U_FUNC=0d20
+
+PING_TIMEOUT_SEC=4
+PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
+
+ROUTERS=''
+HOSTS=''
+
+SETUP_ERR=1
+
+ret=${ksft_skip}
+nsuccess=0
+nfail=0
+
+log_test()
+{
+ local rc="$1"
+ local expected="$2"
+ local msg="$3"
+
+ if [ "${rc}" -eq "${expected}" ]; then
+ nsuccess=$((nsuccess+1))
+ printf "\n TEST: %-60s [ OK ]\n" "${msg}"
+ else
+ ret=1
+ nfail=$((nfail+1))
+ printf "\n TEST: %-60s [FAIL]\n" "${msg}"
+ if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
+ echo
+ echo "hit enter to continue, 'q' to quit"
+ read a
+ [ "$a" = "q" ] && exit 1
+ fi
+ fi
+}
+
+print_log_test_results()
+{
+ printf "\nTests passed: %3d\n" "${nsuccess}"
+ printf "Tests failed: %3d\n" "${nfail}"
+
+ if [ "${ret}" -ne 1 ]; then
+ ret=0
+ fi
+}
+
+log_section()
+{
+ echo
+ echo "################################################################################"
+ echo "TEST SECTION: $*"
+ echo "################################################################################"
+}
+
+test_command_or_ksft_skip()
+{
+ local cmd="$1"
+
+ if [ ! -x "$(command -v "${cmd}")" ]; then
+ echo "SKIP: Could not run test without \"${cmd}\" tool";
+ exit "${ksft_skip}"
+ fi
+}
+
+get_rtname()
+{
+ local rtid="$1"
+
+ echo "rt_${rtid}"
+}
+
+get_hsname()
+{
+ local hsid="$1"
+
+ echo "hs_${hsid}"
+}
+
+create_router()
+{
+ local rtid="$1"
+ local nsname
+
+ nsname="$(get_rtname "${rtid}")"
+ setup_ns "${nsname}"
+}
+
+create_host()
+{
+ local hsid="$1"
+ local nsname
+
+ nsname="$(get_hsname "${hsid}")"
+ setup_ns "${nsname}"
+}
+
+cleanup()
+{
+ cleanup_all_ns
+
+ if [ "${SETUP_ERR}" -ne 0 ]; then
+ echo "SKIP: Setting up the testing environment failed"
+ exit "${ksft_skip}"
+ fi
+
+ exit "${ret}"
+}
+
+add_link_rt_pairs()
+{
+ local rt="$1"
+ local rt_neighs="$2"
+ local neigh
+ local nsname
+ local neigh_nsname
+
+ eval nsname=\${$(get_rtname "${rt}")}
+
+ for neigh in ${rt_neighs}; do
+ eval neigh_nsname=\${$(get_rtname "${neigh}")}
+
+ ip link add "veth-rt-${rt}-${neigh}" netns "${nsname}" \
+ type veth peer name "veth-rt-${neigh}-${rt}" \
+ netns "${neigh_nsname}"
+ done
+}
+
+get_network_prefix()
+{
+ local rt="$1"
+ local neigh="$2"
+ local p="${rt}"
+ local q="${neigh}"
+
+ if [ "${p}" -gt "${q}" ]; then
+ p="${q}"; q="${rt}"
+ fi
+
+ echo "${IPv6_RT_NETWORK}:${p}:${q}"
+}
+
+setup_rt_networking()
+{
+ local rt="$1"
+ local rt_neighs="$2"
+ local nsname
+ local net_prefix
+ local devname
+ local neigh
+
+ eval nsname=\${$(get_rtname "${rt}")}
+
+ for neigh in ${rt_neighs}; do
+ devname="veth-rt-${rt}-${neigh}"
+
+ net_prefix="$(get_network_prefix "${rt}" "${neigh}")"
+
+ ip -netns "${nsname}" addr \
+ add "${net_prefix}::${rt}/64" dev "${devname}" nodad
+
+ ip -netns "${nsname}" link set "${devname}" up
+ done
+
+ ip -netns "${nsname}" link add "${DUMMY_DEVNAME}" type dummy
+
+ ip -netns "${nsname}" link set "${DUMMY_DEVNAME}" up
+ ip -netns "${nsname}" link set lo up
+
+ ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.all.accept_dad=0
+ ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.default.accept_dad=0
+ ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.all.forwarding=1
+ ip netns exec "${nsname}" sysctl -wq net.ipv4.ip_forward=1
+}
+
+setup_rt_local_sids()
+{
+ local rt="$1"
+ local rt_neighs="$2"
+ local net_prefix
+ local devname
+ local nsname
+ local neigh
+
+ eval nsname=\${$(get_rtname "${rt}")}
+
+ for neigh in ${rt_neighs}; do
+ devname="veth-rt-${rt}-${neigh}"
+
+ net_prefix="$(get_network_prefix "${rt}" "${neigh}")"
+
+ # set underlay network routes for SIDs reachability
+ ip -netns "${nsname}" -6 route \
+ add "${VPN_LOCATOR_SERVICE}:${neigh}::/32" \
+ table "${LOCALSID_TABLE_ID}" \
+ via "${net_prefix}::${neigh}" dev "${devname}"
+ done
+
+ # Local End.DT2U behavior: decapsulate L2 frames and deliver on
+ # srl2-0 which is a bridge port; the bridge then forwards to the
+ # host connected via veth-hs.
+ ip -netns "${nsname}" -6 route \
+ add "${VPN_LOCATOR_SERVICE}:${rt}::${DT2U_FUNC}" \
+ table "${LOCALSID_TABLE_ID}" \
+ encap seg6local action End.DT2U l2dev "${SRL2_DEVNAME}" \
+ dev "${DUMMY_DEVNAME}"
+
+ # all SIDs for VPNs start with a common locator. Routes and SRv6
+ # Endpoint behaviors instances are grouped together in the 'localsid'
+ # table.
+ ip -netns "${nsname}" -6 rule add \
+ to "${VPN_LOCATOR_SERVICE}::/16" \
+ lookup "${LOCALSID_TABLE_ID}" prio 999
+}
+
+setup_hs()
+{
+ local hs="$1"
+ local rt="$2"
+ local hsname
+ local rtname
+
+ eval hsname=\${$(get_hsname "${hs}")}
+ eval rtname=\${$(get_rtname "${rt}")}
+
+ ip netns exec "${hsname}" sysctl -wq net.ipv6.conf.all.accept_dad=0
+ ip netns exec "${hsname}" sysctl -wq net.ipv6.conf.default.accept_dad=0
+
+ ip -netns "${hsname}" link add "${HS_VETH_NAME}" type veth \
+ peer name "${RT2HS_DEVNAME}" netns "${rtname}"
+
+ ip -netns "${hsname}" addr add "${IPv6_HS_NETWORK}::${hs}/64" \
+ dev "${HS_VETH_NAME}" nodad
+ ip -netns "${hsname}" addr add "${IPv4_HS_NETWORK}.${hs}/24" \
+ dev "${HS_VETH_NAME}"
+
+ ip -netns "${hsname}" link set "${HS_VETH_NAME}" up
+ ip -netns "${hsname}" link set lo up
+
+ # veth-hs is a bridge port; IPs go on br0 (see setup_bridge)
+ ip -netns "${rtname}" link set "${RT2HS_DEVNAME}" up
+}
+
+# Create srl2 device, bridge, and wire them together.
+# The srl2 device encapsulates L2 frames in IPv6+SRH towards the
+# remote router's End.DT2U SID. The bridge connects the host (via
+# veth-hs) with the SRv6 tunnel (via srl2-0).
+# args:
+# $1 - router id
+# $2 - remote router id (for SID list)
+setup_bridge()
+{
+ local rt="$1"
+ local remote_rt="$2"
+ local nsname
+
+ eval nsname=\${$(get_rtname "${rt}")}
+
+ # create the srl2 device pointing to the remote End.DT2U SID
+ ip -netns "${nsname}" link add "${SRL2_DEVNAME}" type srl2 \
+ segs "${VPN_LOCATOR_SERVICE}:${remote_rt}::${DT2U_FUNC}"
+ ip -netns "${nsname}" link set "${SRL2_DEVNAME}" up
+
+ # create bridge and add ports
+ ip -netns "${nsname}" link add "${BRIDGE_DEVNAME}" type bridge
+ ip -netns "${nsname}" link set "${BRIDGE_DEVNAME}" up
+
+ ip -netns "${nsname}" link set "${RT2HS_DEVNAME}" master \
+ "${BRIDGE_DEVNAME}"
+ ip -netns "${nsname}" link set "${SRL2_DEVNAME}" master \
+ "${BRIDGE_DEVNAME}"
+
+ # IP addresses on br0 (gateway for the hosts)
+ ip -netns "${nsname}" addr add "${IPv6_HS_NETWORK}::254/64" \
+ dev "${BRIDGE_DEVNAME}" nodad
+ ip -netns "${nsname}" addr \
+ add "${IPv4_HS_NETWORK}.254/24" dev "${BRIDGE_DEVNAME}"
+}
+
+setup()
+{
+ local i
+
+ # create routers
+ ROUTERS="1 2"; readonly ROUTERS
+ for i in ${ROUTERS}; do
+ create_router "${i}"
+ done
+
+ # create hosts
+ HOSTS="1 2"; readonly HOSTS
+ for i in ${HOSTS}; do
+ create_host "${i}"
+ done
+
+ # set up the links for connecting routers
+ add_link_rt_pairs 1 "2"
+
+ # set up the basic connectivity of routers and routes required for
+ # reachability of SIDs.
+ setup_rt_networking 1 "2"
+ setup_rt_networking 2 "1"
+
+ # set up the hosts connected to routers
+ setup_hs 1 1
+ setup_hs 2 2
+
+ # set up srl2 devices and bridges on each router.
+ # rt-1's srl2-0 encapsulates towards rt-2's End.DT2U SID and
+ # vice versa.
+ setup_bridge 1 2
+ setup_bridge 2 1
+
+ # set up SRv6 Endpoints (i.e. SRv6 End.DT2U)
+ setup_rt_local_sids 1 "2"
+ setup_rt_local_sids 2 "1"
+
+ # testing environment was set up successfully
+ SETUP_ERR=0
+}
+
+check_rt_connectivity()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+ local prefix
+ local rtsrc_nsname
+
+ eval rtsrc_nsname=\${$(get_rtname "${rtsrc}")}
+
+ prefix="$(get_network_prefix "${rtsrc}" "${rtdst}")"
+
+ ip netns exec "${rtsrc_nsname}" ping -c 1 -W "${PING_TIMEOUT_SEC}" \
+ "${prefix}::${rtdst}" >/dev/null 2>&1
+}
+
+check_and_log_rt_connectivity()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+
+ check_rt_connectivity "${rtsrc}" "${rtdst}"
+ log_test $? 0 "Routers connectivity: rt-${rtsrc} -> rt-${rtdst}"
+}
+
+check_hs_ipv6_connectivity()
+{
+ local hssrc="$1"
+ local hsdst="$2"
+ local hssrc_nsname
+
+ eval hssrc_nsname=\${$(get_hsname "${hssrc}")}
+
+ ip netns exec "${hssrc_nsname}" ping -c 1 -W "${PING_TIMEOUT_SEC}" \
+ "${IPv6_HS_NETWORK}::${hsdst}" >/dev/null 2>&1
+}
+
+check_hs_ipv4_connectivity()
+{
+ local hssrc="$1"
+ local hsdst="$2"
+ local hssrc_nsname
+
+ eval hssrc_nsname=\${$(get_hsname "${hssrc}")}
+
+ ip netns exec "${hssrc_nsname}" ping -c 1 -W "${PING_TIMEOUT_SEC}" \
+ "${IPv4_HS_NETWORK}.${hsdst}" >/dev/null 2>&1
+}
+
+check_and_log_hs2gw_connectivity()
+{
+ local hssrc="$1"
+
+ check_hs_ipv6_connectivity "${hssrc}" 254
+ log_test $? 0 "IPv6 Hosts connectivity: hs-${hssrc} -> gw"
+
+ check_hs_ipv4_connectivity "${hssrc}" 254
+ log_test $? 0 "IPv4 Hosts connectivity: hs-${hssrc} -> gw"
+}
+
+check_and_log_hs_ipv6_connectivity()
+{
+ local hssrc="$1"
+ local hsdst="$2"
+
+ check_hs_ipv6_connectivity "${hssrc}" "${hsdst}"
+ log_test $? 0 "IPv6 Hosts connectivity: hs-${hssrc} -> hs-${hsdst}"
+}
+
+check_and_log_hs_ipv4_connectivity()
+{
+ local hssrc="$1"
+ local hsdst="$2"
+
+ check_hs_ipv4_connectivity "${hssrc}" "${hsdst}"
+ log_test $? 0 "IPv4 Hosts connectivity: hs-${hssrc} -> hs-${hsdst}"
+}
+
+check_and_log_hs_connectivity()
+{
+ local hssrc="$1"
+ local hsdst="$2"
+
+ check_and_log_hs_ipv4_connectivity "${hssrc}" "${hsdst}"
+ check_and_log_hs_ipv6_connectivity "${hssrc}" "${hsdst}"
+}
+
+router_tests()
+{
+ local i
+ local j
+
+ log_section "IPv6 routers connectivity test"
+
+ for i in ${ROUTERS}; do
+ for j in ${ROUTERS}; do
+ if [ "${i}" -eq "${j}" ]; then
+ continue
+ fi
+
+ check_and_log_rt_connectivity "${i}" "${j}"
+ done
+ done
+}
+
+host2gateway_tests()
+{
+ local hs
+
+ log_section "IPv4/IPv6 connectivity test among hosts and gateways"
+
+ for hs in ${HOSTS}; do
+ check_and_log_hs2gw_connectivity "${hs}"
+ done
+}
+
+host_vpn_tests()
+{
+ log_section "SRv6 srl2 + End.DT2U L2 VPN connectivity test hosts (h1 <-> h2)"
+
+ check_and_log_hs_connectivity 1 2
+ check_and_log_hs_connectivity 2 1
+}
+
+test_dummy_dev_or_ksft_skip()
+{
+ local test_netns
+
+ test_netns="dummy-$(mktemp -u XXXXXXXX)"
+
+ if ! ip netns add "${test_netns}"; then
+ echo "SKIP: Cannot set up netns for testing dummy dev support"
+ exit "${ksft_skip}"
+ fi
+
+ modprobe dummy &>/dev/null || true
+ if ! ip -netns "${test_netns}" link \
+ add "${DUMMY_DEVNAME}" type dummy; then
+ echo "SKIP: dummy dev not supported"
+
+ ip netns del "${test_netns}"
+ exit "${ksft_skip}"
+ fi
+
+ ip netns del "${test_netns}"
+}
+
+test_srl2_dev_or_ksft_skip()
+{
+ local test_netns
+
+ test_netns="srl2-$(mktemp -u XXXXXXXX)"
+
+ if ! ip netns add "${test_netns}"; then
+ echo "SKIP: Cannot set up netns for testing srl2 dev support"
+ exit "${ksft_skip}"
+ fi
+
+ modprobe srl2 &>/dev/null || true
+ if ! ip -netns "${test_netns}" link \
+ add srl2-test type srl2 \
+ segs fc00::1; then
+ echo "SKIP: srl2 dev not supported"
+
+ ip netns del "${test_netns}"
+ exit "${ksft_skip}"
+ fi
+
+ ip netns del "${test_netns}"
+}
+
+test_iproute2_supp_or_ksft_skip()
+{
+ if ! ip route help 2>&1 | grep -qo "End.DT2U"; then
+ echo "SKIP: Missing SRv6 End.DT2U support in iproute2"
+ exit "${ksft_skip}"
+ fi
+
+ if ! ip link help srl2 2>&1 | grep -qo "srl2"; then
+ echo "SKIP: Missing srl2 link type support in iproute2"
+ exit "${ksft_skip}"
+ fi
+}
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo "SKIP: Need root privileges"
+ exit "${ksft_skip}"
+fi
+
+# required programs to carry out this selftest
+test_command_or_ksft_skip ip
+test_command_or_ksft_skip ping
+test_command_or_ksft_skip sysctl
+test_command_or_ksft_skip grep
+
+test_iproute2_supp_or_ksft_skip
+test_dummy_dev_or_ksft_skip
+test_srl2_dev_or_ksft_skip
+
+set -e
+trap cleanup EXIT
+
+setup
+set +e
+
+router_tests
+host2gateway_tests
+host_vpn_tests
+
+print_log_test_results
--
2.20.1
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device
2026-03-22 0:05 [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device Andrea Mayer
` (2 preceding siblings ...)
2026-03-22 0:05 ` [RFC PATCH net-next 3/3] selftests: seg6: add SRv6 srl2 + End.DT2U L2 VPN test Andrea Mayer
@ 2026-03-24 16:00 ` Justin Iurman
2026-03-25 7:10 ` Stefano Salsano
2026-03-26 16:32 ` Nicolas Dichtel
4 siblings, 1 reply; 15+ messages in thread
From: Justin Iurman @ 2026-03-24 16:00 UTC (permalink / raw)
To: Andrea Mayer
Cc: netdev, David S . Miller, David Ahern, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Stefano Salsano,
Paolo Lungaroni, Ahmed Abdelsalam, linux-kernel
On Sun, Mar 22, 2026 at 12:06 AM Andrea Mayer <andrea.mayer@uniroma2.it> wrote:
>
> Hi all,
>
> this RFC series adds support for the SRv6 End.DT2U behavior and
> introduces the srl2 Ethernet pseudowire device, together with
> corresponding selftests.
Hi Andrea,
Thanks for the work you (and Stefano) put into this series! As
discussed offline, there is interest in such a solution.
A general comment: I'm not sure about the name "srl2", as we're
talking about SRv6 specifically here. I would personally love to have
"l2srv6", although I suspect we may end up with "l2seg6" to remain
consistent.
Cheers,
Justin
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2)
2026-03-22 0:05 ` [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2) Andrea Mayer
@ 2026-03-24 16:08 ` Justin Iurman
2026-03-24 16:24 ` Justin Iurman
2026-03-25 13:43 ` Justin Iurman
2026-03-26 16:44 ` Nicolas Dichtel
2 siblings, 1 reply; 15+ messages in thread
From: Justin Iurman @ 2026-03-24 16:08 UTC (permalink / raw)
To: Andrea Mayer
Cc: netdev, David S . Miller, David Ahern, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Stefano Salsano,
Paolo Lungaroni, Ahmed Abdelsalam, linux-kernel
On Sun, Mar 22, 2026 at 12:06 AM Andrea Mayer <andrea.mayer@uniroma2.it> wrote:
>
> Introduce srl2, an Ethernet pseudowire device over SRv6. It
> encapsulates L2 frames in IPv6 with a Segment Routing Header for
> transmission across an SRv6 network.
>
> The encapsulation logic reuses seg6_do_srh_encap() with
> IPPROTO_ETHERNET. The transmit path uses the standard IPv6 tunnel
> infrastructure (dst_cache, ip6_route_output, ip6tunnel_xmit).
>
> The device is configured with a segment list for point-to-point
> L2 encapsulation.
>
> Usage:
>
> ip link add srl2-0 type srl2 segs fc00::a,fc00::b
>
> Co-developed-by: Stefano Salsano <stefano.salsano@uniroma2.it>
> Signed-off-by: Stefano Salsano <stefano.salsano@uniroma2.it>
> Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
> ---
> include/linux/srl2.h | 7 +
> include/uapi/linux/srl2.h | 20 +++
> net/ipv6/Kconfig | 16 +++
> net/ipv6/Makefile | 1 +
> net/ipv6/seg6.c | 1 +
> net/ipv6/srl2.c | 269 ++++++++++++++++++++++++++++++++++++++
> 6 files changed, 314 insertions(+)
> create mode 100644 include/linux/srl2.h
> create mode 100644 include/uapi/linux/srl2.h
> create mode 100644 net/ipv6/srl2.c
>
[snip]
> diff --git a/net/ipv6/srl2.c b/net/ipv6/srl2.c
> new file mode 100644
> index 000000000000..66aa5375d218
> --- /dev/null
> +++ b/net/ipv6/srl2.c
> @@ -0,0 +1,269 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * SRv6 L2 tunnel device (srl2)
> + *
> + * A virtual Ethernet device that encapsulates L2 frames in IPv6 with a
> + * Segment Routing Header (SRH) for transmission over an SRv6 network.
> + * On the remote side, a seg6_local behavior such as End.DT2U or End.DX2
> + * decapsulates the inner Ethernet frame for L2 delivery.
> + *
> + * The encapsulation logic reuses seg6_do_srh_encap() from seg6_iptunnel.c
> + * with IPPROTO_ETHERNET (143). The transmit path uses the standard IPv6
> + * tunnel infrastructure (dst_cache, ip6_route_output, ip6tunnel_xmit).
> + *
> + * Authors:
> + * Andrea Mayer <andrea.mayer@uniroma2.it>
> + * Stefano Salsano <stefano.salsano@uniroma2.it>
> + */
> +
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/etherdevice.h>
> +#include <net/dst_cache.h>
> +#include <net/ip6_route.h>
> +#include <net/ip_tunnels.h>
> +#include <net/ip6_tunnel.h>
> +#include <net/seg6.h>
> +#include <linux/seg6.h>
> +#include <linux/srl2.h>
> +
> +/* Conservative initial estimate for SRH size before newlink provides
> + * the actual value. 256 bytes accommodates up to 15 SIDs.
> + */
> +#define SRL2_SRH_HEADROOM_EST 256
> +
> +struct srl2_priv {
> + struct ipv6_sr_hdr *srh;
> + struct dst_cache dst_cache;
> +};
> +
> +/*
> + * srl2_xmit - encapsulate an L2 frame in IPv6+SRH and transmit
> + *
> + * When the bridge (or local stack) sends a frame through this device,
> + * skb->data points to the inner Ethernet header. We look up a route
> + * towards the first SID, prepend the outer IPv6+SRH via
> + * seg6_do_srh_encap(), and transmit via ip6tunnel_xmit().
> + *
> + * The route lookup result is cached per-cpu in dst_cache. Since the
> + * first SID is constant for the lifetime of the device, the cache
> + * avoids repeated route lookups in the common case.
> + */
> +static netdev_tx_t srl2_xmit(struct sk_buff *skb, struct net_device *dev)
> +{
> + struct srl2_priv *priv = netdev_priv(dev);
> + struct net *net = dev_net(dev);
> + struct dst_entry *dst;
> + struct flowi6 fl6;
> + int err;
> +
> + local_bh_disable();
> + dst = dst_cache_get(&priv->dst_cache);
> + local_bh_enable();
> +
> + if (unlikely(!dst)) {
> + memset(&fl6, 0, sizeof(fl6));
> + fl6.daddr = priv->srh->segments[priv->srh->first_segment];
> +
> + dst = ip6_route_output(net, NULL, &fl6);
> + if (dst->error) {
> + dst_release(dst);
> + DEV_STATS_INC(dev, tx_carrier_errors);
> + goto drop;
> + }
> +
> + if (dst_dev(dst) == dev) {
> + dst_release(dst);
> + DEV_STATS_INC(dev, collisions);
> + goto drop;
> + }
> +
> + local_bh_disable();
> + /* saddr is unused */
> + dst_cache_set_ip6(&priv->dst_cache, dst, &fl6.saddr);
> + local_bh_enable();
> + }
> +
> + skb_scrub_packet(skb, false);
> +
> + skb_dst_set(skb, dst);
> +
> + err = seg6_do_srh_encap(skb, priv->srh, IPPROTO_ETHERNET);
We shouldn't be reusing seg6_do_srh_encap() as it also manages its own
lwt dst_cache. There's probably a need to rework that part to avoid
code duplication.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2)
2026-03-24 16:08 ` Justin Iurman
@ 2026-03-24 16:24 ` Justin Iurman
0 siblings, 0 replies; 15+ messages in thread
From: Justin Iurman @ 2026-03-24 16:24 UTC (permalink / raw)
To: Andrea Mayer
Cc: netdev, David S . Miller, David Ahern, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Stefano Salsano,
Paolo Lungaroni, Ahmed Abdelsalam, linux-kernel
On Tue, Mar 24, 2026 at 4:08 PM Justin Iurman <justin.iurman@6wind.com> wrote:
>
> On Sun, Mar 22, 2026 at 12:06 AM Andrea Mayer <andrea.mayer@uniroma2.it> wrote:
> >
> > Introduce srl2, an Ethernet pseudowire device over SRv6. It
> > encapsulates L2 frames in IPv6 with a Segment Routing Header for
> > transmission across an SRv6 network.
> >
> > The encapsulation logic reuses seg6_do_srh_encap() with
> > IPPROTO_ETHERNET. The transmit path uses the standard IPv6 tunnel
> > infrastructure (dst_cache, ip6_route_output, ip6tunnel_xmit).
> >
> > The device is configured with a segment list for point-to-point
> > L2 encapsulation.
> >
> > Usage:
> >
> > ip link add srl2-0 type srl2 segs fc00::a,fc00::b
> >
> > Co-developed-by: Stefano Salsano <stefano.salsano@uniroma2.it>
> > Signed-off-by: Stefano Salsano <stefano.salsano@uniroma2.it>
> > Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
> > ---
> > include/linux/srl2.h | 7 +
> > include/uapi/linux/srl2.h | 20 +++
> > net/ipv6/Kconfig | 16 +++
> > net/ipv6/Makefile | 1 +
> > net/ipv6/seg6.c | 1 +
> > net/ipv6/srl2.c | 269 ++++++++++++++++++++++++++++++++++++++
> > 6 files changed, 314 insertions(+)
> > create mode 100644 include/linux/srl2.h
> > create mode 100644 include/uapi/linux/srl2.h
> > create mode 100644 net/ipv6/srl2.c
> >
>
> [snip]
>
> > diff --git a/net/ipv6/srl2.c b/net/ipv6/srl2.c
> > new file mode 100644
> > index 000000000000..66aa5375d218
> > --- /dev/null
> > +++ b/net/ipv6/srl2.c
> > @@ -0,0 +1,269 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * SRv6 L2 tunnel device (srl2)
> > + *
> > + * A virtual Ethernet device that encapsulates L2 frames in IPv6 with a
> > + * Segment Routing Header (SRH) for transmission over an SRv6 network.
> > + * On the remote side, a seg6_local behavior such as End.DT2U or End.DX2
> > + * decapsulates the inner Ethernet frame for L2 delivery.
> > + *
> > + * The encapsulation logic reuses seg6_do_srh_encap() from seg6_iptunnel.c
> > + * with IPPROTO_ETHERNET (143). The transmit path uses the standard IPv6
> > + * tunnel infrastructure (dst_cache, ip6_route_output, ip6tunnel_xmit).
> > + *
> > + * Authors:
> > + * Andrea Mayer <andrea.mayer@uniroma2.it>
> > + * Stefano Salsano <stefano.salsano@uniroma2.it>
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/netdevice.h>
> > +#include <linux/etherdevice.h>
> > +#include <net/dst_cache.h>
> > +#include <net/ip6_route.h>
> > +#include <net/ip_tunnels.h>
> > +#include <net/ip6_tunnel.h>
> > +#include <net/seg6.h>
> > +#include <linux/seg6.h>
> > +#include <linux/srl2.h>
> > +
> > +/* Conservative initial estimate for SRH size before newlink provides
> > + * the actual value. 256 bytes accommodates up to 15 SIDs.
> > + */
> > +#define SRL2_SRH_HEADROOM_EST 256
> > +
> > +struct srl2_priv {
> > + struct ipv6_sr_hdr *srh;
> > + struct dst_cache dst_cache;
> > +};
> > +
> > +/*
> > + * srl2_xmit - encapsulate an L2 frame in IPv6+SRH and transmit
> > + *
> > + * When the bridge (or local stack) sends a frame through this device,
> > + * skb->data points to the inner Ethernet header. We look up a route
> > + * towards the first SID, prepend the outer IPv6+SRH via
> > + * seg6_do_srh_encap(), and transmit via ip6tunnel_xmit().
> > + *
> > + * The route lookup result is cached per-cpu in dst_cache. Since the
> > + * first SID is constant for the lifetime of the device, the cache
> > + * avoids repeated route lookups in the common case.
> > + */
> > +static netdev_tx_t srl2_xmit(struct sk_buff *skb, struct net_device *dev)
> > +{
> > + struct srl2_priv *priv = netdev_priv(dev);
> > + struct net *net = dev_net(dev);
> > + struct dst_entry *dst;
> > + struct flowi6 fl6;
> > + int err;
> > +
> > + local_bh_disable();
> > + dst = dst_cache_get(&priv->dst_cache);
> > + local_bh_enable();
> > +
> > + if (unlikely(!dst)) {
> > + memset(&fl6, 0, sizeof(fl6));
> > + fl6.daddr = priv->srh->segments[priv->srh->first_segment];
> > +
> > + dst = ip6_route_output(net, NULL, &fl6);
> > + if (dst->error) {
> > + dst_release(dst);
> > + DEV_STATS_INC(dev, tx_carrier_errors);
> > + goto drop;
> > + }
> > +
> > + if (dst_dev(dst) == dev) {
> > + dst_release(dst);
> > + DEV_STATS_INC(dev, collisions);
> > + goto drop;
> > + }
> > +
> > + local_bh_disable();
> > + /* saddr is unused */
> > + dst_cache_set_ip6(&priv->dst_cache, dst, &fl6.saddr);
> > + local_bh_enable();
> > + }
> > +
> > + skb_scrub_packet(skb, false);
> > +
> > + skb_dst_set(skb, dst);
> > +
> > + err = seg6_do_srh_encap(skb, priv->srh, IPPROTO_ETHERNET);
>
> We shouldn't be reusing seg6_do_srh_encap() as it also manages its own
> lwt dst_cache. There's probably a need to rework that part to avoid
> code duplication.
Never mind, it's just that dst_dev_overhead() would be called with a
default (NULL) dst_entry, so I guess we should remove the likely
annotation for perf reasons in this case. FYI, dst_dev_overhead() was
introduced to mitigate a double reallocation in skb's, but I don't
think we should worry about it in this context as it is only triggered
for much more segments (see https://arxiv.org/pdf/2503.14959, and
related commits 0600cf40e9b3 and 40475b63761a).
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device
2026-03-24 16:00 ` [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device Justin Iurman
@ 2026-03-25 7:10 ` Stefano Salsano
2026-03-25 8:35 ` Justin Iurman
2026-03-26 16:30 ` Nicolas Dichtel
0 siblings, 2 replies; 15+ messages in thread
From: Stefano Salsano @ 2026-03-25 7:10 UTC (permalink / raw)
To: Justin Iurman, Andrea Mayer
Cc: netdev, David S . Miller, David Ahern, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Paolo Lungaroni,
Ahmed Abdelsalam, linux-kernel
Il 24/03/2026 17:00, Justin Iurman ha scritto:
> On Sun, Mar 22, 2026 at 12:06 AM Andrea Mayer <andrea.mayer@uniroma2.it> wrote:
>>
>> Hi all,
>>
>> this RFC series adds support for the SRv6 End.DT2U behavior and
>> introduces the srl2 Ethernet pseudowire device, together with
>> corresponding selftests.
>
> Hi Andrea,
>
> Thanks for the work you (and Stefano) put into this series! As
> discussed offline, there is interest in such a solution.
Hi Justin,
thx for your interest!
> A general comment: I'm not sure about the name "srl2", as we're
> talking about SRv6 specifically here.
fair point
> I would personally love to have
> "l2srv6", although I suspect we may end up with "l2seg6" to remain
> consistent.
I'd like to keep it as short as possible, our initial idea was seg6l2,
very close to your last suggestion, then we opted for srl2 losing the
"6" concept
now it comes to my mind that l2 is somehow redudant in an interface type
name, as an interface is an l2 concept per se, so my preferred option
becomes:
sr6
(short and memorable...)
as an alternative seg6 can work but I strongly prefer sr6
ciao
Stefano
>
> Cheers,
> Justin
--
*******************************************************************
Prof. Stefano Salsano
Dipartimento Ingegneria Elettronica
Universita' di Roma Tor Vergata
Viale Politecnico, 1 - 00133 Roma - ITALY
http://netgroup.uniroma2.it/Stefano_Salsano/
E-mail : stefano.salsano@uniroma2.it
Office : (Tel.) +39 06 72597770 (Fax.) +39 06 72597435
*******************************************************************
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device
2026-03-25 7:10 ` Stefano Salsano
@ 2026-03-25 8:35 ` Justin Iurman
2026-03-26 16:30 ` Nicolas Dichtel
1 sibling, 0 replies; 15+ messages in thread
From: Justin Iurman @ 2026-03-25 8:35 UTC (permalink / raw)
To: Stefano Salsano
Cc: Andrea Mayer, netdev, David S . Miller, David Ahern, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Paolo Lungaroni,
Ahmed Abdelsalam, linux-kernel
On Wed, Mar 25, 2026 at 7:10 AM Stefano Salsano
<stefano.salsano@uniroma2.it> wrote:
>
> Il 24/03/2026 17:00, Justin Iurman ha scritto:
> > On Sun, Mar 22, 2026 at 12:06 AM Andrea Mayer <andrea.mayer@uniroma2.it> wrote:
> >>
> >> Hi all,
> >>
> >> this RFC series adds support for the SRv6 End.DT2U behavior and
> >> introduces the srl2 Ethernet pseudowire device, together with
> >> corresponding selftests.
> >
> > Hi Andrea,
> >
> > Thanks for the work you (and Stefano) put into this series! As
> > discussed offline, there is interest in such a solution.
>
> Hi Justin,
>
> thx for your interest!
>
> > A general comment: I'm not sure about the name "srl2", as we're
> > talking about SRv6 specifically here.
>
> fair point
>
> > I would personally love to have
> > "l2srv6", although I suspect we may end up with "l2seg6" to remain
> > consistent.
>
> I'd like to keep it as short as possible, our initial idea was seg6l2,
> very close to your last suggestion, then we opted for srl2 losing the
> "6" concept
>
> now it comes to my mind that l2 is somehow redudant in an interface type
> name, as an interface is an l2 concept per se, so my preferred option
> becomes:
>
> sr6
>
> (short and memorable...)
>
> as an alternative seg6 can work but I strongly prefer sr6
Hi Stefano,
Makes sense, both would work for me FWIW. Let's see if others have opinions.
Thanks,
Justin
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2)
2026-03-22 0:05 ` [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2) Andrea Mayer
2026-03-24 16:08 ` Justin Iurman
@ 2026-03-25 13:43 ` Justin Iurman
2026-03-26 17:29 ` Stefano Salsano
2026-03-26 16:44 ` Nicolas Dichtel
2 siblings, 1 reply; 15+ messages in thread
From: Justin Iurman @ 2026-03-25 13:43 UTC (permalink / raw)
To: Andrea Mayer
Cc: netdev, David S . Miller, David Ahern, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Stefano Salsano,
Paolo Lungaroni, Ahmed Abdelsalam, linux-kernel
On Sun, Mar 22, 2026 at 12:06 AM Andrea Mayer <andrea.mayer@uniroma2.it> wrote:
>
> Introduce srl2, an Ethernet pseudowire device over SRv6. It
> encapsulates L2 frames in IPv6 with a Segment Routing Header for
> transmission across an SRv6 network.
>
> The encapsulation logic reuses seg6_do_srh_encap() with
> IPPROTO_ETHERNET. The transmit path uses the standard IPv6 tunnel
> infrastructure (dst_cache, ip6_route_output, ip6tunnel_xmit).
>
> The device is configured with a segment list for point-to-point
> L2 encapsulation.
>
> Usage:
>
> ip link add srl2-0 type srl2 segs fc00::a,fc00::b
Thinking out loud...
We'll likely need SRv6 encap configurations specific to each entry
(i.e., MAC address), rather than (or in addition to) per interface.
We could also add a "mode" (e.g., "normal" mode or reduced mode?).
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device
2026-03-25 7:10 ` Stefano Salsano
2026-03-25 8:35 ` Justin Iurman
@ 2026-03-26 16:30 ` Nicolas Dichtel
2026-03-26 17:30 ` Stefano Salsano
1 sibling, 1 reply; 15+ messages in thread
From: Nicolas Dichtel @ 2026-03-26 16:30 UTC (permalink / raw)
To: Stefano Salsano, Justin Iurman, Andrea Mayer
Cc: netdev, David S . Miller, David Ahern, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Paolo Lungaroni,
Ahmed Abdelsalam, linux-kernel
Le 25/03/2026 à 08:10, Stefano Salsano a écrit :
> Il 24/03/2026 17:00, Justin Iurman ha scritto:
>> On Sun, Mar 22, 2026 at 12:06 AM Andrea Mayer <andrea.mayer@uniroma2.it> wrote:
>>>
>>> Hi all,
>>>
>>> this RFC series adds support for the SRv6 End.DT2U behavior and
>>> introduces the srl2 Ethernet pseudowire device, together with
>>> corresponding selftests.
>>
>> Hi Andrea,
>>
>> Thanks for the work you (and Stefano) put into this series! As
>> discussed offline, there is interest in such a solution.
>
> Hi Justin,
>
> thx for your interest!
>
>> A general comment: I'm not sure about the name "srl2", as we're
>> talking about SRv6 specifically here.
>
> fair point
>
>> I would personally love to have
>> "l2srv6", although I suspect we may end up with "l2seg6" to remain
>> consistent.
>
> I'd like to keep it as short as possible, our initial idea was seg6l2, very
> close to your last suggestion, then we opted for srl2 losing the "6" concept
>
> now it comes to my mind that l2 is somehow redudant in an interface type name,
> as an interface is an l2 concept per se, so my preferred option becomes:
>
> sr6
Note that the interface name is defined to kind + a number when the IFLA_IFNAME
attribute is not specified:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/core/rtnetlink.c?h=v7.0-rc5#n3823
Here, the default name will be 'sr60' :)
'vti6' is already in this case, so it's probably ok. I vote for 'sr6' too ;-)
My two cents,
Nicolas
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device
2026-03-22 0:05 [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device Andrea Mayer
` (3 preceding siblings ...)
2026-03-24 16:00 ` [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device Justin Iurman
@ 2026-03-26 16:32 ` Nicolas Dichtel
4 siblings, 0 replies; 15+ messages in thread
From: Nicolas Dichtel @ 2026-03-26 16:32 UTC (permalink / raw)
To: Andrea Mayer, netdev
Cc: David S . Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Stefano Salsano, Paolo Lungaroni,
Ahmed Abdelsalam, Justin Iurman, linux-kernel
Le 22/03/2026 à 01:05, Andrea Mayer a écrit :
> Hi all,
Hi,
first, thank you for posting this RFC.
>
> this RFC series adds support for the SRv6 End.DT2U behavior and
> introduces the srl2 Ethernet pseudowire device, together with
> corresponding selftests.
>
> Motivation
> ==========
>
> The main goal is to enable L2 service delivery through an internal
> SRv6 L2 endpoint, in a way similar to how VXLAN provides L2 services
> through a VXLAN netdevice.
>
> Today, Linux supports SRv6 L2 decapsulation only through an
> End.DX2-style model, where the exposed Ethernet frame is forwarded
> to a configured egress interface. The current implementation does
> not provide a native netdevice representing the SRv6 L2 service
> endpoint. This is a significant limitation compared to VXLAN, where
> the tunnel endpoint is represented by a netdevice that can be
> attached to a bridge and integrated naturally into the local L2
> data plane.
>
> On the encapsulation side, the existing H.L2.Encaps mode is
> implemented as a route-based lwtunnel, which only intercepts packets
> entering the IP routing path. Non-IP protocols such as ARP are
> never routed and therefore cannot be encapsulated, making it
> unsuitable for full L2 service delivery.
>
> Design
> ======
>
> This series addresses that limitation by introducing srl2, a native
> SRv6 L2 endpoint interface. The intent is to make SRv6 L2 delivery
> usable in the same deployment model already familiar from VXLAN: an
> internal interface that represents the L2 service endpoint and can
> participate naturally in local L2 forwarding.
The current design, with vxlan, uses two interfaces: one bridge and one vxlan.
I presume this design was chosen because the vxlan protocol is implemented in
the vxlan driver.
I wonder if using the same design for SRv6 is the best choice. Why not use only
a bridge interface? Instead of pointing to a port, the FDB entries could point
to a nexthop id. This enables to associate a srv6 encap directly to a MAC
address. Something like:
$ ip nexthop list id 1234
id 1234 encap seg6 mode encap segs ...
$ bridge fdb show dev br0
02:03:04:05:06:07 dev br0 nhid 1234 ...
What is the gain of having two interfaces?
In term of scalability, it is interesting to have only one interface.
Regards,
Nicolas
>
> On top of that, the series adds support for End.DT2U so that Ethernet
> payloads carried over SRv6 can be decapsulated and delivered into an
> L2 domain through either a bridge port or an srl2 interface.
>
> Relation to RFC 8986
> ====================
>
> RFC 8986 describes End.DT2U in terms of an abstract L2 table
> associated with the SID. That abstraction specifies the externally
> observable forwarding behavior of the node, but it does not mandate a
> specific internal implementation. What matters is functional
> equivalence.
>
> This series enforces that property by restricting valid End.DT2U
> decapsulation targets to endpoints that provide the required L2
> forwarding semantics by construction.
>
> When the target device is a bridge port, the Linux bridge provides
> the expected behavior directly: source MAC learning, destination MAC
> lookup, and unknown-unicast flooding within the corresponding L2
> domain.
>
> When the target device is an srl2 interface, the same model is
> realized in a degenerate but valid form, corresponding to a minimal
> two-port L2 domain in which one endpoint is srl2. In that case,
> forwarding is trivial: frames are delivered to srl2 only when
> addressed to its MAC address, or when they are broadcast or multicast.
> No general FDB is required in this case because there are no
> additional L2 destinations to learn.
>
> Any other type of decapsulation target is rejected at configuration
> time, since it would not guarantee the forwarding behavior required by
> End.DT2U.
>
> The srl2 device is the minimal baseline implementation providing
> point-to-point L2 encapsulation with a single segment list.
> Additional features can be added incrementally based on community
> feedback and use cases.
>
> Usage
> =====
>
> ip link add srl2-0 type srl2 segs fc00::a,fc00::b
> ip link set srl2-0 master br0
>
> ip -6 route add fc00::100/128 encap seg6local action End.DT2U \
> l2dev srl2-0 dev dum0
>
> # encapsulating traffic from veth-hs into srl2-0
> ip link set veth-hs master br0
>
> Selftest topology
> =================
>
> cafe::1 cafe::2
> 10.0.0.1 10.0.0.2
> +--------+ +--------+
> | hs-1 | | hs-2 |
> +---+----+ +----+---+
> | |
> +-----+------+ +------+-----+
> | veth-hs | | veth-hs |
> | | | fcf0:0:1:2::/64 | | |
> | br0 +-------------------------+ br0 |
> | | | | | |
> | srl2-0 | | srl2-0 |
> | rt-1 | | rt-2 |
> +------------+ +------------+
>
> The test verifies IPv4/IPv6 host-to-host and host-to-gateway
> connectivity over the SRv6 L2 VPN, including ARP resolution
> through the tunnel.
>
> A companion iproute2 series provides userspace support:
>
> [RFC PATCH iproute2-next 0/4] seg6: add SRv6 End.DT2U and srl2 support
>
>
> This is an RFC posting to collect feedback on both the overall design
> and the userspace API before moving forward with a formal submission.
>
> Comments are very welcome, especially on:
> - the introduction of srl2 as an internal SRv6 L2 endpoint
> - the choice of constraining End.DT2U targets to bridge ports
> and srl2 devices
> - the netlink/uAPI exposure
> - the selftest coverage and topology
>
> Thanks,
> Andrea and Stefano
>
>
> Andrea Mayer (3):
> seg6: add support for the SRv6 End.DT2U behavior
> seg6: add SRv6 L2 tunnel device (srl2)
> selftests: seg6: add SRv6 srl2 + End.DT2U L2 VPN test
>
> include/linux/srl2.h | 7 +
> include/uapi/linux/seg6_local.h | 3 +
> include/uapi/linux/srl2.h | 20 +
> net/ipv6/Kconfig | 16 +
> net/ipv6/Makefile | 1 +
> net/ipv6/seg6.c | 1 +
> net/ipv6/seg6_local.c | 160 ++++-
> net/ipv6/srl2.c | 269 ++++++++
> tools/testing/selftests/net/Makefile | 1 +
> tools/testing/selftests/net/config | 1 +
> .../selftests/net/srv6_srl2_l2vpn_test.sh | 621 ++++++++++++++++++
> 11 files changed, 1094 insertions(+), 6 deletions(-)
> create mode 100644 include/linux/srl2.h
> create mode 100644 include/uapi/linux/srl2.h
> create mode 100644 net/ipv6/srl2.c
> create mode 100755 tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2)
2026-03-22 0:05 ` [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2) Andrea Mayer
2026-03-24 16:08 ` Justin Iurman
2026-03-25 13:43 ` Justin Iurman
@ 2026-03-26 16:44 ` Nicolas Dichtel
2 siblings, 0 replies; 15+ messages in thread
From: Nicolas Dichtel @ 2026-03-26 16:44 UTC (permalink / raw)
To: Andrea Mayer, netdev
Cc: David S . Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Stefano Salsano, Paolo Lungaroni,
Ahmed Abdelsalam, Justin Iurman, linux-kernel
Le 22/03/2026 à 01:05, Andrea Mayer a écrit :
> Introduce srl2, an Ethernet pseudowire device over SRv6. It
> encapsulates L2 frames in IPv6 with a Segment Routing Header for
> transmission across an SRv6 network.
>
> The encapsulation logic reuses seg6_do_srh_encap() with
> IPPROTO_ETHERNET. The transmit path uses the standard IPv6 tunnel
> infrastructure (dst_cache, ip6_route_output, ip6tunnel_xmit).
>
> The device is configured with a segment list for point-to-point
> L2 encapsulation.
>
> Usage:
>
> ip link add srl2-0 type srl2 segs fc00::a,fc00::b
>
> Co-developed-by: Stefano Salsano <stefano.salsano@uniroma2.it>
> Signed-off-by: Stefano Salsano <stefano.salsano@uniroma2.it>
> Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
> ---
> include/linux/srl2.h | 7 +
> include/uapi/linux/srl2.h | 20 +++
> net/ipv6/Kconfig | 16 +++
> net/ipv6/Makefile | 1 +
> net/ipv6/seg6.c | 1 +
> net/ipv6/srl2.c | 269 ++++++++++++++++++++++++++++++++++++++
> 6 files changed, 314 insertions(+)
> create mode 100644 include/linux/srl2.h
> create mode 100644 include/uapi/linux/srl2.h
> create mode 100644 net/ipv6/srl2.c
>
> diff --git a/include/linux/srl2.h b/include/linux/srl2.h
> new file mode 100644
> index 000000000000..c1342b979402
> --- /dev/null
> +++ b/include/linux/srl2.h
> @@ -0,0 +1,7 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +#ifndef _LINUX_SRL2_H
> +#define _LINUX_SRL2_H
> +
> +#include <uapi/linux/srl2.h>
> +
> +#endif
Is this really needed?
> diff --git a/include/uapi/linux/srl2.h b/include/uapi/linux/srl2.h
> new file mode 100644
> index 000000000000..e7c8f6fc0791
> --- /dev/null
> +++ b/include/uapi/linux/srl2.h
> @@ -0,0 +1,20 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */
> +/*
> + * SRv6 L2 tunnel device
> + *
> + * Author:
> + * Andrea Mayer <andrea.mayer@uniroma2.it>
> + */
> +
> +#ifndef _UAPI_LINUX_SRL2_H
> +#define _UAPI_LINUX_SRL2_H
> +
> +enum {
> + IFLA_SRL2_UNSPEC,
> + IFLA_SRL2_SRH, /* binary: struct ipv6_sr_hdr + segments */
> + __IFLA_SRL2_MAX,
> +};
> +
> +#define IFLA_SRL2_MAX (__IFLA_SRL2_MAX - 1)
It should probably be generated automatically from specs, see
https://docs.kernel.org/userspace-api/netlink/intro-specs.html
> +
> +#endif
> diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
> index b8f9a8c0302e..9c8f7e254435 100644
> --- a/net/ipv6/Kconfig
> +++ b/net/ipv6/Kconfig
> @@ -318,6 +318,22 @@ config IPV6_SEG6_BPF
> depends on IPV6_SEG6_LWTUNNEL
> depends on IPV6 = y
>
> +config IPV6_SRL2
> + tristate "IPv6: SRv6 L2 tunnel device"
> + depends on IPV6_SEG6_LWTUNNEL
> + select DST_CACHE
> + help
> + SRv6 virtual Ethernet device that encapsulates L2 frames in
> + IPv6 with a Segment Routing Header (SRH) for transmission
> + over an SRv6 network.
> + Intended for use with a remote seg6local L2 decapsulation
> + behavior, such as End.DT2U or End.DX2.
> +
> + To compile this as a module, choose M here: the module will
> + be called srl2.
> +
> + If unsure, say N.
> +
> config IPV6_RPL_LWTUNNEL
> bool "IPv6: RPL Source Routing Header support"
> depends on IPV6
> diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile
> index 2c9ce2ccbde1..a7e81d0293ca 100644
> --- a/net/ipv6/Makefile
> +++ b/net/ipv6/Makefile
> @@ -24,6 +24,7 @@ ipv6-$(CONFIG_SYN_COOKIES) += syncookies.o
> ipv6-$(CONFIG_NETLABEL) += calipso.o
> ipv6-$(CONFIG_IPV6_SEG6_LWTUNNEL) += seg6_iptunnel.o seg6_local.o
> ipv6-$(CONFIG_IPV6_SEG6_HMAC) += seg6_hmac.o
> +obj-$(CONFIG_IPV6_SRL2) += srl2.o
> ipv6-$(CONFIG_IPV6_RPL_LWTUNNEL) += rpl_iptunnel.o
> ipv6-$(CONFIG_IPV6_IOAM6_LWTUNNEL) += ioam6_iptunnel.o
>
> diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c
> index 1c3ad25700c4..23213ab4fefd 100644
> --- a/net/ipv6/seg6.c
> +++ b/net/ipv6/seg6.c
> @@ -72,6 +72,7 @@ bool seg6_validate_srh(struct ipv6_sr_hdr *srh, int len, bool reduced)
>
> return true;
> }
> +EXPORT_SYMBOL_GPL(seg6_validate_srh);
>
> struct ipv6_sr_hdr *seg6_get_srh(struct sk_buff *skb, int flags)
> {
> diff --git a/net/ipv6/srl2.c b/net/ipv6/srl2.c
> new file mode 100644
> index 000000000000..66aa5375d218
> --- /dev/null
> +++ b/net/ipv6/srl2.c
> @@ -0,0 +1,269 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * SRv6 L2 tunnel device (srl2)
> + *
> + * A virtual Ethernet device that encapsulates L2 frames in IPv6 with a
> + * Segment Routing Header (SRH) for transmission over an SRv6 network.
> + * On the remote side, a seg6_local behavior such as End.DT2U or End.DX2
> + * decapsulates the inner Ethernet frame for L2 delivery.
> + *
> + * The encapsulation logic reuses seg6_do_srh_encap() from seg6_iptunnel.c
> + * with IPPROTO_ETHERNET (143). The transmit path uses the standard IPv6
> + * tunnel infrastructure (dst_cache, ip6_route_output, ip6tunnel_xmit).
> + *
> + * Authors:
> + * Andrea Mayer <andrea.mayer@uniroma2.it>
> + * Stefano Salsano <stefano.salsano@uniroma2.it>
> + */
> +
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/etherdevice.h>
> +#include <net/dst_cache.h>
> +#include <net/ip6_route.h>
> +#include <net/ip_tunnels.h>
> +#include <net/ip6_tunnel.h>
> +#include <net/seg6.h>
> +#include <linux/seg6.h>
> +#include <linux/srl2.h>
> +
> +/* Conservative initial estimate for SRH size before newlink provides
> + * the actual value. 256 bytes accommodates up to 15 SIDs.
> + */
> +#define SRL2_SRH_HEADROOM_EST 256
> +
> +struct srl2_priv {
> + struct ipv6_sr_hdr *srh;
> + struct dst_cache dst_cache;
> +};
> +
> +/*
> + * srl2_xmit - encapsulate an L2 frame in IPv6+SRH and transmit
> + *
> + * When the bridge (or local stack) sends a frame through this device,
> + * skb->data points to the inner Ethernet header. We look up a route
> + * towards the first SID, prepend the outer IPv6+SRH via
> + * seg6_do_srh_encap(), and transmit via ip6tunnel_xmit().
> + *
> + * The route lookup result is cached per-cpu in dst_cache. Since the
> + * first SID is constant for the lifetime of the device, the cache
> + * avoids repeated route lookups in the common case.
> + */
> +static netdev_tx_t srl2_xmit(struct sk_buff *skb, struct net_device *dev)
> +{
> + struct srl2_priv *priv = netdev_priv(dev);
> + struct net *net = dev_net(dev);
> + struct dst_entry *dst;
> + struct flowi6 fl6;
> + int err;
> +
> + local_bh_disable();
> + dst = dst_cache_get(&priv->dst_cache);
> + local_bh_enable();
> +
> + if (unlikely(!dst)) {
> + memset(&fl6, 0, sizeof(fl6));
> + fl6.daddr = priv->srh->segments[priv->srh->first_segment];
> +
> + dst = ip6_route_output(net, NULL, &fl6);
> + if (dst->error) {
> + dst_release(dst);
> + DEV_STATS_INC(dev, tx_carrier_errors);
> + goto drop;
> + }
> +
> + if (dst_dev(dst) == dev) {
> + dst_release(dst);
> + DEV_STATS_INC(dev, collisions);
> + goto drop;
> + }
> +
> + local_bh_disable();
> + /* saddr is unused */
> + dst_cache_set_ip6(&priv->dst_cache, dst, &fl6.saddr);
> + local_bh_enable();
> + }
> +
> + skb_scrub_packet(skb, false);
> +
> + skb_dst_set(skb, dst);
> +
> + err = seg6_do_srh_encap(skb, priv->srh, IPPROTO_ETHERNET);
> + if (unlikely(err)) {
> + DEV_STATS_INC(dev, tx_errors);
> + kfree_skb(skb);
> + return NETDEV_TX_OK;
> + }
> +
> + skb->protocol = htons(ETH_P_IPV6);
> +
> + ip6tunnel_xmit(NULL, skb, dev, 0);
> +
> + return NETDEV_TX_OK;
> +
> +drop:
> + DEV_STATS_INC(dev, tx_dropped);
> + kfree_skb(skb);
> + return NETDEV_TX_OK;
> +}
> +
> +static int srl2_dev_init(struct net_device *dev)
> +{
> + struct srl2_priv *priv = netdev_priv(dev);
> +
> + return dst_cache_init(&priv->dst_cache, GFP_KERNEL);
> +}
> +
> +static void srl2_dev_uninit(struct net_device *dev)
> +{
> + struct srl2_priv *priv = netdev_priv(dev);
> +
> + dst_cache_destroy(&priv->dst_cache);
> +}
> +
> +static void srl2_dev_free(struct net_device *dev)
> +{
> + struct srl2_priv *priv = netdev_priv(dev);
> +
> + kfree(priv->srh);
> +}
> +
> +static const struct net_device_ops srl2_netdev_ops = {
> + .ndo_init = srl2_dev_init,
> + .ndo_uninit = srl2_dev_uninit,
> + .ndo_start_xmit = srl2_xmit,
> + .ndo_set_mac_address = eth_mac_addr,
> + .ndo_validate_addr = eth_validate_addr,
> +};
> +
> +static void srl2_setup(struct net_device *dev)
> +{
> + ether_setup(dev);
> +
> + dev->netdev_ops = &srl2_netdev_ops;
> + dev->needs_free_netdev = true;
> + dev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;
> + dev->needed_headroom = LL_MAX_HEADER + sizeof(struct ipv6hdr) +
> + SRL2_SRH_HEADROOM_EST;
> +
> + dev->priv_flags &= ~IFF_TX_SKB_SHARING;
> + dev->priv_flags |= IFF_LIVE_ADDR_CHANGE | IFF_NO_QUEUE;
> + dev->lltx = true;
> +
Maybe setting dev->netns_immutable to true ?
Regards,
Nicolas
> + eth_hw_addr_random(dev);
> +}
> +
> +static const struct nla_policy srl2_policy[IFLA_SRL2_MAX + 1] = {
> + [IFLA_SRL2_SRH] = { .type = NLA_BINARY },
> +};
> +
> +static int srl2_validate(struct nlattr *tb[], struct nlattr *data[],
> + struct netlink_ext_ack *extack)
> +{
> + if (!data || !data[IFLA_SRL2_SRH]) {
> + NL_SET_ERR_MSG(extack, "SRH with segment list is required");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int srl2_newlink(struct net_device *dev,
> + struct rtnl_newlink_params *params,
> + struct netlink_ext_ack *extack)
> +{
> + struct srl2_priv *priv = netdev_priv(dev);
> + struct nlattr **data = params->data;
> + struct ipv6_sr_hdr *srh;
> + int srhlen;
> + int len;
> +
> + srh = nla_data(data[IFLA_SRL2_SRH]);
> + len = nla_len(data[IFLA_SRL2_SRH]);
> +
> + if (len < sizeof(*srh) + sizeof(struct in6_addr)) {
> + NL_SET_ERR_MSG(extack, "SRH too short");
> + return -EINVAL;
> + }
> +
> + if (!seg6_validate_srh(srh, len, false)) {
> + NL_SET_ERR_MSG(extack, "Invalid SRH");
> + return -EINVAL;
> + }
> +
> + priv->srh = kmemdup(srh, len, GFP_KERNEL);
> + if (!priv->srh)
> + return -ENOMEM;
> +
> + srhlen = ipv6_optlen(srh);
> +
> + dev->needed_headroom = LL_MAX_HEADER + sizeof(struct ipv6hdr) + srhlen;
> +
> + /* dev->mtu is the inner L3 payload size. Since SRv6 encapsulation
> + * carries the full inner Ethernet frame, subtract both the outer
> + * IPv6+SRH overhead and ETH_HLEN from ETH_DATA_LEN.
> + */
> + dev->mtu = ETH_DATA_LEN - sizeof(struct ipv6hdr) - srhlen - ETH_HLEN;
> + dev->min_mtu = ETH_MIN_MTU;
> + dev->max_mtu = IP_MAX_MTU - sizeof(struct ipv6hdr) - srhlen - ETH_HLEN;
> +
> + dev->priv_destructor = srl2_dev_free;
> +
> + return register_netdevice(dev);
> +}
> +
> +static void srl2_dellink(struct net_device *dev, struct list_head *head)
> +{
> + unregister_netdevice_queue(dev, head);
> +}
> +
> +static size_t srl2_get_size(const struct net_device *dev)
> +{
> + const struct srl2_priv *priv = netdev_priv(dev);
> + int srhlen = ipv6_optlen(priv->srh);
> +
> + return nla_total_size(srhlen);
> +}
> +
> +static int srl2_fill_info(struct sk_buff *skb, const struct net_device *dev)
> +{
> + const struct srl2_priv *priv = netdev_priv(dev);
> + int srhlen = ipv6_optlen(priv->srh);
> +
> + if (nla_put(skb, IFLA_SRL2_SRH, srhlen, priv->srh))
> + return -EMSGSIZE;
> +
> + return 0;
> +}
> +
> +static struct rtnl_link_ops srl2_link_ops __read_mostly = {
> + .kind = "srl2",
> + .maxtype = IFLA_SRL2_MAX,
> + .policy = srl2_policy,
> + .priv_size = sizeof(struct srl2_priv),
> + .setup = srl2_setup,
> + .validate = srl2_validate,
> + .newlink = srl2_newlink,
> + .dellink = srl2_dellink,
> + .get_size = srl2_get_size,
> + .fill_info = srl2_fill_info,
> +};
> +
> +static int __init srl2_init(void)
> +{
> + return rtnl_link_register(&srl2_link_ops);
> +}
> +
> +static void __exit srl2_exit(void)
> +{
> + rtnl_link_unregister(&srl2_link_ops);
> +}
> +
> +module_init(srl2_init);
> +module_exit(srl2_exit);
> +
> +MODULE_AUTHOR("Andrea Mayer <andrea.mayer@uniroma2.it>");
> +MODULE_AUTHOR("Stefano Salsano <stefano.salsano@uniroma2.it>");
> +MODULE_DESCRIPTION("SRv6 L2 tunnel device");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS_RTNL_LINK("srl2");
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2)
2026-03-25 13:43 ` Justin Iurman
@ 2026-03-26 17:29 ` Stefano Salsano
0 siblings, 0 replies; 15+ messages in thread
From: Stefano Salsano @ 2026-03-26 17:29 UTC (permalink / raw)
To: Justin Iurman, Andrea Mayer
Cc: netdev, David S . Miller, David Ahern, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Paolo Lungaroni,
Ahmed Abdelsalam, linux-kernel
Il 25/03/2026 14:43, Justin Iurman ha scritto:
> On Sun, Mar 22, 2026 at 12:06 AM Andrea Mayer <andrea.mayer@uniroma2.it> wrote:
>>
>> Introduce srl2, an Ethernet pseudowire device over SRv6. It
>> encapsulates L2 frames in IPv6 with a Segment Routing Header for
>> transmission across an SRv6 network.
>>
>> The encapsulation logic reuses seg6_do_srh_encap() with
>> IPPROTO_ETHERNET. The transmit path uses the standard IPv6 tunnel
>> infrastructure (dst_cache, ip6_route_output, ip6tunnel_xmit).
>>
>> The device is configured with a segment list for point-to-point
>> L2 encapsulation.
>>
>> Usage:
>>
>> ip link add srl2-0 type srl2 segs fc00::a,fc00::b
>
> Thinking out loud...
>
> We'll likely need SRv6 encap configurations specific to each entry
> (i.e., MAC address), rather than (or in addition to) per interface.
agreed, this is the natural next step towards multipoint
we've started exploring the design for this, happy to collaborate if
you're interested
> We could also add a "mode" (e.g., "normal" mode or reduced mode?).
good point, the infrastructure is already in place, so adding a "mode"
parameter to sr6 to support reduced encap should be straightforward
we suggest to address it as an immediate follow-up to this baseline
series and we have already some prototype code ready
ciao
Stefano and Andrea
--
*******************************************************************
Prof. Stefano Salsano
Dipartimento Ingegneria Elettronica
Universita' di Roma Tor Vergata
Viale Politecnico, 1 - 00133 Roma - ITALY
http://netgroup.uniroma2.it/Stefano_Salsano/
E-mail : stefano.salsano@uniroma2.it
Office : (Tel.) +39 06 72597770 (Fax.) +39 06 72597435
*******************************************************************
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device
2026-03-26 16:30 ` Nicolas Dichtel
@ 2026-03-26 17:30 ` Stefano Salsano
0 siblings, 0 replies; 15+ messages in thread
From: Stefano Salsano @ 2026-03-26 17:30 UTC (permalink / raw)
To: nicolas.dichtel, Justin Iurman, Andrea Mayer
Cc: netdev, David S . Miller, David Ahern, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Paolo Lungaroni,
Ahmed Abdelsalam, linux-kernel
Il 26/03/2026 17:30, Nicolas Dichtel ha scritto:
>
>
> Le 25/03/2026 à 08:10, Stefano Salsano a écrit :
>> Il 24/03/2026 17:00, Justin Iurman ha scritto:
>>> On Sun, Mar 22, 2026 at 12:06 AM Andrea Mayer <andrea.mayer@uniroma2.it> wrote:
>>>>
>>>> Hi all,
>>>>
>>>> this RFC series adds support for the SRv6 End.DT2U behavior and
>>>> introduces the srl2 Ethernet pseudowire device, together with
>>>> corresponding selftests.
>>>
>>> Hi Andrea,
>>>
>>> Thanks for the work you (and Stefano) put into this series! As
>>> discussed offline, there is interest in such a solution.
>>
>> Hi Justin,
>>
>> thx for your interest!
>>
>>> A general comment: I'm not sure about the name "srl2", as we're
>>> talking about SRv6 specifically here.
>>
>> fair point
>>
>>> I would personally love to have
>>> "l2srv6", although I suspect we may end up with "l2seg6" to remain
>>> consistent.
>>
>> I'd like to keep it as short as possible, our initial idea was seg6l2, very
>> close to your last suggestion, then we opted for srl2 losing the "6" concept
>>
>> now it comes to my mind that l2 is somehow redudant in an interface type name,
>> as an interface is an l2 concept per se, so my preferred option becomes:
>>
>> sr6
> Note that the interface name is defined to kind + a number when the IFLA_IFNAME
> attribute is not specified:
> https://urldefense.com/v3/__https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/core/rtnetlink.c?h=v7.0-rc5*n3823__;Iw!!O5Bi4QcV!Bw3h__JfgzAoQlG3DyfQsdX5VEQi5Zx9dXbROUeKuwomkISAWgGMwNxjv059RhKI5xV9MpHHknEXDTb9diS16akPWyJi_dCP7aE$
>
> Here, the default name will be 'sr60' :)
+1
> 'vti6' is already in this case, so it's probably ok. I vote for 'sr6' too ;-)
>
> My two cents,
> Nicolas
--
*******************************************************************
Prof. Stefano Salsano
Dipartimento Ingegneria Elettronica
Universita' di Roma Tor Vergata
Viale Politecnico, 1 - 00133 Roma - ITALY
http://netgroup.uniroma2.it/Stefano_Salsano/
E-mail : stefano.salsano@uniroma2.it
Office : (Tel.) +39 06 72597770 (Fax.) +39 06 72597435
*******************************************************************
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-03-26 17:31 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-22 0:05 [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device Andrea Mayer
2026-03-22 0:05 ` [RFC PATCH net-next 1/3] seg6: add support for the SRv6 End.DT2U behavior Andrea Mayer
2026-03-22 0:05 ` [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2) Andrea Mayer
2026-03-24 16:08 ` Justin Iurman
2026-03-24 16:24 ` Justin Iurman
2026-03-25 13:43 ` Justin Iurman
2026-03-26 17:29 ` Stefano Salsano
2026-03-26 16:44 ` Nicolas Dichtel
2026-03-22 0:05 ` [RFC PATCH net-next 3/3] selftests: seg6: add SRv6 srl2 + End.DT2U L2 VPN test Andrea Mayer
2026-03-24 16:00 ` [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device Justin Iurman
2026-03-25 7:10 ` Stefano Salsano
2026-03-25 8:35 ` Justin Iurman
2026-03-26 16:30 ` Nicolas Dichtel
2026-03-26 17:30 ` Stefano Salsano
2026-03-26 16:32 ` Nicolas Dichtel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox