All of lore.kernel.org
 help / color / mirror / Atom feed
From: Eric Dumazet <edumazet@kernel.org>
To: "David S . Miller" <davem@davemloft.net>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>
Cc: Willem de Bruijn <willemb@google.com>,
	Simon Horman <horms@kernel.org>,
	netdev@vger.kernel.org, edumazet@kernel.org
Subject: [PATCH net 2/2] selftests: net: add a test for NSH GSO
Date: Thu, 12 Nov 2026 11:20:01 +0100	[thread overview]
Message-ID: <20261112102001.3744643-3-edumazet@kernel.org> (raw)
In-Reply-To: <20261112102001.3744643-1-edumazet@kernel.org>

Send TCPv4 GSO packets encapsulated in a stack of NSH headers to a
dummy device which has TSO disabled, using a packet socket with
PACKET_VNET_HDR, and count the packets the device transmits.

Packets with one or four NSH headers must be segmented. Packets with
5, 64 or 8000 NSH headers must be dropped: they exceed the recursion
limit of the GSO handlers. Without the limit in nsh_gso_segment(),
the last one overflows the kernel stack.

 # ./nsh_gso.sh
 PASS: 1 NSH headers: 4 packets sent
 PASS: 4 NSH headers: 4 packets sent
 PASS: 5 NSH headers: 0 packets sent
 PASS: 64 NSH headers: 0 packets sent
 PASS: 8000 NSH headers: 0 packets sent

Signed-off-by: Eric Dumazet <edumazet@kernel.org>
---
 tools/testing/selftests/net/.gitignore |   1 +
 tools/testing/selftests/net/Makefile   |   2 +
 tools/testing/selftests/net/config     |   1 +
 tools/testing/selftests/net/nsh_gso.c  | 126 +++++++++++++++++++++++++
 tools/testing/selftests/net/nsh_gso.sh |  71 ++++++++++++++
 5 files changed, 201 insertions(+)
 create mode 100644 tools/testing/selftests/net/nsh_gso.c
 create mode 100755 tools/testing/selftests/net/nsh_gso.sh

diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
index ca451bfeda75..27761239d069 100644
--- a/tools/testing/selftests/net/.gitignore
+++ b/tools/testing/selftests/net/.gitignore
@@ -21,6 +21,7 @@ log.txt
 msg_zerocopy
 netlink-dumps
 nettest
+nsh_gso
 proc_net_pktgen
 psock_fanout
 psock_snd
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 85f51a30c6a1..baea3e7f0f8f 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -76,6 +76,7 @@ TEST_PROGS := \
 	nk_qlease.py \
 	nl_netdev.py \
 	nl_nlctrl.py \
+	nsh_gso.sh \
 	pmtu.sh \
 	protodown.sh \
 	psock_snd.sh \
@@ -157,6 +158,7 @@ TEST_GEN_FILES := \
 	ipv6_flowlabel \
 	msg_zerocopy \
 	nettest \
+	nsh_gso \
 	psock_fanout \
 	psock_snd \
 	psock_tpacket \
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index d4416f8bdcfd..3e7ce41bd64d 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -99,6 +99,7 @@ CONFIG_NET_IPVTI=y
 CONFIG_NETKIT=y
 CONFIG_NET_L3_MASTER_DEV=y
 CONFIG_NET_NS=y
+CONFIG_NET_NSH=m
 CONFIG_NET_PKTGEN=m
 CONFIG_NET_SCH_ETF=m
 CONFIG_NET_SCH_FQ=m
