From nobody Sat Apr 4 01:35:53 2026 Received: from smtp.uniroma2.it (smtp.uniroma2.it [160.80.4.37]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B865140DFD8; Sun, 22 Mar 2026 00:07:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=160.80.4.37 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774138026; cv=none; b=QjeJgXyzlLHXKnOnIhNZAmyBzRymfuPXExwoQuRGhMyBnpJvaRZUdTRSm6OKZTdJ7MaFCRqlfk8DojtWK8mzcluuru/11uOqzVwr7PjJfKQ6tIIIvcT8kdC4YxjAjq5ODRy3J30k/eu2utgdJaWF0Xee0rapS5RCXMLnRiXb4MM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774138026; c=relaxed/simple; bh=ydZr5tIQVHkCWq3/6Au930Fb1e1Dj1nhBQ13K5iuYlA=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=s6maN+AVVoG1qIASf7DR/sxK2pFImiP5Gy/fTrPUuQRcTghpDXMXXkaEksy4l7r4rpvvbl0MKpM9WwkmQWfElJ6xJzLFC8elKSvP1Y/s4+nHlMkQ1ByTK0bNkTPatGxb6vF04cBnH63QzwFfrbdOcPyOU6JxUrBfO7mZGNHszGo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=uniroma2.it; spf=pass smtp.mailfrom=uniroma2.it; arc=none smtp.client-ip=160.80.4.37 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=uniroma2.it Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=uniroma2.it Received: from localhost.localdomain ([160.80.103.126]) by smtp-2015.uniroma2.it (8.14.4/8.14.4/Debian-8) with ESMTP id 62M06J54021482; Sun, 22 Mar 2026 01:06:25 +0100 From: Andrea Mayer To: netdev@vger.kernel.org Cc: "David S . Miller" , David Ahern , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Stefano Salsano , Paolo Lungaroni , Ahmed Abdelsalam , Justin Iurman , linux-kernel@vger.kernel.org, Andrea Mayer Subject: [RFC PATCH net-next 1/3] seg6: add support for the SRv6 End.DT2U behavior Date: Sun, 22 Mar 2026 01:05:55 +0100 Message-Id: <20260322000557.12559-2-andrea.mayer@uniroma2.it> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20260322000557.12559-1-andrea.mayer@uniroma2.it> References: <20260322000557.12559-1-andrea.mayer@uniroma2.it> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: clamav-milter 0.100.0 at smtp-2015 X-Virus-Status: Clean Content-Type: text/plain; charset="utf-8" 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 Signed-off-by: Stefano Salsano Signed-off-by: Andrea Mayer --- 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_loca= l.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 =3D 15, /* decap and lookup of DA in v4 or v6 table */ SEG6_LOCAL_ACTION_END_DT46 =3D 16, + /* decap and unicast MAC L2 table lookup */ + SEG6_LOCAL_ACTION_END_DT2U =3D 17, =20 __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; } =20 +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 =3D 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 =3D dev_get_by_index_rcu(net, slwt->l2dev); + if (!l2dev) + goto drop; + + if (l2dev->type !=3D 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 =3D 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 =3D &fib6_cfg->fc_nlinfo; + + return nli->nl_net; +} + +static int seg6_end_dt2u_build(struct seg6_local_lwt *slwt, const void *cf= g, + struct netlink_ext_ack *extack) +{ + struct net *net =3D fib6_config_get_net(cfg); + struct net_device *dev; + + dev =3D __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, } =20 #ifdef CONFIG_NET_L3_MASTER_DEV -static struct net *fib6_config_get_net(const struct fib6_config *fib6_cfg) -{ - const struct nl_info *nli =3D &fib6_cfg->fc_nlinfo; - - return nli->nl_net; -} =20 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[] = =3D { .optattrs =3D SEG6_F_LOCAL_COUNTERS, .input =3D input_action_end_bpf, }, + { + .action =3D SEG6_LOCAL_ACTION_END_DT2U, + .attrs =3D SEG6_F_ATTR(SEG6_LOCAL_L2DEV), + .optattrs =3D SEG6_F_LOCAL_COUNTERS, + .input =3D input_action_end_dt2u, + .slwt_ops =3D { + .build_state =3D seg6_end_dt2u_build, + }, + }, =20 }; =20 @@ -1655,6 +1771,7 @@ static const struct nla_policy seg6_local_policy[SEG6= _LOCAL_MAX + 1] =3D { [SEG6_LOCAL_BPF] =3D { .type =3D NLA_NESTED }, [SEG6_LOCAL_COUNTERS] =3D { .type =3D NLA_NESTED }, [SEG6_LOCAL_FLAVORS] =3D { .type =3D NLA_NESTED }, + [SEG6_LOCAL_L2DEV] =3D { .type =3D NLA_U32 }, }; =20 static int parse_nla_srh(struct nlattr **attrs, struct seg6_local_lwt *slw= t, @@ -1889,6 +2006,30 @@ static int cmp_nla_oif(struct seg6_local_lwt *a, str= uct seg6_local_lwt *b) return 0; } =20 +static int parse_nla_l2dev(struct nlattr **attrs, struct seg6_local_lwt *s= lwt, + struct netlink_ext_ack *extack) +{ + slwt->l2dev =3D 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 !=3D 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= ] =3D { [SEG6_LOCAL_BPF_PROG] =3D { .type =3D NLA_U32, }, @@ -2318,6 +2459,10 @@ static struct seg6_action_param seg6_action_params[S= EG6_LOCAL_MAX + 1] =3D { [SEG6_LOCAL_FLAVORS] =3D { .parse =3D parse_nla_flavors, .put =3D put_nla_flavors, .cmp =3D cmp_nla_flavors }, + + [SEG6_LOCAL_L2DEV] =3D { .parse =3D parse_nla_l2dev, + .put =3D put_nla_l2dev, + .cmp =3D cmp_nla_l2dev }, }; =20 /* 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 +=3D encap_size_flavors(slwt); =20 + if (attrs & SEG6_F_ATTR(SEG6_LOCAL_L2DEV)) + nlsize +=3D nla_total_size(4); + return nlsize; } =20 --=20 2.20.1 From nobody Sat Apr 4 01:35:53 2026 Received: from smtp.uniroma2.it (smtp.uniroma2.it [160.80.4.37]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 403BD7478; Sun, 22 Mar 2026 00:07:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=160.80.4.37 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774138025; cv=none; b=rNf474MmacBXT0Vtz4atlVH1GlEhxW4yogOzr2flrFTP7AVcR72ct1W5WEpD2s6octoBpzm1jyn0eL+gOvScBP/cpij4OJLi1kY+hJxXuqUIzHrVptLFjGONOlbO0xoNHOrSTLkU2A8eKVC3ujTFkfCfjl/5e3yuqTkT/ajUkfE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774138025; c=relaxed/simple; bh=ln/naS4oASngnzJ5yMkbK6O44l2fukyL8IAzeiWnKcc=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=pvQNs8FmllwIJlM+JJLMumMLvRCQZvW7JTuRezmr9lLs/nR4WvBv1rUSr8cjpPRnhK5FrgBj5MkkbJpAzqW+5ejQT38LB1DJz5GqHN+RSxdkuvswSsIDMOCLwnKkk5r5mKrBdF5QH2CqhZbCA/qlLZLQ5/5cOQgDa/f+8QBikes= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=uniroma2.it; spf=pass smtp.mailfrom=uniroma2.it; arc=none smtp.client-ip=160.80.4.37 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=uniroma2.it Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=uniroma2.it Received: from localhost.localdomain ([160.80.103.126]) by smtp-2015.uniroma2.it (8.14.4/8.14.4/Debian-8) with ESMTP id 62M06J55021482; Sun, 22 Mar 2026 01:06:25 +0100 From: Andrea Mayer To: netdev@vger.kernel.org Cc: "David S . Miller" , David Ahern , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Stefano Salsano , Paolo Lungaroni , Ahmed Abdelsalam , Justin Iurman , linux-kernel@vger.kernel.org, Andrea Mayer Subject: [RFC PATCH net-next 2/3] seg6: add SRv6 L2 tunnel device (srl2) Date: Sun, 22 Mar 2026 01:05:56 +0100 Message-Id: <20260322000557.12559-3-andrea.mayer@uniroma2.it> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20260322000557.12559-1-andrea.mayer@uniroma2.it> References: <20260322000557.12559-1-andrea.mayer@uniroma2.it> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: clamav-milter 0.100.0 at smtp-2015 X-Virus-Status: Clean Content-Type: text/plain; charset="utf-8" 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 Signed-off-by: Stefano Salsano Signed-off-by: Andrea Mayer --- 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 + +#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 + */ + +#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 =3D y =20 +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) +=3D syncookies.o ipv6-$(CONFIG_NETLABEL) +=3D calipso.o ipv6-$(CONFIG_IPV6_SEG6_LWTUNNEL) +=3D seg6_iptunnel.o seg6_local.o ipv6-$(CONFIG_IPV6_SEG6_HMAC) +=3D seg6_hmac.o +obj-$(CONFIG_IPV6_SRL2) +=3D srl2.o ipv6-$(CONFIG_IPV6_RPL_LWTUNNEL) +=3D rpl_iptunnel.o ipv6-$(CONFIG_IPV6_IOAM6_LWTUNNEL) +=3D ioam6_iptunnel.o =20 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) =20 return true; } +EXPORT_SYMBOL_GPL(seg6_validate_srh); =20 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 + * Stefano Salsano + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 =3D netdev_priv(dev); + struct net *net =3D dev_net(dev); + struct dst_entry *dst; + struct flowi6 fl6; + int err; + + local_bh_disable(); + dst =3D dst_cache_get(&priv->dst_cache); + local_bh_enable(); + + if (unlikely(!dst)) { + memset(&fl6, 0, sizeof(fl6)); + fl6.daddr =3D priv->srh->segments[priv->srh->first_segment]; + + dst =3D 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) =3D=3D 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 =3D 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 =3D 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 =3D 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 =3D netdev_priv(dev); + + dst_cache_destroy(&priv->dst_cache); +} + +static void srl2_dev_free(struct net_device *dev) +{ + struct srl2_priv *priv =3D netdev_priv(dev); + + kfree(priv->srh); +} + +static const struct net_device_ops srl2_netdev_ops =3D { + .ndo_init =3D srl2_dev_init, + .ndo_uninit =3D srl2_dev_uninit, + .ndo_start_xmit =3D srl2_xmit, + .ndo_set_mac_address =3D eth_mac_addr, + .ndo_validate_addr =3D eth_validate_addr, +}; + +static void srl2_setup(struct net_device *dev) +{ + ether_setup(dev); + + dev->netdev_ops =3D &srl2_netdev_ops; + dev->needs_free_netdev =3D true; + dev->pcpu_stat_type =3D NETDEV_PCPU_STAT_DSTATS; + dev->needed_headroom =3D LL_MAX_HEADER + sizeof(struct ipv6hdr) + + SRL2_SRH_HEADROOM_EST; + + dev->priv_flags &=3D ~IFF_TX_SKB_SHARING; + dev->priv_flags |=3D IFF_LIVE_ADDR_CHANGE | IFF_NO_QUEUE; + dev->lltx =3D true; + + eth_hw_addr_random(dev); +} + +static const struct nla_policy srl2_policy[IFLA_SRL2_MAX + 1] =3D { + [IFLA_SRL2_SRH] =3D { .type =3D 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 =3D netdev_priv(dev); + struct nlattr **data =3D params->data; + struct ipv6_sr_hdr *srh; + int srhlen; + int len; + + srh =3D nla_data(data[IFLA_SRL2_SRH]); + len =3D 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 =3D kmemdup(srh, len, GFP_KERNEL); + if (!priv->srh) + return -ENOMEM; + + srhlen =3D ipv6_optlen(srh); + + dev->needed_headroom =3D 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 =3D ETH_DATA_LEN - sizeof(struct ipv6hdr) - srhlen - ETH_HLEN; + dev->min_mtu =3D ETH_MIN_MTU; + dev->max_mtu =3D IP_MAX_MTU - sizeof(struct ipv6hdr) - srhlen - ETH_HLEN; + + dev->priv_destructor =3D 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 =3D netdev_priv(dev); + int srhlen =3D ipv6_optlen(priv->srh); + + return nla_total_size(srhlen); +} + +static int srl2_fill_info(struct sk_buff *skb, const struct net_device *de= v) +{ + const struct srl2_priv *priv =3D netdev_priv(dev); + int srhlen =3D 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 =3D { + .kind =3D "srl2", + .maxtype =3D IFLA_SRL2_MAX, + .policy =3D srl2_policy, + .priv_size =3D sizeof(struct srl2_priv), + .setup =3D srl2_setup, + .validate =3D srl2_validate, + .newlink =3D srl2_newlink, + .dellink =3D srl2_dellink, + .get_size =3D srl2_get_size, + .fill_info =3D 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 "); +MODULE_AUTHOR("Stefano Salsano "); +MODULE_DESCRIPTION("SRv6 L2 tunnel device"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_RTNL_LINK("srl2"); --=20 2.20.1 From nobody Sat Apr 4 01:35:53 2026 Received: from smtp.uniroma2.it (smtp.uniroma2.it [160.80.4.37]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B862D79CD; Sun, 22 Mar 2026 00:07:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=160.80.4.37 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774138025; cv=none; b=Qs5Zv+WhI4KTRFs4tVm3aTmaBkhVIqVe+mZiXAtnrpfC2fdcnk/XuqMof3fqo4ay3wy+fZlfWHD6iMRpXdkDSorlyI4q1d8xU0w/JpUExFwVHPnNlqfzPlmHG+okctCvB9M8K5wLP3uvVHaC7LFeSqBb0zcKdF7UG8RwHrxng5g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774138025; c=relaxed/simple; bh=S10CNGulsSsjB4/T4ThdvAJzj/O2sy1yX3ScZIOapXY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Z9SEQZhe8lmlmnr8GT6ALJl3ER9c/kVAu0RH1DZJkxGYluUgGdisZU//tGu1SAF1mjcLuIiR/93PRBvz0I63+EQj4SQRtUvQvEmC9NzNPi8fuEuIRotM9xQKc2xEVpIiRs8R2zayPVaUE1muisNF7xdTm7oh6bNl7BxE4UQi4x4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=uniroma2.it; spf=pass smtp.mailfrom=uniroma2.it; arc=none smtp.client-ip=160.80.4.37 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=uniroma2.it Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=uniroma2.it Received: from localhost.localdomain ([160.80.103.126]) by smtp-2015.uniroma2.it (8.14.4/8.14.4/Debian-8) with ESMTP id 62M06J56021482; Sun, 22 Mar 2026 01:06:25 +0100 From: Andrea Mayer To: netdev@vger.kernel.org Cc: "David S . Miller" , David Ahern , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Stefano Salsano , Paolo Lungaroni , Ahmed Abdelsalam , Justin Iurman , linux-kernel@vger.kernel.org, Andrea Mayer , Shuah Khan , linux-kselftest@vger.kernel.org Subject: [RFC PATCH net-next 3/3] selftests: seg6: add SRv6 srl2 + End.DT2U L2 VPN test Date: Sun, 22 Mar 2026 01:05:57 +0100 Message-Id: <20260322000557.12559-4-andrea.mayer@uniroma2.it> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20260322000557.12559-1-andrea.mayer@uniroma2.it> References: <20260322000557.12559-1-andrea.mayer@uniroma2.it> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: clamav-milter 0.100.0 at smtp-2015 X-Virus-Status: Clean Content-Type: text/plain; charset="utf-8" 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 Cc: linux-kselftest@vger.kernel.org Signed-off-by: Andrea Mayer --- 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 :=3D \ 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/n= et/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=3Dy CONFIG_IPV6_RPL_LWTUNNEL=3Dy CONFIG_IPV6_SEG6_LWTUNNEL=3Dy CONFIG_IPV6_SIT=3Dy +CONFIG_IPV6_SRL2=3Dm CONFIG_IPV6_VTI=3Dy CONFIG_IPVLAN=3Dm CONFIG_IPVTAP=3Dm diff --git a/tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh b/tools/te= sting/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 +# +# 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 +# =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +# +# 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 +# =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +# +# 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=3D"dum0" +readonly SRL2_DEVNAME=3D"srl2-0" +readonly RT2HS_DEVNAME=3D"veth-hs" +readonly BRIDGE_DEVNAME=3D"br0" +readonly HS_VETH_NAME=3D"veth0" +readonly LOCALSID_TABLE_ID=3D90 +readonly IPv6_RT_NETWORK=3Dfcf0:0 +readonly IPv6_HS_NETWORK=3Dcafe +readonly IPv4_HS_NETWORK=3D10.0.0 +readonly VPN_LOCATOR_SERVICE=3Dfcff +readonly DT2U_FUNC=3D0d20 + +PING_TIMEOUT_SEC=3D4 +PAUSE_ON_FAIL=3D${PAUSE_ON_FAIL:=3Dno} + +ROUTERS=3D'' +HOSTS=3D'' + +SETUP_ERR=3D1 + +ret=3D${ksft_skip} +nsuccess=3D0 +nfail=3D0 + +log_test() +{ + local rc=3D"$1" + local expected=3D"$2" + local msg=3D"$3" + + if [ "${rc}" -eq "${expected}" ]; then + nsuccess=3D$((nsuccess+1)) + printf "\n TEST: %-60s [ OK ]\n" "${msg}" + else + ret=3D1 + nfail=3D$((nfail+1)) + printf "\n TEST: %-60s [FAIL]\n" "${msg}" + if [ "${PAUSE_ON_FAIL}" =3D "yes" ]; then + echo + echo "hit enter to continue, 'q' to quit" + read a + [ "$a" =3D "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=3D0 + fi +} + +log_section() +{ + echo + echo "###################################################################= #############" + echo "TEST SECTION: $*" + echo "###################################################################= #############" +} + +test_command_or_ksft_skip() +{ + local cmd=3D"$1" + + if [ ! -x "$(command -v "${cmd}")" ]; then + echo "SKIP: Could not run test without \"${cmd}\" tool"; + exit "${ksft_skip}" + fi +} + +get_rtname() +{ + local rtid=3D"$1" + + echo "rt_${rtid}" +} + +get_hsname() +{ + local hsid=3D"$1" + + echo "hs_${hsid}" +} + +create_router() +{ + local rtid=3D"$1" + local nsname + + nsname=3D"$(get_rtname "${rtid}")" + setup_ns "${nsname}" +} + +create_host() +{ + local hsid=3D"$1" + local nsname + + nsname=3D"$(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=3D"$1" + local rt_neighs=3D"$2" + local neigh + local nsname + local neigh_nsname + + eval nsname=3D\${$(get_rtname "${rt}")} + + for neigh in ${rt_neighs}; do + eval neigh_nsname=3D\${$(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=3D"$1" + local neigh=3D"$2" + local p=3D"${rt}" + local q=3D"${neigh}" + + if [ "${p}" -gt "${q}" ]; then + p=3D"${q}"; q=3D"${rt}" + fi + + echo "${IPv6_RT_NETWORK}:${p}:${q}" +} + +setup_rt_networking() +{ + local rt=3D"$1" + local rt_neighs=3D"$2" + local nsname + local net_prefix + local devname + local neigh + + eval nsname=3D\${$(get_rtname "${rt}")} + + for neigh in ${rt_neighs}; do + devname=3D"veth-rt-${rt}-${neigh}" + + net_prefix=3D"$(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=3D0 + ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.default.accept_dad=3D0 + ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.all.forwarding=3D1 + ip netns exec "${nsname}" sysctl -wq net.ipv4.ip_forward=3D1 +} + +setup_rt_local_sids() +{ + local rt=3D"$1" + local rt_neighs=3D"$2" + local net_prefix + local devname + local nsname + local neigh + + eval nsname=3D\${$(get_rtname "${rt}")} + + for neigh in ${rt_neighs}; do + devname=3D"veth-rt-${rt}-${neigh}" + + net_prefix=3D"$(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=3D"$1" + local rt=3D"$2" + local hsname + local rtname + + eval hsname=3D\${$(get_hsname "${hs}")} + eval rtname=3D\${$(get_rtname "${rt}")} + + ip netns exec "${hsname}" sysctl -wq net.ipv6.conf.all.accept_dad=3D0 + ip netns exec "${hsname}" sysctl -wq net.ipv6.conf.default.accept_dad=3D0 + + 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=3D"$1" + local remote_rt=3D"$2" + local nsname + + eval nsname=3D\${$(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=3D"1 2"; readonly ROUTERS + for i in ${ROUTERS}; do + create_router "${i}" + done + + # create hosts + HOSTS=3D"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=3D0 +} + +check_rt_connectivity() +{ + local rtsrc=3D"$1" + local rtdst=3D"$2" + local prefix + local rtsrc_nsname + + eval rtsrc_nsname=3D\${$(get_rtname "${rtsrc}")} + + prefix=3D"$(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=3D"$1" + local rtdst=3D"$2" + + check_rt_connectivity "${rtsrc}" "${rtdst}" + log_test $? 0 "Routers connectivity: rt-${rtsrc} -> rt-${rtdst}" +} + +check_hs_ipv6_connectivity() +{ + local hssrc=3D"$1" + local hsdst=3D"$2" + local hssrc_nsname + + eval hssrc_nsname=3D\${$(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=3D"$1" + local hsdst=3D"$2" + local hssrc_nsname + + eval hssrc_nsname=3D\${$(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=3D"$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=3D"$1" + local hsdst=3D"$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=3D"$1" + local hsdst=3D"$2" + + check_hs_ipv4_connectivity "${hssrc}" "${hsdst}" + log_test $? 0 "IPv4 Hosts connectivity: hs-${hssrc} -> hs-${hsdst}" +} + +check_and_log_hs_connectivity() +{ + local hssrc=3D"$1" + local hsdst=3D"$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=3D"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=3D"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 --=20 2.20.1