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
next prev 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.