diff --git a/tools/testing/selftests/net/nsh_gso.c b/tools/testing/selftests/net/nsh_gso.c
new file mode 100644
index 000000000000..9acd3f755723
--- /dev/null
+++ b/tools/testing/selftests/net/nsh_gso.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Send one TCPv4 GSO packet encapsulated in a stack of NSH headers,
+ * through a packet socket with PACKET_VNET_HDR.
+ *
+ * Usage: nsh_gso <ifname> <number of NSH headers>
+ *
+ * The packet is made of 4 segments. The send() result is not checked:
+ * the caller looks at the device counters.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <error.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/virtio_net.h>
+#include <net/if.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#ifndef ETH_P_NSH
+#define ETH_P_NSH	0x894F
+#endif
+
+#define NSH_HLEN	8	/* base header + service path header */
+#define NSH_MD_TYPE2	2	/* no mandatory context header */
+#define NSH_NP_IPV4	1
+#define NSH_NP_NSH	4
+
+#define GSO_SIZE	500
+#define NR_SEGS		4
+
+struct nsh_base {
+	__be16	ver_flags_ttl_len;
+	__u8	mdtype;
+	__u8	np;
+	__be32	path_hdr;
+};
+
+int main(int argc, char **argv)
+{
+	unsigned int depth, hlen, len, i;
+	struct sockaddr_ll addr = {};
+	struct virtio_net_hdr *vnet;
+	struct nsh_base *nsh;
+	struct ethhdr *eth;
+	struct tcphdr *tcp;
+	int fd, one = 1;
+	struct iphdr *ip;
+	char *buf;
+
+	if (argc != 3)
+		error(1, 0, "usage: %s <ifname> <depth>", argv[0]);
+
+	depth = strtoul(argv[2], NULL, 0);
+	hlen = sizeof(*eth) + depth * NSH_HLEN + sizeof(*ip) + sizeof(*tcp);
+	if (!depth || hlen > UINT16_MAX)
+		error(1, 0, "invalid depth %u", depth);
+	len = sizeof(*vnet) + hlen + NR_SEGS * GSO_SIZE;
+
+	buf = calloc(1, len);
+	if (!buf)
+		error(1, errno, "calloc");
+
+	vnet = (void *)buf;
+	vnet->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
+	vnet->gso_type = VIRTIO_NET_HDR_GSO_TCPV4;
+	vnet->hdr_len = hlen;
+	vnet->gso_size = GSO_SIZE;
+	vnet->csum_start = hlen - sizeof(*tcp);
+	vnet->csum_offset = __builtin_offsetof(struct tcphdr, check);
+
+	eth = (void *)(vnet + 1);
+	memset(eth->h_dest, 0xff, ETH_ALEN);
+	eth->h_source[0] = 0x02;
+	eth->h_proto = htons(ETH_P_NSH);
+
+	nsh = (void *)(eth + 1);
+	for (i = 0; i < depth; i++, nsh++) {
+		/* version 0, ttl 63, length in 4 byte words */
+		nsh->ver_flags_ttl_len = htons(63 << 6 | NSH_HLEN / 4);
+		nsh->mdtype = NSH_MD_TYPE2;
+		nsh->np = i == depth - 1 ? NSH_NP_IPV4 : NSH_NP_NSH;
+		nsh->path_hdr = htonl(1 << 8 | 255);
+	}
+
+	ip = (void *)nsh;
+	ip->version = 4;
+	ip->ihl = 5;
+	ip->ttl = 64;
+	ip->protocol = IPPROTO_TCP;
+	ip->saddr = htonl(0xc0000201);	/* 192.0.2.1 */
+	ip->daddr = htonl(0xc0000202);	/* 192.0.2.2 */
+
+	tcp = (void *)(ip + 1);
+	tcp->source = htons(1234);
+	tcp->dest = htons(5678);
+	tcp->doff = 5;
+	tcp->ack = 1;
+
+	fd = socket(AF_PACKET, SOCK_RAW, 0);
+	if (fd < 0)
+		error(1, errno, "socket");
+	if (setsockopt(fd, SOL_PACKET, PACKET_VNET_HDR, &one, sizeof(one)))
+		error(1, errno, "setsockopt PACKET_VNET_HDR");
+
+	addr.sll_family = AF_PACKET;
+	addr.sll_protocol = htons(ETH_P_NSH);
+	addr.sll_ifindex = if_nametoindex(argv[1]);
+	if (!addr.sll_ifindex)
+		error(1, errno, "if_nametoindex %s", argv[1]);
+
+	/* A packet dropped at segmentation time is reported as an error,
+	 * this is expected for deep stacks.
+	 */
+	sendto(fd, buf, len, 0, (void *)&addr, sizeof(addr));
+
+	close(fd);
+	free(buf);
+	return 0;
+}
diff --git a/tools/testing/selftests/net/nsh_gso.sh b/tools/testing/selftests/net/nsh_gso.sh
new file mode 100755
index 000000000000..d0746cf1d528
--- /dev/null
+++ b/tools/testing/selftests/net/nsh_gso.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Check software GSO of NSH packets: a TCP GSO packet behind a few NSH
+# headers is segmented, and a deep stack of NSH headers is dropped
+# instead of recursing in nsh_gso_segment() for each of them.
+
+source lib.sh
+
+NR_SEGS=4
+ret=0
+
+cleanup()
+{
+	cleanup_ns $ns1
+}
+
+tx_packets()
+{
+	ip netns exec $ns1 cat /sys/class/net/dummy0/statistics/tx_packets
+}
+
+# $1: number of NSH headers, $2: expected number of transmitted packets
+check_nsh_gso()
+{
+	local depth=$1
+	local expected=$2
+	local before
+	local sent
+
+	before=$(tx_packets)
+	ip netns exec $ns1 ./nsh_gso dummy0 $depth
+	sent=$(($(tx_packets) - before))
+
+	if [ $sent -eq $expected ]; then
+		echo "PASS: $depth NSH headers: $sent packets sent"
+	else
+		echo "FAIL: $depth NSH headers: $sent packets sent, expected $expected"
+		ret=1
+	fi
+}
+
+modprobe -q nsh
+nsh_loaded=$?
+
+trap cleanup EXIT
+setup_ns ns1 || exit $ksft_skip
+
+ip -netns $ns1 link add dummy0 type dummy
+ip -netns $ns1 link set dummy0 up
+# No IPv6 autoconf traffic on the device, to get stable counters.
+ip netns exec $ns1 sysctl -qw net.ipv6.conf.dummy0.disable_ipv6=1
+# Force software segmentation in validate_xmit_skb().
+ip netns exec $ns1 ethtool -K dummy0 tso off >/dev/null || exit $ksft_skip
+
+before=$(tx_packets)
+ip netns exec $ns1 ./nsh_gso dummy0 1
+if [ $(($(tx_packets) - before)) -eq 0 ] && [ $nsh_loaded -ne 0 ]; then
+	echo "SKIP: NSH GSO not available"
+	exit $ksft_skip
+fi
+
+check_nsh_gso 1 $NR_SEGS
+# The recursion limit is shared with the inner IPv4 header.
+check_nsh_gso 4 $NR_SEGS
+check_nsh_gso 5 0
+check_nsh_gso 64 0
+# The largest stack that fits in a 64KB header.
+check_nsh_gso 8000 0
+
+exit $ret
-- 
2.53.0


  parent reply	other threads:[~2026-11-12 10:20 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-12 10:19 [PATCH net 0/2] nsh: limit recursion in nsh_gso_segment() Eric Dumazet
2026-11-12 10:20 ` [PATCH net 1/2] " Eric Dumazet
2026-11-12 10:33   ` Jiri Benc
2026-11-12 22:54   ` David Laight
2026-11-12 10:20 ` Eric Dumazet [this message]
2026-11-12 19:40 ` [PATCH net 0/2] " patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261112102001.3744643-3-edumazet@kernel.org \
    --to=edumazet@kernel.org \
    --cc=davem@davemloft.net \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=willemb@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.