From: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
To: Wang Zhan <wang.zhan@smartx.com>, netdev@vger.kernel.org
Cc: davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, horms@kernel.org, keyong.sun@smartx.com,
Willem de Bruijn <willemdebruijn.kernel@gmail.com>,
Jason Wang <jasowangio@gmail.com>,
Andrew Lunn <andrew+netdev@lunn.ch>,
Aaron Conole <aconole@redhat.com>,
Eelco Chaudron <echaudro@redhat.com>,
Ilya Maximets <i.maximets@ovn.org>,
dev@openvswitch.org, Daniel Borkmann <daniel@iogearbox.net>,
Neal Cardwell <ncardwell@google.com>,
Kuniyuki Iwashima <kuniyu@google.com>,
Alice Mikityanska <alice@isovalent.com>,
David Laight <david.laight.linux@gmail.com>,
Wang Zhan <wang.zhan@smartx.com>
Subject: Re: [PATCH net-next v4 5/5] net: net_test: add tests for TCP re-segmentation
Date: Wed, 30 Sep 2026 15:53:09 -0400 [thread overview]
Message-ID: <willemdebruijn.kernel.134bdb5c71ee0@gmail.com> (raw)
In-Reply-To: <20260930111526.2183107-6-wang.zhan@smartx.com>
Wang Zhan wrote:
> The GSO engine can now be asked to group several MSS segments into one
> output skb, which is what re-segmentation needs. Add KUnit coverage for
> the max_segs parameter.
>
> The parameterized GSO test gains a max_segs input and three cases: two
> limits which cut the input into two and three output skbs, and a max_segs
> of one MSS, which must leave the output unchanged. It drives
> skb_segment() directly, because the synthetic protocol it uses has no
> gso_segment callback, and stores max_segs in the GSO control block itself.
>
> The TCP test drives __skb_gso_segment() with a max_segs of two MSS and
> checks that every output skb stays GSO, keeps its gso_size, and stays
> within the limit. The length test runs a 200 KiB TCP skb through
> validate_xmit_skb_list(), the caller which sets max_segs, and checks that
> the length declared by every output matches the L3 length of that output.
> The limit test checks that the GSO size limit which netif_skb_features()
> applies follows the packet's L3 protocol, for an IPv4 and an IPv6 skb,
> also with the tag inside the frame.
>
> Assisted-by: LLM
> Signed-off-by: Wang Zhan <wang.zhan@smartx.com>
>
> ---
> v4:
> - name the cases and the descriptions after re-segmentation
> - reserve the headroom the VLAN step needs in both builders
> - check the exact gso_segs of each output
> - free the segments when a bounded case does not match
> v3: https://lore.kernel.org/20260928044102.1004310-6-wang.zhan@smartx.com/
> v2: https://lore.kernel.org/20260918084651.3022878-5-wang.zhan@smartx.com/
> v1: https://lore.kernel.org/20260917063854.2011613-5-wang.zhan@smartx.com/
> ---
> net/core/net_test.c | 351 ++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 351 insertions(+)
>
> diff --git a/net/core/net_test.c b/net/core/net_test.c
> index 9c3a590865d269..6a91db83e53017 100644
> --- a/net/core/net_test.c
> +++ b/net/core/net_test.c
> @@ -4,7 +4,14 @@
>
> /* GSO */
>
> +#include <linux/if_ether.h>
> +#include <linux/if_vlan.h>
> +#include <linux/ip.h>
> +#include <linux/ipv6.h>
> +#include <linux/netdevice.h>
> #include <linux/skbuff.h>
> +#include <linux/tcp.h>
> +#include <net/gso.h>
>
> static const char hdr[] = "abcdefgh";
> #define GSO_TEST_SIZE 1000
> @@ -34,6 +41,9 @@ enum gso_test_nr {
> GSO_TEST_FRAG_LIST_PURE,
> GSO_TEST_FRAG_LIST_NON_UNIFORM,
> GSO_TEST_GSO_BY_FRAGS,
> + GSO_TEST_RESEGMENT,
> + GSO_TEST_RESEGMENT_MULTI,
> + GSO_TEST_RESEGMENT_ONE_MSS,
> };
>
> struct gso_test_case {
> @@ -46,10 +56,12 @@ struct gso_test_case {
> const unsigned int *frags;
> unsigned int nr_frag_skbs;
> const unsigned int *frag_skbs;
> + unsigned int max_segs;
>
> /* output as expected */
> unsigned int nr_segs;
> const unsigned int *segs;
> + bool segs_are_gso;
> };
>
> static struct gso_test_case cases[] = {
> @@ -135,6 +147,51 @@ static struct gso_test_case cases[] = {
> .nr_segs = 4,
> .segs = (const unsigned int[]) { 100, 200, 300, 400 },
> },
> + {
> + .id = GSO_TEST_RESEGMENT,
> + .name = "resegment",
> + .linear_len = GSO_TEST_SIZE,
> + .nr_frags = 3,
> + .frags = (const unsigned int[]) {
> + GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
> + },
> + .max_segs = 2,
> + .nr_segs = 2,
> + .segs = (const unsigned int[]) {
> + 2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,
> + },
> + .segs_are_gso = true,
> + },
> + {
> + .id = GSO_TEST_RESEGMENT_MULTI,
> + .name = "resegment_multi",
> + .linear_len = 2 * GSO_TEST_SIZE,
> + .nr_frags = 4,
> + .frags = (const unsigned int[]) {
> + GSO_TEST_SIZE, GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
> + },
> + .max_segs = 2,
> + .nr_segs = 3,
> + .segs = (const unsigned int[]) {
> + 2 * GSO_TEST_SIZE, 2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,
> + },
> + .segs_are_gso = true,
> + },
> + {
> + /* A max_segs of one leaves one MSS per skb, like no limit. */
> + .id = GSO_TEST_RESEGMENT_ONE_MSS,
> + .name = "resegment_one_mss",
> + .linear_len = GSO_TEST_SIZE,
> + .nr_frags = 3,
> + .frags = (const unsigned int[]) {
> + GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
> + },
> + .max_segs = 1,
> + .nr_segs = 4,
> + .segs = (const unsigned int[]) {
> + GSO_TEST_SIZE, GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
> + },
> + },
> };
Given that we have the above (concise) testcases:
Do we need to add the below verbose code? What does it add, just the
TCP specific path?
> static void gso_test_case_to_desc(struct gso_test_case *t, char *desc)
> @@ -226,6 +283,7 @@ static void gso_test_func(struct kunit *test)
> if (tcase->id == GSO_TEST_FRAG_LIST_NON_UNIFORM)
> features &= ~NETIF_F_SG;
>
> + SKB_GSO_CB(skb)->max_segs = tcase->max_segs;
> segs = skb_segment(skb, features);
> if (IS_ERR(segs)) {
> KUNIT_FAIL(test, "segs error %pe", segs);
> @@ -239,6 +297,7 @@ static void gso_test_func(struct kunit *test)
> for (cur = segs, i = 0; cur; cur = next, i++) {
> next = cur->next;
>
> + KUNIT_ASSERT_LT(test, i, tcase->nr_segs);
> KUNIT_ASSERT_EQ(test, cur->len, sizeof(hdr) + tcase->segs[i]);
>
> /* segs have skb->data pointing to the mac header */
> @@ -247,6 +306,18 @@ static void gso_test_func(struct kunit *test)
>
> /* header was copied to all segs */
> KUNIT_ASSERT_EQ(test, memcmp(skb_mac_header(cur), hdr, sizeof(hdr)), 0);
> + if (tcase->segs_are_gso) {
> + KUNIT_EXPECT_TRUE(test, skb_is_gso(cur));
> + KUNIT_EXPECT_EQ(test, skb_shinfo(cur)->gso_size,
> + GSO_TEST_SIZE);
> + KUNIT_EXPECT_EQ(test, skb_shinfo(cur)->gso_segs,
> + DIV_ROUND_UP(tcase->segs[i],
> + GSO_TEST_SIZE));
> + KUNIT_EXPECT_FALSE(test, skb_shinfo(cur)->gso_type &
> + SKB_GSO_PARTIAL);
> + } else if (tcase->max_segs) {
> + KUNIT_EXPECT_FALSE(test, skb_is_gso(cur));
> + }
>
> /* last seg can be found through segs->prev pointer */
> if (!next)
> @@ -261,6 +332,283 @@ static void gso_test_func(struct kunit *test)
> consume_skb(skb);
> }
>
> +#define GSO_TCP_HDR_LEN \
> + (ETH_HLEN + sizeof(struct iphdr) + sizeof(struct tcphdr))
> +
> +static struct sk_buff *gso_tcp_skb_new(unsigned int payload_len)
> +{
> + struct sk_buff *skb;
> + struct ethhdr *eth;
> + struct tcphdr *th;
> + struct iphdr *iph;
> +
> + skb = alloc_skb(NET_SKB_PAD + GSO_TCP_HDR_LEN + payload_len,
> + GFP_KERNEL);
> + if (!skb)
> + return NULL;
> + skb_reserve(skb, NET_SKB_PAD);
> + skb_put_zero(skb, GSO_TCP_HDR_LEN + payload_len);
> +
> + skb_reset_mac_header(skb);
> + eth = eth_hdr(skb);
> + eth->h_proto = htons(ETH_P_IP);
> + skb->protocol = eth->h_proto;
> +
> + skb_set_network_header(skb, ETH_HLEN);
> + iph = ip_hdr(skb);
> + iph->version = 4;
> + iph->ihl = sizeof(*iph) / 4;
> + iph->protocol = IPPROTO_TCP;
> + iph->tot_len = htons(sizeof(*iph) + sizeof(*th) + payload_len);
> +
> + skb_set_transport_header(skb, ETH_HLEN + sizeof(*iph));
> + th = tcp_hdr(skb);
> + th->doff = sizeof(*th) / 4;
> +
> + skb->ip_summed = CHECKSUM_PARTIAL;
> + skb->csum_start = skb_transport_header(skb) - skb->head;
> + skb->csum_offset = offsetof(struct tcphdr, check);
> + skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;
> + skb_shinfo(skb)->gso_size = GSO_TEST_SIZE;
> + skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(payload_len, GSO_TEST_SIZE);
> +
> + return skb;
> +}
> +
> +/*
> + * The transmit path passes the features of the device which rejects this
> + * skb, with the GSO bits cleared, so the engine segments it.
> + */
> +static void gso_test_tcp_resegment(struct kunit *test)
> +{
> + netdev_features_t features = NETIF_F_SG | NETIF_F_HW_CSUM;
> + const unsigned int payload_len = 3 * GSO_TEST_SIZE + 3;
> + struct sk_buff *skb, *segs, *cur, *next;
> + const unsigned int expected[] = {
> + 2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,
> + };
> + const unsigned int max_segs = 2;
> + int i = 0;
> +
> + if (!IS_ENABLED(CONFIG_INET))
> + kunit_skip(test, "requires CONFIG_INET");
> +
> + skb = gso_tcp_skb_new(payload_len);
> + if (!skb) {
> + KUNIT_FAIL(test, "no skb");
> + return;
> + }
> +
> + segs = __skb_gso_segment(skb, features, true, max_segs);
> + if (IS_ERR_OR_NULL(segs)) {
> + KUNIT_FAIL(test, "segs error %pe", segs);
> + consume_skb(skb);
> + return;
> + }
> +
> + for (cur = segs; cur; cur = next, i++) {
> + next = cur->next;
> +
> + if (i < ARRAY_SIZE(expected)) {
> + KUNIT_EXPECT_EQ(test, cur->len,
> + GSO_TCP_HDR_LEN + expected[i]);
> + KUNIT_EXPECT_EQ(test, skb_shinfo(cur)->gso_segs,
> + DIV_ROUND_UP(expected[i],
> + GSO_TEST_SIZE));
> + }
> + KUNIT_EXPECT_TRUE(test, skb_is_gso(cur));
> + KUNIT_EXPECT_EQ(test, skb_shinfo(cur)->gso_size,
> + GSO_TEST_SIZE);
> +
> + consume_skb(cur);
> + }
> +
> + KUNIT_EXPECT_EQ(test, i, ARRAY_SIZE(expected));
> + consume_skb(skb);
> +}
> +
> +#define GSO_TCP6_HDR_LEN \
> + (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr))
> +
> +static struct sk_buff *gso_tcp6_skb_new(unsigned int payload_len)
> +{
> + struct ipv6hdr *ip6h;
> + struct sk_buff *skb;
> + struct ethhdr *eth;
> + struct tcphdr *th;
> +
> + skb = alloc_skb(NET_SKB_PAD + GSO_TCP6_HDR_LEN + payload_len,
> + GFP_KERNEL);
> + if (!skb)
> + return NULL;
> + skb_reserve(skb, NET_SKB_PAD);
> + skb_put_zero(skb, GSO_TCP6_HDR_LEN + payload_len);
> +
> + skb_reset_mac_header(skb);
> + eth = eth_hdr(skb);
> + eth->h_proto = htons(ETH_P_IPV6);
> + skb->protocol = eth->h_proto;
> +
> + skb_set_network_header(skb, ETH_HLEN);
> + ip6h = ipv6_hdr(skb);
> + ip6h->version = 6;
> + ip6h->nexthdr = IPPROTO_TCP;
> + ip6h->payload_len = htons(sizeof(*th) + payload_len);
> +
> + skb_set_transport_header(skb, ETH_HLEN + sizeof(*ip6h));
> + th = tcp_hdr(skb);
> + th->doff = sizeof(*th) / 4;
> +
> + skb->ip_summed = CHECKSUM_PARTIAL;
> + skb->csum_start = skb_transport_header(skb) - skb->head;
> + skb->csum_offset = offsetof(struct tcphdr, check);
> + skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6;
> + skb_shinfo(skb)->gso_size = GSO_TEST_SIZE;
> + skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(payload_len, GSO_TEST_SIZE);
> +
> + return skb;
> +}
> +
> +/*
> + * The device GSO size limit is per L3 protocol, so only a lowered limit of
> + * the packet's own protocol may cost it the TSO bit, and a tag inside the
> + * frame, which replaces skb->protocol with the ethertype, must not change
> + * which limit applies. Takes ownership of @skb.
> + */
> +static void gso_test_tcp_l3_limit(struct kunit *test, struct net_device *dev,
> + struct sk_buff *skb, netdev_features_t tso,
> + bool ipv6)
> +{
> + unsigned int other = GSO_LEGACY_MAX_SIZE;
> + unsigned int own = GSO_MAX_SIZE;
> +
> + /* The skb fits the limit of its own L3 protocol, not the other's. */
> + dev->gso_max_size = ipv6 ? own : other;
> + dev->gso_ipv4_max_size = ipv6 ? other : own;
> + KUNIT_EXPECT_TRUE(test, netif_skb_features(skb) & tso);
> +
> + /* ...and the other way around. */
> + swap(own, other);
> + dev->gso_max_size = ipv6 ? own : other;
> + dev->gso_ipv4_max_size = ipv6 ? other : own;
> + KUNIT_EXPECT_FALSE(test, netif_skb_features(skb) & tso);
> +
> + skb = vlan_insert_tag_set_proto(skb, htons(ETH_P_8021Q), 0);
> + if (!skb) {
> + KUNIT_FAIL(test, "no tagged skb");
> + return;
> + }
> + KUNIT_EXPECT_TRUE(test, skb->protocol == htons(ETH_P_8021Q));
> +
> + swap(own, other);
> + dev->gso_max_size = ipv6 ? own : other;
> + dev->gso_ipv4_max_size = ipv6 ? other : own;
> + KUNIT_EXPECT_TRUE(test, netif_skb_features(skb) & tso);
> +
> + swap(own, other);
> + dev->gso_max_size = ipv6 ? own : other;
> + dev->gso_ipv4_max_size = ipv6 ? other : own;
> + KUNIT_EXPECT_FALSE(test, netif_skb_features(skb) & tso);
> +
> + consume_skb(skb);
> +}
> +
> +static void gso_test_tcp_limit_l3_proto(struct kunit *test)
> +{
> + static const struct net_device_ops dummy_netdev_ops = { };
> + const unsigned int payload_len = 100 * 1024;
> + struct net_device *dev;
> + struct sk_buff *skb;
> +
> + dev = alloc_etherdev(0);
> + if (!dev) {
> + KUNIT_FAIL(test, "no net_device");
> + return;
> + }
> + dev->netdev_ops = &dummy_netdev_ops;
> + dev->hw_features = NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_TSO |
> + NETIF_F_TSO6;
> + dev->features = dev->hw_features;
> + dev->vlan_features = dev->hw_features;
> +
> + skb = gso_tcp6_skb_new(payload_len);
> + if (!skb) {
> + KUNIT_FAIL(test, "no IPv6 skb");
> + goto free_dev;
> + }
> + skb->dev = dev;
> + gso_test_tcp_l3_limit(test, dev, skb, NETIF_F_TSO6, true);
> +
> + skb = gso_tcp_skb_new(payload_len);
> + if (!skb) {
> + KUNIT_FAIL(test, "no IPv4 skb");
> + goto free_dev;
> + }
> + skb->dev = dev;
> + gso_test_tcp_l3_limit(test, dev, skb, NETIF_F_TSO, false);
> +
> +free_dev:
> + free_netdev(dev);
> +}
> +
> +/*
> + * A re-segmented output is a plain GSO skb, so the whole length lands in the
> + * 16-bit L3 length field. A device which accepts GSO skbs above 64 KiB would
> + * otherwise be handed outputs whose length field truncates.
> + */
> +static void gso_test_tcp_resegment_l3_len(struct kunit *test)
> +{
> + static const struct net_device_ops dummy_netdev_ops = { };
> + const unsigned int payload_len = 200 * 1024;
> + struct sk_buff *skb, *segs, *cur, *next;
> + unsigned int nr_segs = 0;
> + struct net_device *dev;
> + bool again = false;
> +
> + if (!IS_ENABLED(CONFIG_INET))
> + kunit_skip(test, "requires CONFIG_INET");
> +
> + dev = alloc_etherdev(0);
> + if (!dev) {
> + KUNIT_FAIL(test, "no net_device");
> + return;
> + }
> + dev->netdev_ops = &dummy_netdev_ops;
> + dev->hw_features = NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_TSO;
> + dev->features = dev->hw_features;
> + dev->vlan_features = dev->hw_features;
> + dev->gso_ipv4_max_size = 120 * 1024;
> +
> + skb = gso_tcp_skb_new(payload_len);
> + if (!skb) {
> + KUNIT_FAIL(test, "no skb");
> + goto free_dev;
> + }
> + skb->dev = dev;
> +
> + segs = validate_xmit_skb_list(skb, dev, &again);
> + if (IS_ERR_OR_NULL(segs)) {
> + KUNIT_FAIL(test, "segs error %pe", segs);
> + goto free_dev;
> + }
> +
> + for (cur = segs; cur; cur = next, nr_segs++) {
> + next = cur->next;
> + cur->next = NULL;
> +
> + KUNIT_EXPECT_TRUE(test, skb_is_gso(cur));
> + KUNIT_EXPECT_EQ(test, ntohs(ip_hdr(cur)->tot_len),
> + cur->len - (skb_network_header(cur) -
> + skb_mac_header(cur)));
> + consume_skb(cur);
> + }
> +
> + KUNIT_EXPECT_EQ(test, nr_segs, 4);
> +
> +free_dev:
> + free_netdev(dev);
> +}
> +
> /* IP tunnel flags */
>
> #include <net/ip_tunnels.h>
> @@ -372,6 +720,9 @@ static void ip_tunnel_flags_test_run(struct kunit *test)
>
> static struct kunit_case net_test_cases[] = {
> KUNIT_CASE_PARAM(gso_test_func, gso_test_gen_params),
> + KUNIT_CASE(gso_test_tcp_resegment),
> + KUNIT_CASE(gso_test_tcp_limit_l3_proto),
> + KUNIT_CASE(gso_test_tcp_resegment_l3_len),
> KUNIT_CASE_PARAM(ip_tunnel_flags_test_run,
> ip_tunnel_flags_test_gen_params),
> { },
> --
> 2.47.3
>
next prev parent reply other threads:[~2026-09-30 19:53 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-30 11:15 [PATCH net-next v4 0/5] net: re-segment oversized TCP GSO skbs Wang Zhan
2026-09-30 11:15 ` [PATCH net-next v4 1/5] net: core: use the packet's L3 protocol for the GSO size limit Wang Zhan
2026-09-30 19:40 ` Willem de Bruijn
2026-09-30 11:15 ` [PATCH net-next v4 2/5] net: core: factor out the GSO device limit check Wang Zhan
2026-09-30 19:40 ` Willem de Bruijn
2026-09-30 11:15 ` [PATCH net-next v4 3/5] net: gso: support re-segmentation of TCP GSO skbs Wang Zhan
2026-09-30 19:52 ` Willem de Bruijn
2026-10-02 16:51 ` Wang Zhan
2026-10-02 11:16 ` netdev-bot+sashiko
2026-09-30 11:15 ` [PATCH net-next v4 4/5] net: core: re-segment oversized " Wang Zhan
2026-09-30 19:52 ` Willem de Bruijn
2026-10-02 11:16 ` netdev-bot+sashiko
2026-09-30 11:15 ` [PATCH net-next v4 5/5] net: net_test: add tests for TCP re-segmentation Wang Zhan
2026-09-30 19:53 ` Willem de Bruijn [this message]
2026-10-02 17:59 ` Wang Zhan
2026-10-03 19:10 ` Willem de Bruijn
2026-10-02 11:16 ` netdev-bot+sashiko
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=willemdebruijn.kernel.134bdb5c71ee0@gmail.com \
--to=willemdebruijn.kernel@gmail.com \
--cc=aconole@redhat.com \
--cc=alice@isovalent.com \
--cc=andrew+netdev@lunn.ch \
--cc=daniel@iogearbox.net \
--cc=davem@davemloft.net \
--cc=david.laight.linux@gmail.com \
--cc=dev@openvswitch.org \
--cc=echaudro@redhat.com \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=i.maximets@ovn.org \
--cc=jasowangio@gmail.com \
--cc=keyong.sun@smartx.com \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=ncardwell@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=wang.zhan@smartx.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.