AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/07 17:40 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "HSR"
  ],
  "FocusSymbols": [
    "hsr_forward_skb",
    "dev_disable_gro",
    "netif_disable_gro"
  ],
  "KMSANReasoning": "The patch modifies HSR (High-availability Seamless Redundancy) to handle GSO (Generic Segmentation Offload) super-packets by segmenting them before forwarding, and disables GRO (Generic Receive Offload) on enslaved devices. It also adjusts locking around sequence number increments. The changes primarily involve network packet processing logic, skb segmentation (`__skb_gso_segment`), and reading packet headers using the standard `skb_header_pointer` function. No new kernel structures are exposed to user space, and no complex data structures are allocated without initialization. Any potential bugs introduced by these changes, such as out-of-bounds memory accesses, use-after-free, or locking issues, would be effectively caught by standard KASAN and LOCKDEP. There is no risk of uninitialized memory usage or information leaks that would require a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces functional changes to the HSR (High-availability Seamless Redundancy) protocol to handle GSO super-packets by segmenting them in `hsr_forward_skb`. It also adds `dev_disable_gro` and `netif_disable_gro` to the core network device API to disable Generic Receive Offload (GRO) on a device, and uses this to disable GRO on HSR slave devices. These changes affect packet processing and network device configuration, which are reachable in virtualized environments and should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/08/07 17:40 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit c4da17e9661d9b3d3a54458b405e7fdf6ad162e2\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Aug 7 17:40:02 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/include/linux/netdevice.h b/include/linux/netdevice.h\nindex 9981d637f8b54..eba2c26a49ba8 100644\n--- a/include/linux/netdevice.h\n+++ b/include/linux/netdevice.h\n@@ -3434,6 +3434,8 @@ void dev_close(struct net_device *dev);\n void netif_close_many(struct list_head *head, bool unlink);\n void netif_disable_lro(struct net_device *dev);\n void dev_disable_lro(struct net_device *dev);\n+void netif_disable_gro(struct net_device *dev);\n+void dev_disable_gro(struct net_device *dev);\n int dev_loopback_xmit(struct net *net, struct sock *sk, struct sk_buff *newskb);\n u16 dev_pick_tx_zero(struct net_device *dev, struct sk_buff *skb,\n \t\t     struct net_device *sb_dev);\ndiff --git a/net/core/dev.c b/net/core/dev.c\nindex 5933c5dab09ee..f20d5ab0cf729 100644\n--- a/net/core/dev.c\n+++ b/net/core/dev.c\n@@ -1840,6 +1840,21 @@ void netif_disable_lro(struct net_device *dev)\n \t}\n }\n \n+void netif_disable_gro(struct net_device *dev)\n+{\n+\tstruct net_device *lower_dev;\n+\tstruct list_head *iter;\n+\n+\tdev-\u003ewanted_features \u0026= ~(NETIF_F_GRO | NETIF_F_GRO_HW);\n+\tnetdev_update_features(dev);\n+\n+\tnetdev_for_each_lower_dev(dev, lower_dev, iter) {\n+\t\tnetdev_lock_ops(lower_dev);\n+\t\tnetif_disable_gro(lower_dev);\n+\t\tnetdev_unlock_ops(lower_dev);\n+\t}\n+}\n+\n /**\n  *\tdev_disable_gro_hw - disable HW Generic Receive Offload on a device\n  *\t@dev: device\ndiff --git a/net/core/dev_api.c b/net/core/dev_api.c\nindex 437947dd08ed5..3ca2515ad0486 100644\n--- a/net/core/dev_api.c\n+++ b/net/core/dev_api.c\n@@ -269,6 +269,27 @@ void dev_disable_lro(struct net_device *dev)\n }\n EXPORT_SYMBOL(dev_disable_lro);\n \n+/**\n+ * dev_disable_gro() - disable Generic Receive Offload on a device\n+ * @dev: device\n+ *\n+ * Best-effort disable of Generic Receive Offload (GRO) on a net\n+ * device.  Must be called under RTNL.  This is needed if received\n+ * packets may be forwarded to another interface.\n+ *\n+ * The disable is best-effort: a device with a fixed-on feature (for\n+ * example GRO_HW on a virtio-net device negotiated without\n+ * VIRTIO_NET_F_CTRL_GUEST_OFFLOADS) keeps it enabled.  Callers that\n+ * need a hard guarantee must inspect the resulting feature state.\n+ */\n+void dev_disable_gro(struct net_device *dev)\n+{\n+\tnetdev_lock_ops(dev);\n+\tnetif_disable_gro(dev);\n+\tnetdev_unlock_ops(dev);\n+}\n+EXPORT_SYMBOL(dev_disable_gro);\n+\n /**\n  * dev_set_promiscuity() - update promiscuity count on a device\n  * @dev: device\ndiff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c\nindex 5555b71ab19b5..248cbb142e21e 100644\n--- a/net/hsr/hsr_device.c\n+++ b/net/hsr/hsr_device.c\n@@ -232,9 +232,7 @@ static netdev_tx_t hsr_dev_xmit(struct sk_buff *skb, struct net_device *dev)\n \t\tskb-\u003edev = master-\u003edev;\n \t\tskb_reset_mac_header(skb);\n \t\tskb_reset_mac_len(skb);\n-\t\tspin_lock_bh(\u0026hsr-\u003eseqnr_lock);\n \t\thsr_forward_skb(skb, master);\n-\t\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n \t} else {\n \t\tdev_core_stats_tx_dropped_inc(dev);\n \t\tdev_kfree_skb_any(skb);\n@@ -335,6 +333,7 @@ static void send_hsr_supervision_frame(struct hsr_port *port,\n \t\thsr_stag-\u003esequence_nr = htons(hsr-\u003esequence_nr);\n \t\thsr-\u003esequence_nr++;\n \t}\n+\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n \n \thsr_stag-\u003etlv.HSR_TLV_type = type;\n \t/* HSRv0 has 6 unused bytes after the MAC */\n@@ -356,14 +355,10 @@ static void send_hsr_supervision_frame(struct hsr_port *port,\n \t\tether_addr_copy(hsr_sp-\u003emacaddress_A, hsr-\u003emacaddress_redbox);\n \t}\n \n-\tif (skb_put_padto(skb, ETH_ZLEN)) {\n-\t\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n+\tif (skb_put_padto(skb, ETH_ZLEN))\n \t\treturn;\n-\t}\n \n \thsr_forward_skb(skb, port);\n-\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n-\treturn;\n }\n \n static void send_prp_supervision_frame(struct hsr_port *master,\n@@ -390,6 +385,7 @@ static void send_prp_supervision_frame(struct hsr_port *master,\n \tspin_lock_bh(\u0026hsr-\u003eseqnr_lock);\n \thsr_stag-\u003esequence_nr = htons(hsr-\u003esup_sequence_nr);\n \thsr-\u003esup_sequence_nr++;\n+\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n \thsr_stag-\u003etlv.HSR_TLV_type = PRP_TLV_LIFE_CHECK_DD;\n \thsr_stag-\u003etlv.HSR_TLV_length = sizeof(struct hsr_sup_payload);\n \n@@ -397,13 +393,10 @@ static void send_prp_supervision_frame(struct hsr_port *master,\n \thsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload));\n \tether_addr_copy(hsr_sp-\u003emacaddress_A, master-\u003edev-\u003edev_addr);\n \n-\tif (skb_put_padto(skb, ETH_ZLEN)) {\n-\t\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n+\tif (skb_put_padto(skb, ETH_ZLEN))\n \t\treturn;\n-\t}\n \n \thsr_forward_skb(skb, master);\n-\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n }\n \n /* Announce (supervision frame) timer function\n@@ -659,7 +652,7 @@ void hsr_dev_setup(struct net_device *dev)\n \tdev-\u003eneeds_free_netdev = true;\n \n \tdev-\u003ehw_features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA |\n-\t\t\t   NETIF_F_GSO_MASK | NETIF_F_HW_CSUM |\n+\t\t\t   NETIF_F_HW_CSUM |\n \t\t\t   NETIF_F_HW_VLAN_CTAG_TX |\n \t\t\t   NETIF_F_HW_VLAN_CTAG_FILTER;\n \ndiff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c\nindex 0774981a65c16..f42694cf43099 100644\n--- a/net/hsr/hsr_forward.c\n+++ b/net/hsr/hsr_forward.c\n@@ -12,6 +12,7 @@\n #include \u003clinux/skbuff.h\u003e\n #include \u003clinux/etherdevice.h\u003e\n #include \u003clinux/if_vlan.h\u003e\n+#include \u003cnet/gso.h\u003e\n #include \"hsr_main.h\"\n #include \"hsr_framereg.h\"\n \n@@ -621,9 +622,10 @@ static void handle_std_frame(struct sk_buff *skb,\n \tif (port-\u003etype == HSR_PT_MASTER ||\n \t    port-\u003etype == HSR_PT_INTERLINK) {\n \t\t/* Sequence nr for the master/interlink node */\n-\t\tlockdep_assert_held(\u0026hsr-\u003eseqnr_lock);\n+\t\tspin_lock_bh(\u0026hsr-\u003eseqnr_lock);\n \t\tframe-\u003esequence_nr = hsr-\u003esequence_nr;\n \t\thsr-\u003esequence_nr++;\n+\t\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n \t}\n }\n \n@@ -731,7 +733,7 @@ static int fill_frame_info(struct hsr_frame_info *frame,\n }\n \n /* Must be called holding rcu read lock (because of the port parameter) */\n-void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)\n+static void hsr_forward_skb_one(struct sk_buff *skb, struct hsr_port *port)\n {\n \tstruct hsr_frame_info frame;\n \n@@ -746,8 +748,8 @@ void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)\n \t * So check and increment stats for master port only here.\n \t */\n \tif (port-\u003etype == HSR_PT_MASTER || port-\u003etype == HSR_PT_INTERLINK) {\n-\t\tport-\u003edev-\u003estats.tx_packets++;\n-\t\tport-\u003edev-\u003estats.tx_bytes += skb-\u003elen;\n+\t\tDEV_STATS_INC(port-\u003edev, tx_packets);\n+\t\tDEV_STATS_ADD(port-\u003edev, tx_bytes, skb-\u003elen);\n \t}\n \n \tkfree_skb(frame.skb_hsr);\n@@ -757,6 +759,105 @@ void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)\n \n out_drop:\n \trcu_read_unlock();\n-\tport-\u003edev-\u003estats.tx_dropped++;\n+\tDEV_STATS_INC(port-\u003edev, tx_dropped);\n+\tkfree_skb(skb);\n+}\n+\n+/* GSO fan-out funnel: unfold super-packets before per-frame processing so\n+ * each wire frame gets its own HSR/PRP tag and sequence number.\n+ */\n+/* Effective frame protocol of a (possibly VLAN-tagged) skb, or 0 when\n+ * it cannot be determined or the tagging exceeds what HSR supports.\n+ * HSR supports one 802.1Q C-tag only, matching fill_frame_info(): an\n+ * accelerated tag must be a C-tag with a non-VLAN inner protocol; an\n+ * in-band tag is unwrapped exactly once and a residual VLAN EtherType\n+ * is rejected. Read-only; no state is kept beyond the immediate\n+ * protocol value.\n+ */\n+static __be16 hsr_gso_effective_proto(const struct sk_buff *skb)\n+{\n+\tstruct ethhdr eh;\n+\tstruct vlan_hdr vh;\n+\tconst struct ethhdr *eth;\n+\tconst struct vlan_hdr *vhdr;\n+\t__be16 proto;\n+\n+\tif (skb_vlan_tag_present(skb)) {\n+\t\t/* HSR supports one 802.1Q C-tag only. */\n+\t\tif (skb-\u003evlan_proto != htons(ETH_P_8021Q))\n+\t\t\treturn 0;\n+\t\tif (eth_type_vlan(skb-\u003eprotocol))\n+\t\t\treturn 0;\n+\t\treturn skb-\u003eprotocol;\n+\t}\n+\n+\teth = skb_header_pointer(skb, 0, sizeof(eh), \u0026eh);\n+\tif (!eth)\n+\t\treturn 0;\n+\n+\tproto = eth-\u003eh_proto;\n+\tif (!eth_type_vlan(proto))\n+\t\treturn proto;\n+\tif (proto != htons(ETH_P_8021Q))\n+\t\treturn 0;\n+\n+\tvhdr = skb_header_pointer(skb, ETH_HLEN, sizeof(vh), \u0026vh);\n+\tif (!vhdr)\n+\t\treturn 0;\n+\n+\tproto = vhdr-\u003eh_vlan_encapsulated_proto;\n+\tif (eth_type_vlan(proto))\n+\t\treturn 0;\n+\n+\treturn proto;\n+}\n+\n+void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)\n+{\n+\tstruct sk_buff *segs, *next;\n+\t__be16 proto;\n+\n+\tif (likely(!skb_is_gso(skb))) {\n+\t\thsr_forward_skb_one(skb, port);\n+\t\treturn;\n+\t}\n+\n+\t/* Conforming plain-protocol GSO super-packets carry trailer-free\n+\t * sender payload and are segmented here: each segment is delivered\n+\t * or forwarded as its own wire frame, on any ingress role.\n+\t *\n+\t * The gate is content-based, not port-based. An aggregate whose\n+\t * effective protocol is ETH_P_HSR or ETH_P_PRP cannot be safely\n+\t * segmented and is dropped, as is any skb whose header cannot be\n+\t * read or whose tagging exceeds the single 802.1Q C-tag HSR\n+\t * supports. With NETIF_F_HW_HSR_TAG_RM the lower has already\n+\t * stripped the tag, so such aggregates arrive plain and are\n+\t * segmented.\n+\t */\n+\tproto = hsr_gso_effective_proto(skb);\n+\tif (!proto)\n+\t\tgoto drop_gso; /* classification failure, fail-safe */\n+\tif (proto == htons(ETH_P_HSR) || proto == htons(ETH_P_PRP))\n+\t\tgoto drop_gso;\n+\n+\t/* features = 0: request full software segmentation. tx_path is true\n+\t * only for locally generated traffic on the master; ingress from\n+\t * the interlink follows RX checksum semantics.\n+\t */\n+\tsegs = __skb_gso_segment(skb, 0, port-\u003etype == HSR_PT_MASTER);\n+\tif (IS_ERR(segs) || unlikely(!segs))\n+\t\tgoto drop_gso;\n+\n+\tconsume_skb(skb);\n+\twhile (segs) {\n+\t\tnext = segs-\u003enext;\n+\t\tsegs-\u003enext = NULL;\n+\t\thsr_forward_skb_one(segs, port);\n+\t\tsegs = next;\n+\t}\n+\treturn;\n+\n+drop_gso:\n+\tDEV_STATS_INC(port-\u003edev, tx_dropped);\n \tkfree_skb(skb);\n }\ndiff --git a/net/hsr/hsr_slave.c b/net/hsr/hsr_slave.c\nindex 01c73b4b50ddd..aa2154b19c0b9 100644\n--- a/net/hsr/hsr_slave.c\n+++ b/net/hsr/hsr_slave.c\n@@ -73,16 +73,7 @@ static rx_handler_result_t hsr_handle_frame(struct sk_buff **pskb)\n \t}\n \tskb_reset_mac_len(skb);\n \n-\t/* Only the frames received over the interlink port will assign a\n-\t * sequence number and require synchronisation vs other sender.\n-\t */\n-\tif (port-\u003etype == HSR_PT_INTERLINK) {\n-\t\tspin_lock_bh(\u0026hsr-\u003eseqnr_lock);\n-\t\thsr_forward_skb(skb, port);\n-\t\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n-\t} else {\n-\t\thsr_forward_skb(skb, port);\n-\t}\n+\thsr_forward_skb(skb, port);\n \n finish_consume:\n \treturn RX_HANDLER_CONSUMED;\n@@ -171,6 +162,11 @@ static int hsr_portdev_setup(struct hsr_priv *hsr, struct net_device *dev,\n \t\tgoto fail_rx_handler;\n \tdev_disable_lro(dev);\n \n+\t/* GRO disabling is best-effort: devices with fixed-on\n+\t * GRO/GRO_HW cannot be forced off.\n+\t */\n+\tdev_disable_gro(dev);\n+\n \treturn 0;\n \n fail_rx_handler:\ndiff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile\nindex 31fb9326cf533..0d105476e7c50 100644\n--- a/tools/testing/selftests/net/hsr/Makefile\n+++ b/tools/testing/selftests/net/hsr/Makefile\n@@ -3,6 +3,7 @@\n top_srcdir = ../../../../..\n \n TEST_PROGS := \\\n+\thsr_gro_superpacket.sh \\\n \thsr_ping.sh \\\n \thsr_redbox.sh \\\n \tlink_faults.sh \\\ndiff --git a/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh b/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh\nnew file mode 100755\nindex 0000000000000..ebcbd9fc41ec4\n--- /dev/null\n+++ b/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh\n@@ -0,0 +1,631 @@\n+#!/bin/bash\n+# SPDX-License-Identifier: GPL-2.0\n+#\n+# Test HSR handling of GRO/GSO super-packets:\n+#\n+#  1. Enslaving a device to an HSR master disables GRO on it\n+#     (dev_disable_gro()).\n+#  2. The HSR master does not advertise GSO/TSO features.\n+#  3. A TCP stream from a TSO-enabled SAN (which therefore emits GSO\n+#     super-packets) is unfolded at the HSR forward entry. Evidence:\n+#     interface-counter deltas show super-packet-sized frames leaving\n+#     the SAN and per-frame-sized traffic leaving the DUT's LAN ports.\n+#\n+# Topology (100.64.0.0/24):\n+#\n+#   ns_san                    ns_dut                      ns_peer\n+#  +-----------+  interlink  +---------------+  LAN A/B  +-----------+\n+#  | s0 [0.1]  |-------------| d_il   hsr0   |-----------| hsr1 [0.3]|\n+#  +-----------+             | d_a / d_b     |           | p_a / p_b |\n+#                            +---------------+           +-----------+\n+#\n+# SAN traffic reaches ns_peer only through hsr0's forward path\n+# (interlink RX -\u003e LAN A/B TX), so every SAN frame is tagged and\n+# forwarded by the DUT.\n+\n+source ./hsr_common.sh\n+\n+san_ip=\"100.64.0.1\"\n+peer_ip=\"100.64.0.3\"\n+\n+# Aggregate counter thresholds for the stream test (bytes/packets):\n+# SAN_AVG_MIN proves GSO super-packets left the SAN; LAN_AVG_MAX is a\n+# guard with margin, not the protocol maximum (see do_tso_stream_test).\n+SAN_AVG_MIN=2048\n+LAN_AVG_MAX=1514\n+\n+iperf_pid=\"\"\n+server_wrapper=\"\"\n+active_srv_ns=\"\"\n+workdir=\"\"\n+pidfile=\"\"\n+ns_dut=\"\"\n+ns_san=\"\"\n+ns_peer=\"\"\n+ns_ls=\"\"\n+rcfile=\"\"\n+\n+# Delete the per-server private work directory and reset its\n+# variables. Called after a successful reap and from the EXIT\n+# trap, which owns all failure paths.\n+cleanup_workdir()\n+{\n+\t# remove only the known non-empty private directory\n+\tif [ -n \"${workdir}\" ] \u0026\u0026 [ -d \"${workdir}\" ]; then\n+\t\trm -rf \"${workdir}\"\n+\tfi\n+\tworkdir=\"\"\n+\tpidfile=\"\"\n+\trcfile=\"\"\n+}\n+\n+cleanup()\n+{\n+\t# Server cleanup targets only the namespace of the currently\n+\t# active server (recorded by start_iperf_server); after a\n+\t# successful reap nothing is active.\n+\tif [ -n \"${active_srv_ns}\" ]; then\n+\t\t# exact-PID kill only after RE-validating identity (guards\n+\t\t# against PID reuse between publication and cleanup)\n+\t\tif [ -n \"${iperf_pid}\" ] \u0026\u0026 valid_server_pid \"${active_srv_ns}\" \"${iperf_pid}\"; then\n+\t\t\tkill \"${iperf_pid}\" 2\u003e/dev/null\n+\t\tfi\n+\t\tiperf_pid=\"\"\n+\t\tif [ -n \"${server_wrapper}\" ]; then\n+\t\t\t# the wrapper waits on the server; reap it with a 5s\n+\t\t\t# bound so a live-but-unpublished server can never\n+\t\t\t# hang cleanup\n+\t\t\tfor _ in $(seq 1 50); do\n+\t\t\t\tkill -0 \"${server_wrapper}\" 2\u003e/dev/null || break\n+\t\t\t\tsleep 0.1\n+\t\t\tdone\n+\t\t\tkill \"${server_wrapper}\" 2\u003e/dev/null\n+\t\t\twait \"${server_wrapper}\" 2\u003e/dev/null\n+\t\t\tserver_wrapper=\"\"\n+\t\tfi\n+\t\t# last resort, namespace-scoped only: TERM the iperf3\n+\t\t# processes that actually live in the active server netns,\n+\t\t# poll for bounded exit, then SIGKILL any survivor before\n+\t\t# touching the namespace name. A blind pkill would scan\n+\t\t# the host PID space and hit unrelated tests.\n+\t\tlocal _p _still\n+\t\tfor _p in $(ip netns pids \"${active_srv_ns}\" 2\u003e/dev/null); do\n+\t\t\tif is_iperf3_pid \"$_p\"; then\n+\t\t\t\tkill \"$_p\" 2\u003e/dev/null\n+\t\t\tfi\n+\t\tdone\n+\t\tfor _ in $(seq 1 50); do\n+\t\t\t_still=0\n+\t\t\tfor _p in $(ip netns pids \"${active_srv_ns}\" 2\u003e/dev/null); do\n+\t\t\t\tif is_iperf3_pid \"$_p\"; then\n+\t\t\t\t\t_still=1\n+\t\t\t\t\tbreak\n+\t\t\t\tfi\n+\t\t\tdone\n+\t\t\t[ \"$_still\" -eq 0 ] \u0026\u0026 break\n+\t\t\tsleep 0.1\n+\t\tdone\n+\t\tfor _p in $(ip netns pids \"${active_srv_ns}\" 2\u003e/dev/null); do\n+\t\t\tif is_iperf3_pid \"$_p\"; then\n+\t\t\t\tkill -9 \"$_p\" 2\u003e/dev/null\n+\t\t\tfi\n+\t\tdone\n+\tfi\n+\tactive_srv_ns=\"\"\n+\tcleanup_workdir\n+\tcleanup_all_ns\n+}\n+\n+trap cleanup EXIT\n+\n+check_tool()\n+{\n+\tif ! command -v \"$1\" \u003e /dev/null 2\u003e\u00261; then\n+\t\techo \"SKIP: Could not run test without $1\"\n+\t\texit $ksft_skip\n+\tfi\n+}\n+\n+nsx()\n+{\n+\tip netns exec \"$1\" bash -c \"$2\"\n+}\n+\n+is_iperf3_pid()\n+{\n+\t[ \"$(cat /proc/\"$1\"/comm 2\u003e/dev/null)\" = \"iperf3\" ]\n+}\n+\n+# Bounded reap of the one-shot server: wait at most 5s for it to\n+# exit, then reap the wrapper and REQUIRE the rcfile with its real\n+# status. A stuck server can never hang the script.\n+reap_iperf_server()\n+{\n+\tlocal server_rc\n+\n+\tfor _ in $(seq 1 50); do\n+\t\tkill -0 \"${iperf_pid}\" 2\u003e/dev/null || break\n+\t\tsleep 0.1\n+\tdone\n+\tif kill -0 \"${iperf_pid}\" 2\u003e/dev/null; then\n+\t\techo \"FAIL: iperf3 server did not exit within 5s\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn 1\n+\tfi\n+\twait \"${server_wrapper}\"\n+\tserver_wrapper=\"\"\n+\tif [ ! -s \"${rcfile}\" ]; then\n+\t\techo \"FAIL: iperf3 server status file missing (${rcfile})\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn 1\n+\tfi\n+\tserver_rc=$(cat \"${rcfile}\")\n+\tif ! [[ \"$server_rc\" =~ ^[0-9]+$ ]] || [ \"$server_rc\" -ne 0 ]; then\n+\t\techo \"FAIL: iperf3 server exited with rc='${server_rc}'\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn 1\n+\tfi\n+\tiperf_pid=\"\"\n+\tcleanup_workdir\n+\tactive_srv_ns=\"\"\n+\treturn 0\n+}\n+\n+# Decimal-counter validation for the snapshot blocks: every value must\n+# be a plain decimal number. A parse failure in read_tx_counters yields\n+# empty/garbled fields, which this check turns into an immediate FAIL.\n+valid_decimals()\n+{\n+\tlocal v\n+\n+\tfor v in \"$@\"; do\n+\t\t[[ \"$v\" =~ ^[0-9]+$ ]] || return 1\n+\tdone\n+\treturn 0\n+}\n+\n+setup_topo()\n+{\n+\tsetup_ns ns_dut ns_san ns_peer || exit $?\n+\n+\tip link add d_a netns \"$ns_dut\" type veth peer name p_a netns \"$ns_peer\"\n+\tip link add d_b netns \"$ns_dut\" type veth peer name p_b netns \"$ns_peer\"\n+\tip link add d_il netns \"$ns_dut\" type veth peer name s0 netns \"$ns_san\"\n+\n+\t# HSR tags add 6 bytes per frame; give the LAN legs headroom.\n+\tfor iface in d_a d_b; do\n+\t\tnsx \"$ns_dut\" \"ip link set $iface mtu 1600; \\\n+\t\t\tip link set $iface up\"\n+\tdone\n+\tfor iface in p_a p_b; do\n+\t\tnsx \"$ns_peer\" \"ip link set $iface mtu 1600; \\\n+\t\t\tip link set $iface up\"\n+\tdone\n+\n+\tnsx \"$ns_dut\" \"ip link set d_il up\"\n+\tnsx \"$ns_san\" \"ip link set s0 up; ip addr add $san_ip/24 dev s0\"\n+\n+\tnsx \"$ns_dut\" \"ip link add hsr0 type hsr \\\n+\t\tslave1 d_a slave2 d_b interlink d_il proto 0; \\\n+\t\tip link set hsr0 up\"\n+\tnsx \"$ns_peer\" \"ip link add hsr1 type hsr \\\n+\t\tslave1 p_a slave2 p_b proto 0; \\\n+\t\tip link set hsr1 up; ip addr add $peer_ip/24 dev hsr1\"\n+\n+\t# Let the nodes see each other's supervision frames.\n+\tsleep 2\n+}\n+\n+check_feature()\n+{\n+\tlocal ns=\"$1\"\n+\tlocal iface=\"$2\"\n+\tlocal feature=\"$3\"\n+\tlocal want=\"$4\"\n+\n+\tif nsx \"$ns\" \"ethtool -k $iface\" | grep -q \"^$feature: $want\"; then\n+\t\techo \"INFO: $ns/$iface $feature is $want [ OK ]\"\n+\telse\n+\t\techo \"FAIL: $ns/$iface $feature is not $want\" 1\u003e\u00262\n+\t\tret=1\n+\tfi\n+}\n+\n+# Off-or-absent variant: fails only when the feature is present AND on,\n+# so devices that simply do not list the feature do not fail it.\n+check_feature_not_on()\n+{\n+\tlocal ns=\"$1\"\n+\tlocal iface=\"$2\"\n+\tlocal feature=\"$3\"\n+\n+\tif nsx \"$ns\" \"ethtool -k $iface\" | grep -q \"^$feature: on\"; then\n+\t\techo \"FAIL: $ns/$iface $feature is on\" 1\u003e\u00262\n+\t\tret=1\n+\telse\n+\t\techo \"INFO: $ns/$iface $feature not on [ OK ]\"\n+\tfi\n+}\n+\n+do_gro_feature_checks()\n+{\n+\techo \"INFO: Checking that enslavement disabled GRO.\"\n+\tcheck_feature \"$ns_dut\" d_a generic-receive-offload off\n+\tcheck_feature \"$ns_dut\" d_b generic-receive-offload off\n+\tcheck_feature \"$ns_dut\" d_il generic-receive-offload off\n+\tstop_if_error \"GRO not disabled on enslaved devices.\"\n+\n+\techo \"INFO: Checking that enslavement disabled HW-GRO.\"\n+\tcheck_feature \"$ns_dut\" d_a rx-gro-hw off\n+\tcheck_feature \"$ns_dut\" d_b rx-gro-hw off\n+\tcheck_feature \"$ns_dut\" d_il rx-gro-hw off\n+\tstop_if_error \"HW-GRO not disabled on enslaved devices.\"\n+\n+\techo \"INFO: Checking that the HSR master does not advertise GSO/TSO.\"\n+\tcheck_feature \"$ns_dut\" hsr0 generic-segmentation-offload off\n+\tcheck_feature \"$ns_dut\" hsr0 tcp-segmentation-offload off\n+\tcheck_feature_not_on \"$ns_dut\" hsr0 tx-udp-segmentation\n+\tcheck_feature_not_on \"$ns_dut\" hsr0 tx-gso-list\n+\tstop_if_error \"HSR master still advertises GSO-family features.\"\n+}\n+\n+alloc_workdir()\n+{\n+\t# Allocated only here, long after the initial topology cleanup, so\n+\t# cleanup() at setup_topo() time can never remove it. mktemp failure\n+\t# is a hard test failure.\n+\tworkdir=$(mktemp -d /tmp/hsr_gro_test.XXXXXX) || {\n+\t\techo \"FAIL: mktemp -d failed\" 1\u003e\u00262\n+\t\texit 1\n+\t}\n+\tchmod 700 \"${workdir}\"\n+\tpidfile=\"${workdir}/iperf.pid\"\n+\trcfile=\"${workdir}/iperf.rc\"\n+}\n+\n+# Numeric, alive, comm == iperf3, and really owned by the given netns.\n+valid_server_pid()\n+{\n+\tlocal pns=\"$1\" p=\"$2\"\n+\n+\t[[ \"$p\" =~ ^[0-9]+$ ]] || return 1\n+\tkill -0 \"$p\" 2\u003e/dev/null || return 1\n+\t[ \"$(cat /proc/\"$p\"/comm 2\u003e/dev/null)\" = \"iperf3\" ] || return 1\n+\tip netns pids \"$pns\" 2\u003e/dev/null | grep -qx \"$p\"\n+}\n+\n+start_iperf_server()\n+{\n+\tlocal srv_ns=\"$1\"\n+\tlocal srv_ip=\"${2:-}\"\n+\tlocal candidate_pid\n+\n+\t# One-shot server, no -D: the wrapper records its exact PID and its\n+\t# real exit status (netns shares the PID namespace and the host fs).\n+\talloc_workdir\n+\t# record the server namespace for cleanup() before\n+\t# anything can fail with the server running\n+\tactive_srv_ns=\"$srv_ns\"\n+\t( nsx \"$srv_ns\" \"iperf3 -s -1 ${srv_ip:+-B $srv_ip} \u003e /dev/null 2\u003e\u00261 \u0026 \\\n+\t\techo \\$! \u003e ${pidfile}; \\\n+\t\twait \\$!; \\\n+\t\techo \\$? \u003e ${rcfile}\" ) \u0026\n+\tserver_wrapper=$!\n+\t# the wrapper writes the pidfile asynchronously; wait for it to\n+\t# appear instead of racing the read\n+\tfor _ in $(seq 1 50); do\n+\t\t[ -s \"${pidfile}\" ] \u0026\u0026 break\n+\t\tsleep 0.1\n+\tdone\n+\tif [ ! -s \"${pidfile}\" ]; then\n+\t\techo \"FAIL: iperf3 server did not publish a pid\" \\\n+\t\t\t\"(no pidfile)\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn 1\n+\tfi\n+\tcandidate_pid=$(\u003c\"${pidfile}\")\n+\tif ! valid_server_pid \"$srv_ns\" \"${candidate_pid}\"; then\n+\t\techo \"FAIL: iperf3 server pid '${candidate_pid}'\" \\\n+\t\t\t\"failed validation\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn 1\n+\tfi\n+\t# publish only after full validation\n+\tiperf_pid=\"${candidate_pid}\"\n+\tsleep 1\n+\treturn 0\n+}\n+\n+# Print \"\u003cbytes\u003e \u003cpackets\u003e\" for exactly one TX record of ns/dev; anything\n+# else (missing, duplicated, non-numeric) is a hard FAIL.\n+read_tx_counters()\n+{\n+\tlocal ns=\"$1\" dev=\"$2\"\n+\tlocal out cnt\n+\n+\tout=$(nsx \"$ns\" \"ip -s link show $dev\" | \\\n+\t\tawk '/^ +TX:/{getline; print $1, $2}')\n+\tcnt=$(echo \"$out\" | grep -c '^[0-9]* [0-9]*$')\n+\tif [ \"$cnt\" -ne 1 ]; then\n+\t\techo \"FAIL: cannot parse TX counters of $ns/$dev\" \\\n+\t\t\t\"(records=$cnt)\" 1\u003e\u00262\n+\t\treturn 1\n+\tfi\n+\techo \"$out\"\n+\treturn 0\n+}\n+\n+# Print \"\u003cbytes\u003e \u003cpackets\u003e\" for exactly one RX record of ns/dev; the\n+# same single-record discipline as read_tx_counters.\n+read_rx_counters()\n+{\n+\tlocal ns=\"$1\" dev=\"$2\"\n+\tlocal out cnt\n+\n+\tout=$(nsx \"$ns\" \"ip -s link show $dev\" | \\\n+\t\tawk '/^ +RX:/{getline; print $1, $2}')\n+\tcnt=$(echo \"$out\" | grep -c '^[0-9]* [0-9]*$')\n+\tif [ \"$cnt\" -ne 1 ]; then\n+\t\techo \"FAIL: cannot parse RX counters of $ns/$dev\" \\\n+\t\t\t\"(records=$cnt)\" 1\u003e\u00262\n+\t\treturn 1\n+\tfi\n+\techo \"$out\"\n+\treturn 0\n+}\n+\n+eval_counter_delta()\n+{\n+\tlocal name=\"$1\" b0=\"$2\" p0=\"$3\" b1=\"$4\" p1=\"$5\" op=\"$6\" limit=\"$7\"\n+\tlocal bd pd\n+\n+\tif ! [[ \"$b0\" =~ ^[0-9]+$ \u0026\u0026 \"$b1\" =~ ^[0-9]+$ \u0026\u0026 \\\n+\t\t\"$p0\" =~ ^[0-9]+$ \u0026\u0026 \"$p1\" =~ ^[0-9]+$ ]]; then\n+\t\techo \"FAIL: non-numeric counter input for $name\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn 1\n+\tfi\n+\tbd=$((b1 - b0))\n+\tpd=$((p1 - p0))\n+\tif [ \"$bd\" -lt 0 ] || [ \"$pd\" -le 0 ]; then\n+\t\techo \"FAIL: counter delta invalid for $name\" \\\n+\t\t\t\"(bytes=$bd pkts=$pd)\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn 1\n+\tfi\n+\tif [ \"$op\" = \"gt\" ]; then\n+\t\tif [ \"$bd\" -le $((pd * limit)) ]; then\n+\t\t\techo \"FAIL: $name bytes/packets $bd/$pd \u003c= $limit\" 1\u003e\u00262\n+\t\t\tret=1\n+\t\t\treturn 1\n+\t\tfi\n+\telse\n+\t\tif [ \"$bd\" -gt $((pd * limit)) ]; then\n+\t\t\techo \"FAIL: $name bytes/packets $bd/$pd \u003e $limit\" 1\u003e\u00262\n+\t\t\tret=1\n+\t\t\treturn 1\n+\t\tfi\n+\tfi\n+\techo \"INFO: $name counter delta bytes=$bd packets=$pd\" \\\n+\t\t\"(op $op limit $limit) [ OK ]\"\n+\treturn 0\n+}\n+\n+# LAN-slave plain-GSO regression: a plain SAN aggregate entering a PRP\n+# LAN slave must be segmented at the forward entry, not dropped. PRP\n+# drops slave-to-slave forwarding by design (prp_drop_frame), so the\n+# consumer of LAN-slave SAN traffic is local delivery to the master.\n+# Two independent oracles: the SAN-side proof that aggregates really\n+# arrived at the DUT, and the volume + per-frame shape of the local\n+# delivery. On the broken gate the aggregates are dropped and TCP\n+# crawls on retransmitted single segments, separating the kernels by\n+# an order of magnitude in delivered bytes.\n+do_lansan_gso_test()\n+{\n+\tlocal prp_ip=\"10.99.1.1\" ls_ip=\"10.99.1.10\"\n+\tlocal out san_b0 san_p0 san_b1 san_p1\n+\tlocal a_b0 a_p0 a_b1 a_p1 r_b0 r_p0 r_b1 r_p1\n+\n+\tsetup_ns ns_ls || exit $?\n+\n+\tip link add d_pa netns \"$ns_dut\" type veth peer name ls_a netns \"$ns_ls\"\n+\tip link add d_pb netns \"$ns_dut\" type veth peer name ls_p netns \"$ns_ls\"\n+\n+\tnsx \"$ns_dut\" \"ip link set d_pa mtu 1600 up\"\n+\tnsx \"$ns_dut\" \"ip link set d_pb mtu 1600 up\"\n+\tnsx \"$ns_ls\" \"ip link set ls_a mtu 1600 up; \\\n+\t\tip link set ls_p mtu 1600 up; ip addr add $ls_ip/24 dev ls_a\"\n+\n+\t# PRP DUT with the SAN on a LAN slave: plain SAN aggregates are\n+\t# valid traffic on a PRP LAN and must not be dropped.\n+\tnsx \"$ns_dut\" \"ip link add prp0 type hsr \\\n+\t\tslave1 d_pa slave2 d_pb proto 1; \\\n+\t\tip link set prp0 up; ip addr add $prp_ip/24 dev prp0\"\n+\n+\t# Let supervision frames converge.\n+\tsleep 2\n+\n+\techo \"INFO: Enabling TSO/GSO on the LAN-side SAN interface.\"\n+\tnsx \"$ns_ls\" \"ethtool -K ls_a tso on gso on\"\n+\tcheck_feature \"$ns_ls\" ls_a tcp-segmentation-offload on\n+\tstop_if_error \"Could not enable TSO on the LAN-side SAN interface.\"\n+\n+\tstart_iperf_server \"$ns_dut\" \"$prp_ip\" || return\n+\n+\tread -r r_b0 r_p0 \u003c\u003cEOF\n+$(read_rx_counters \"$ns_dut\" prp0)\n+EOF\n+\tread -r san_b0 san_p0 \u003c\u003cEOF\n+$(read_tx_counters \"$ns_ls\" ls_a)\n+EOF\n+\tif ! valid_decimals \"$r_b0\" \"$r_p0\" \"$san_b0\" \"$san_p0\"; then\n+\t\techo \"FAIL: baseline counter snapshot invalid\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn 1\n+\tfi\n+\n+\tif ! out=$(nsx \"$ns_ls\" \"timeout 60 iperf3 -c $prp_ip -M 1446 \\\n+\t\t-b 2G -t 10\" 2\u003e\u00261); then\n+\t\techo \"FAIL: LAN-slave GSO local-delivery stream failed:\" 1\u003e\u00262\n+\t\techo \"$out\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn\n+\tfi\n+\n+\tread -r r_b1 r_p1 \u003c\u003cEOF\n+$(read_rx_counters \"$ns_dut\" prp0)\n+EOF\n+\tread -r san_b1 san_p1 \u003c\u003cEOF\n+$(read_tx_counters \"$ns_ls\" ls_a)\n+EOF\n+\tif ! valid_decimals \"$r_b1\" \"$r_p1\" \"$san_b1\" \"$san_p1\"; then\n+\t\techo \"FAIL: final counter snapshot invalid\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn 1\n+\tfi\n+\n+\t# Oracle 1 - aggregates really arrived: the SAN emitted\n+\t# super-packets toward the DUT.\n+\teval_counter_delta \"SAN ls_a TX\" \"$san_b0\" \"$san_p0\" \\\n+\t\t\"$san_b1\" \"$san_p1\" gt \"$SAN_AVG_MIN\"\n+\t[ \"${ret:-0}\" -eq 0 ] || return\n+\n+\t# Oracle 2 - local delivery of those aggregates: volume and\n+\t# per-frame shape on the PRP master. The volume gate separates\n+\t# full delivery from the retransmit crawl the broken gate leaves;\n+\t# the average gate proves the bytes arrived per-frame.\n+\tif [ $((r_b1 - r_b0)) -lt 100000000 ]; then\n+\t\techo \"FAIL: LAN-slave local delivery degraded\" \\\n+\t\t\t\"(prp0 RX delta $((r_b1 - r_b0)) bytes \u003c 100000000)\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn\n+\tfi\n+\tif [ $((r_b1 - r_b0)) -gt $(( (r_p1 - r_p0) * LAN_AVG_MAX )) ]; then\n+\t\techo \"FAIL: local delivery not per-frame\" \\\n+\t\t\t\"(avg $(( (r_b1 - r_b0) / (r_p1 - r_p0) )) \u003e $LAN_AVG_MAX)\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn\n+\tfi\n+\techo \"INFO: LAN-slave local delivery prp0 RX delta\" \\\n+\t\t\"$((r_b1 - r_b0)) bytes / $((r_p1 - r_p0)) pkts [ OK ]\"\n+\treap_iperf_server || return\n+\techo \"INFO: LAN-slave plain-GSO regression [ OK ]\"\n+}\n+\n+do_tso_stream_test()\n+{\n+\tlocal out sender_retr\n+\tlocal san_b0 san_p0 san_b1 san_p1\n+\tlocal a_b0 a_p0 a_b1 a_p1 b_b0 b_p0 b_b1 b_p1\n+\n+\techo \"INFO: Enabling TSO/GSO on the SAN interface.\"\n+\tnsx \"$ns_san\" \"ethtool -K s0 tso on gso on\"\n+\tcheck_feature \"$ns_san\" s0 tcp-segmentation-offload on\n+\tstop_if_error \"Could not enable TSO on the SAN interface.\"\n+\n+\techo \"INFO: Running 10s TCP stream SAN -\u003e peer through the HSR DUT.\"\n+\tstart_iperf_server \"$ns_peer\" || return\n+\n+\t# Counter snapshots around the stream window. The SAN-side average\n+\t# must exceed SAN_AVG_MIN (aggregate proof that GSO super-packets\n+\t# really left the SAN); each DUT LAN leg must stay under LAN_AVG_MAX\n+\t# (aggregate proof that bulk output was segmented per-frame). These\n+\t# are aggregate discriminators, not a per-frame maximum proof.\n+\tsan_b0=0; san_p0=0; a_b0=0; a_p0=0; b_b0=0; b_p0=0\n+\tread -r san_b0 san_p0 \u003c\u003cEOF\n+$(read_tx_counters \"$ns_san\" s0)\n+EOF\n+\tread -r a_b0 a_p0 \u003c\u003cEOF\n+$(read_tx_counters \"$ns_dut\" d_a)\n+EOF\n+\tread -r b_b0 b_p0 \u003c\u003cEOF\n+$(read_tx_counters \"$ns_dut\" d_b)\n+EOF\n+\tif ! valid_decimals \"$san_b0\" \"$san_p0\" \"$a_b0\" \"$a_p0\" \\\n+\t\t\"$b_b0\" \"$b_p0\"; then\n+\t\techo \"FAIL: baseline TX counter snapshot invalid\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn 1\n+\tfi\n+\n+\t# rate-capped: the PRIMARY discriminator is the counter inequality\n+\t# above, not max throughput; retransmits are informational only.\n+\t# Uncapped runs flap at VM/CI edge rates without indicating a\n+\t# functional problem.\n+\tif ! out=$(nsx \"$ns_san\" \"timeout 60 iperf3 -c $peer_ip -M 1446 \\\n+\t\t-b 2G -t 10\" 2\u003e\u00261); then\n+\t\techo \"FAIL: iperf3 client failed:\" 1\u003e\u00262\n+\t\techo \"$out\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn\n+\tfi\n+\n+\tread -r san_b1 san_p1 \u003c\u003cEOF\n+$(read_tx_counters \"$ns_san\" s0)\n+EOF\n+\tread -r a_b1 a_p1 \u003c\u003cEOF\n+$(read_tx_counters \"$ns_dut\" d_a)\n+EOF\n+\tread -r b_b1 b_p1 \u003c\u003cEOF\n+$(read_tx_counters \"$ns_dut\" d_b)\n+EOF\n+\tif ! valid_decimals \"$san_b1\" \"$san_p1\" \"$a_b1\" \"$a_p1\" \\\n+\t\t\"$b_b1\" \"$b_p1\"; then\n+\t\techo \"FAIL: final TX counter snapshot invalid\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn 1\n+\tfi\n+\n+\teval_counter_delta \"SAN s0 TX\" \"$san_b0\" \"$san_p0\" \"$san_b1\" \"$san_p1\" \\\n+\t\tgt \"$SAN_AVG_MIN\"\n+\teval_counter_delta \"DUT d_a TX\" \"$a_b0\" \"$a_p0\" \"$a_b1\" \"$a_p1\" \\\n+\t\tle \"$LAN_AVG_MAX\"\n+\teval_counter_delta \"DUT d_b TX\" \"$b_b0\" \"$b_p0\" \"$b_b1\" \"$b_p1\" \\\n+\t\tle \"$LAN_AVG_MAX\"\n+\t[ \"${ret:-0}\" -eq 0 ] || return\n+\n+\t# success path: bounded reap with the server's real status\n+\treap_iperf_server || return\n+\n+\t# secondary health signal only: anchored, single-match, numeric —\n+\t# any parse anomaly is a loud FAIL, but the value itself no longer\n+\t# gates (the counter inequalities above are the primary evidence).\n+\tsender_retr=$(echo \"$out\" | awk '/sec .* sender$/ {print $(NF-1)}')\n+\tif [ \"$(echo \"$sender_retr\" | grep -Ec '^[0-9]+$')\" -ne 1 ]; then\n+\t\techo \"FAIL: cannot parse sender retransmits reliably\" 1\u003e\u00262\n+\t\techo \"$out\" 1\u003e\u00262\n+\t\tret=1\n+\t\treturn\n+\tfi\n+\techo \"INFO: TCP stream done;\" \\\n+\t\t\"sender retransmits=$sender_retr (secondary signal)\"\n+\techo \"$out\" | grep -E \"sender|receiver\"\n+}\n+\n+check_prerequisites\n+check_tool ethtool\n+check_tool iperf3\n+check_tool timeout\n+\n+# iproute2 must know the HSR interlink syntax.\n+if ! ip link help hsr 2\u003e\u00261 | grep -qi interlink; then\n+\techo \"SKIP: iproute2 has no HSR interlink support\"\n+\texit $ksft_skip\n+fi\n+\n+setup_topo\n+\n+echo \"INFO: Initial validation ping (SAN -\u003e peer through the DUT).\"\n+do_ping \"$ns_san\" \"$peer_ip\"\n+stop_if_error \"Initial validation failed.\"\n+\n+do_gro_feature_checks\n+do_tso_stream_test\n+stop_if_error \"GSO super-packet stream test failed.\"\n+\n+do_lansan_gso_test\n+stop_if_error \"LAN-slave plain-GSO regression failed.\"\n+\n+echo \"INFO: All good.\"\n+cleanup\n+exit $ret\n"
}

2/1 2026/08/07 17:40 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "HSR"
  ],
  "FocusSymbols": [
    "hsr_forward_skb",
    "dev_disable_gro",
    "netif_disable_gro"
  ],
  "Reasoning": "The patch introduces functional changes to the HSR (High-availability Seamless Redundancy) protocol to handle GSO super-packets by segmenting them in `hsr_forward_skb`. It also adds `dev_disable_gro` and `netif_disable_gro` to the core network device API to disable Generic Receive Offload (GRO) on a device, and uses this to disable GRO on HSR slave devices. These changes affect packet processing and network device configuration, which are reachable in virtualized environments and should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit c4da17e9661d9b3d3a54458b405e7fdf6ad162e2
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Aug 7 17:40:02 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 9981d637f8b54..eba2c26a49ba8 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -3434,6 +3434,8 @@ void dev_close(struct net_device *dev);
 void netif_close_many(struct list_head *head, bool unlink);
 void netif_disable_lro(struct net_device *dev);
 void dev_disable_lro(struct net_device *dev);
+void netif_disable_gro(struct net_device *dev);
+void dev_disable_gro(struct net_device *dev);
 int dev_loopback_xmit(struct net *net, struct sock *sk, struct sk_buff *newskb);
 u16 dev_pick_tx_zero(struct net_device *dev, struct sk_buff *skb,
 		     struct net_device *sb_dev);
diff --git a/net/core/dev.c b/net/core/dev.c
index 5933c5dab09ee..f20d5ab0cf729 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1840,6 +1840,21 @@ void netif_disable_lro(struct net_device *dev)
 	}
 }
 
+void netif_disable_gro(struct net_device *dev)
+{
+	struct net_device *lower_dev;
+	struct list_head *iter;
+
+	dev->wanted_features &= ~(NETIF_F_GRO | NETIF_F_GRO_HW);
+	netdev_update_features(dev);
+
+	netdev_for_each_lower_dev(dev, lower_dev, iter) {
+		netdev_lock_ops(lower_dev);
+		netif_disable_gro(lower_dev);
+		netdev_unlock_ops(lower_dev);
+	}
+}
+
 /**
  *	dev_disable_gro_hw - disable HW Generic Receive Offload on a device
  *	@dev: device
diff --git a/net/core/dev_api.c b/net/core/dev_api.c
index 437947dd08ed5..3ca2515ad0486 100644
--- a/net/core/dev_api.c
+++ b/net/core/dev_api.c
@@ -269,6 +269,27 @@ void dev_disable_lro(struct net_device *dev)
 }
 EXPORT_SYMBOL(dev_disable_lro);
 
+/**
+ * dev_disable_gro() - disable Generic Receive Offload on a device
+ * @dev: device
+ *
+ * Best-effort disable of Generic Receive Offload (GRO) on a net
+ * device.  Must be called under RTNL.  This is needed if received
+ * packets may be forwarded to another interface.
+ *
+ * The disable is best-effort: a device with a fixed-on feature (for
+ * example GRO_HW on a virtio-net device negotiated without
+ * VIRTIO_NET_F_CTRL_GUEST_OFFLOADS) keeps it enabled.  Callers that
+ * need a hard guarantee must inspect the resulting feature state.
+ */
+void dev_disable_gro(struct net_device *dev)
+{
+	netdev_lock_ops(dev);
+	netif_disable_gro(dev);
+	netdev_unlock_ops(dev);
+}
+EXPORT_SYMBOL(dev_disable_gro);
+
 /**
  * dev_set_promiscuity() - update promiscuity count on a device
  * @dev: device
diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c
index 5555b71ab19b5..248cbb142e21e 100644
--- a/net/hsr/hsr_device.c
+++ b/net/hsr/hsr_device.c
@@ -232,9 +232,7 @@ static netdev_tx_t hsr_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 		skb->dev = master->dev;
 		skb_reset_mac_header(skb);
 		skb_reset_mac_len(skb);
-		spin_lock_bh(&hsr->seqnr_lock);
 		hsr_forward_skb(skb, master);
-		spin_unlock_bh(&hsr->seqnr_lock);
 	} else {
 		dev_core_stats_tx_dropped_inc(dev);
 		dev_kfree_skb_any(skb);
@@ -335,6 +333,7 @@ static void send_hsr_supervision_frame(struct hsr_port *port,
 		hsr_stag->sequence_nr = htons(hsr->sequence_nr);
 		hsr->sequence_nr++;
 	}
+	spin_unlock_bh(&hsr->seqnr_lock);
 
 	hsr_stag->tlv.HSR_TLV_type = type;
 	/* HSRv0 has 6 unused bytes after the MAC */
@@ -356,14 +355,10 @@ static void send_hsr_supervision_frame(struct hsr_port *port,
 		ether_addr_copy(hsr_sp->macaddress_A, hsr->macaddress_redbox);
 	}
 
-	if (skb_put_padto(skb, ETH_ZLEN)) {
-		spin_unlock_bh(&hsr->seqnr_lock);
+	if (skb_put_padto(skb, ETH_ZLEN))
 		return;
-	}
 
 	hsr_forward_skb(skb, port);
-	spin_unlock_bh(&hsr->seqnr_lock);
-	return;
 }
 
 static void send_prp_supervision_frame(struct hsr_port *master,
@@ -390,6 +385,7 @@ static void send_prp_supervision_frame(struct hsr_port *master,
 	spin_lock_bh(&hsr->seqnr_lock);
 	hsr_stag->sequence_nr = htons(hsr->sup_sequence_nr);
 	hsr->sup_sequence_nr++;
+	spin_unlock_bh(&hsr->seqnr_lock);
 	hsr_stag->tlv.HSR_TLV_type = PRP_TLV_LIFE_CHECK_DD;
 	hsr_stag->tlv.HSR_TLV_length = sizeof(struct hsr_sup_payload);
 
@@ -397,13 +393,10 @@ static void send_prp_supervision_frame(struct hsr_port *master,
 	hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload));
 	ether_addr_copy(hsr_sp->macaddress_A, master->dev->dev_addr);
 
-	if (skb_put_padto(skb, ETH_ZLEN)) {
-		spin_unlock_bh(&hsr->seqnr_lock);
+	if (skb_put_padto(skb, ETH_ZLEN))
 		return;
-	}
 
 	hsr_forward_skb(skb, master);
-	spin_unlock_bh(&hsr->seqnr_lock);
 }
 
 /* Announce (supervision frame) timer function
@@ -659,7 +652,7 @@ void hsr_dev_setup(struct net_device *dev)
 	dev->needs_free_netdev = true;
 
 	dev->hw_features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA |
-			   NETIF_F_GSO_MASK | NETIF_F_HW_CSUM |
+			   NETIF_F_HW_CSUM |
 			   NETIF_F_HW_VLAN_CTAG_TX |
 			   NETIF_F_HW_VLAN_CTAG_FILTER;
 
diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
index 0774981a65c16..f42694cf43099 100644
--- a/net/hsr/hsr_forward.c
+++ b/net/hsr/hsr_forward.c
@@ -12,6 +12,7 @@
 #include <linux/skbuff.h>
 #include <linux/etherdevice.h>
 #include <linux/if_vlan.h>
+#include <net/gso.h>
 #include "hsr_main.h"
 #include "hsr_framereg.h"
 
@@ -621,9 +622,10 @@ static void handle_std_frame(struct sk_buff *skb,
 	if (port->type == HSR_PT_MASTER ||
 	    port->type == HSR_PT_INTERLINK) {
 		/* Sequence nr for the master/interlink node */
-		lockdep_assert_held(&hsr->seqnr_lock);
+		spin_lock_bh(&hsr->seqnr_lock);
 		frame->sequence_nr = hsr->sequence_nr;
 		hsr->sequence_nr++;
+		spin_unlock_bh(&hsr->seqnr_lock);
 	}
 }
 
@@ -731,7 +733,7 @@ static int fill_frame_info(struct hsr_frame_info *frame,
 }
 
 /* Must be called holding rcu read lock (because of the port parameter) */
-void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)
+static void hsr_forward_skb_one(struct sk_buff *skb, struct hsr_port *port)
 {
 	struct hsr_frame_info frame;
 
@@ -746,8 +748,8 @@ void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)
 	 * So check and increment stats for master port only here.
 	 */
 	if (port->type == HSR_PT_MASTER || port->type == HSR_PT_INTERLINK) {
-		port->dev->stats.tx_packets++;
-		port->dev->stats.tx_bytes += skb->len;
+		DEV_STATS_INC(port->dev, tx_packets);
+		DEV_STATS_ADD(port->dev, tx_bytes, skb->len);
 	}
 
 	kfree_skb(frame.skb_hsr);
@@ -757,6 +759,105 @@ void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)
 
 out_drop:
 	rcu_read_unlock();
-	port->dev->stats.tx_dropped++;
+	DEV_STATS_INC(port->dev, tx_dropped);
+	kfree_skb(skb);
+}
+
+/* GSO fan-out funnel: unfold super-packets before per-frame processing so
+ * each wire frame gets its own HSR/PRP tag and sequence number.
+ */
+/* Effective frame protocol of a (possibly VLAN-tagged) skb, or 0 when
+ * it cannot be determined or the tagging exceeds what HSR supports.
+ * HSR supports one 802.1Q C-tag only, matching fill_frame_info(): an
+ * accelerated tag must be a C-tag with a non-VLAN inner protocol; an
+ * in-band tag is unwrapped exactly once and a residual VLAN EtherType
+ * is rejected. Read-only; no state is kept beyond the immediate
+ * protocol value.
+ */
+static __be16 hsr_gso_effective_proto(const struct sk_buff *skb)
+{
+	struct ethhdr eh;
+	struct vlan_hdr vh;
+	const struct ethhdr *eth;
+	const struct vlan_hdr *vhdr;
+	__be16 proto;
+
+	if (skb_vlan_tag_present(skb)) {
+		/* HSR supports one 802.1Q C-tag only. */
+		if (skb->vlan_proto != htons(ETH_P_8021Q))
+			return 0;
+		if (eth_type_vlan(skb->protocol))
+			return 0;
+		return skb->protocol;
+	}
+
+	eth = skb_header_pointer(skb, 0, sizeof(eh), &eh);
+	if (!eth)
+		return 0;
+
+	proto = eth->h_proto;
+	if (!eth_type_vlan(proto))
+		return proto;
+	if (proto != htons(ETH_P_8021Q))
+		return 0;
+
+	vhdr = skb_header_pointer(skb, ETH_HLEN, sizeof(vh), &vh);
+	if (!vhdr)
+		return 0;
+
+	proto = vhdr->h_vlan_encapsulated_proto;
+	if (eth_type_vlan(proto))
+		return 0;
+
+	return proto;
+}
+
+void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)
+{
+	struct sk_buff *segs, *next;
+	__be16 proto;
+
+	if (likely(!skb_is_gso(skb))) {
+		hsr_forward_skb_one(skb, port);
+		return;
+	}
+
+	/* Conforming plain-protocol GSO super-packets carry trailer-free
+	 * sender payload and are segmented here: each segment is delivered
+	 * or forwarded as its own wire frame, on any ingress role.
+	 *
+	 * The gate is content-based, not port-based. An aggregate whose
+	 * effective protocol is ETH_P_HSR or ETH_P_PRP cannot be safely
+	 * segmented and is dropped, as is any skb whose header cannot be
+	 * read or whose tagging exceeds the single 802.1Q C-tag HSR
+	 * supports. With NETIF_F_HW_HSR_TAG_RM the lower has already
+	 * stripped the tag, so such aggregates arrive plain and are
+	 * segmented.
+	 */
+	proto = hsr_gso_effective_proto(skb);
+	if (!proto)
+		goto drop_gso; /* classification failure, fail-safe */
+	if (proto == htons(ETH_P_HSR) || proto == htons(ETH_P_PRP))
+		goto drop_gso;
+
+	/* features = 0: request full software segmentation. tx_path is true
+	 * only for locally generated traffic on the master; ingress from
+	 * the interlink follows RX checksum semantics.
+	 */
+	segs = __skb_gso_segment(skb, 0, port->type == HSR_PT_MASTER);
+	if (IS_ERR(segs) || unlikely(!segs))
+		goto drop_gso;
+
+	consume_skb(skb);
+	while (segs) {
+		next = segs->next;
+		segs->next = NULL;
+		hsr_forward_skb_one(segs, port);
+		segs = next;
+	}
+	return;
+
+drop_gso:
+	DEV_STATS_INC(port->dev, tx_dropped);
 	kfree_skb(skb);
 }
diff --git a/net/hsr/hsr_slave.c b/net/hsr/hsr_slave.c
index 01c73b4b50ddd..aa2154b19c0b9 100644
--- a/net/hsr/hsr_slave.c
+++ b/net/hsr/hsr_slave.c
@@ -73,16 +73,7 @@ static rx_handler_result_t hsr_handle_frame(struct sk_buff **pskb)
 	}
 	skb_reset_mac_len(skb);
 
-	/* Only the frames received over the interlink port will assign a
-	 * sequence number and require synchronisation vs other sender.
-	 */
-	if (port->type == HSR_PT_INTERLINK) {
-		spin_lock_bh(&hsr->seqnr_lock);
-		hsr_forward_skb(skb, port);
-		spin_unlock_bh(&hsr->seqnr_lock);
-	} else {
-		hsr_forward_skb(skb, port);
-	}
+	hsr_forward_skb(skb, port);
 
 finish_consume:
 	return RX_HANDLER_CONSUMED;
@@ -171,6 +162,11 @@ static int hsr_portdev_setup(struct hsr_priv *hsr, struct net_device *dev,
 		goto fail_rx_handler;
 	dev_disable_lro(dev);
 
+	/* GRO disabling is best-effort: devices with fixed-on
+	 * GRO/GRO_HW cannot be forced off.
+	 */
+	dev_disable_gro(dev);
+
 	return 0;
 
 fail_rx_handler:
diff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile
index 31fb9326cf533..0d105476e7c50 100644
--- a/tools/testing/selftests/net/hsr/Makefile
+++ b/tools/testing/selftests/net/hsr/Makefile
@@ -3,6 +3,7 @@
 top_srcdir = ../../../../..
 
 TEST_PROGS := \
+	hsr_gro_superpacket.sh \
 	hsr_ping.sh \
 	hsr_redbox.sh \
 	link_faults.sh \
diff --git a/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh b/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh
new file mode 100755
index 0000000000000..ebcbd9fc41ec4
--- /dev/null
+++ b/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh
@@ -0,0 +1,631 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test HSR handling of GRO/GSO super-packets:
+#
+#  1. Enslaving a device to an HSR master disables GRO on it
+#     (dev_disable_gro()).
+#  2. The HSR master does not advertise GSO/TSO features.
+#  3. A TCP stream from a TSO-enabled SAN (which therefore emits GSO
+#     super-packets) is unfolded at the HSR forward entry. Evidence:
+#     interface-counter deltas show super-packet-sized frames leaving
+#     the SAN and per-frame-sized traffic leaving the DUT's LAN ports.
+#
+# Topology (100.64.0.0/24):
+#
+#   ns_san                    ns_dut                      ns_peer
+#  +-----------+  interlink  +---------------+  LAN A/B  +-----------+
+#  | s0 [0.1]  |-------------| d_il   hsr0   |-----------| hsr1 [0.3]|
+#  +-----------+             | d_a / d_b     |           | p_a / p_b |
+#                            +---------------+           +-----------+
+#
+# SAN traffic reaches ns_peer only through hsr0's forward path
+# (interlink RX -> LAN A/B TX), so every SAN frame is tagged and
+# forwarded by the DUT.
+
+source ./hsr_common.sh
+
+san_ip="100.64.0.1"
+peer_ip="100.64.0.3"
+
+# Aggregate counter thresholds for the stream test (bytes/packets):
+# SAN_AVG_MIN proves GSO super-packets left the SAN; LAN_AVG_MAX is a
+# guard with margin, not the protocol maximum (see do_tso_stream_test).
+SAN_AVG_MIN=2048
+LAN_AVG_MAX=1514
+
+iperf_pid=""
+server_wrapper=""
+active_srv_ns=""
+workdir=""
+pidfile=""
+ns_dut=""
+ns_san=""
+ns_peer=""
+ns_ls=""
+rcfile=""
+
+# Delete the per-server private work directory and reset its
+# variables. Called after a successful reap and from the EXIT
+# trap, which owns all failure paths.
+cleanup_workdir()
+{
+	# remove only the known non-empty private directory
+	if [ -n "${workdir}" ] && [ -d "${workdir}" ]; then
+		rm -rf "${workdir}"
+	fi
+	workdir=""
+	pidfile=""
+	rcfile=""
+}
+
+cleanup()
+{
+	# Server cleanup targets only the namespace of the currently
+	# active server (recorded by start_iperf_server); after a
+	# successful reap nothing is active.
+	if [ -n "${active_srv_ns}" ]; then
+		# exact-PID kill only after RE-validating identity (guards
+		# against PID reuse between publication and cleanup)
+		if [ -n "${iperf_pid}" ] && valid_server_pid "${active_srv_ns}" "${iperf_pid}"; then
+			kill "${iperf_pid}" 2>/dev/null
+		fi
+		iperf_pid=""
+		if [ -n "${server_wrapper}" ]; then
+			# the wrapper waits on the server; reap it with a 5s
+			# bound so a live-but-unpublished server can never
+			# hang cleanup
+			for _ in $(seq 1 50); do
+				kill -0 "${server_wrapper}" 2>/dev/null || break
+				sleep 0.1
+			done
+			kill "${server_wrapper}" 2>/dev/null
+			wait "${server_wrapper}" 2>/dev/null
+			server_wrapper=""
+		fi
+		# last resort, namespace-scoped only: TERM the iperf3
+		# processes that actually live in the active server netns,
+		# poll for bounded exit, then SIGKILL any survivor before
+		# touching the namespace name. A blind pkill would scan
+		# the host PID space and hit unrelated tests.
+		local _p _still
+		for _p in $(ip netns pids "${active_srv_ns}" 2>/dev/null); do
+			if is_iperf3_pid "$_p"; then
+				kill "$_p" 2>/dev/null
+			fi
+		done
+		for _ in $(seq 1 50); do
+			_still=0
+			for _p in $(ip netns pids "${active_srv_ns}" 2>/dev/null); do
+				if is_iperf3_pid "$_p"; then
+					_still=1
+					break
+				fi
+			done
+			[ "$_still" -eq 0 ] && break
+			sleep 0.1
+		done
+		for _p in $(ip netns pids "${active_srv_ns}" 2>/dev/null); do
+			if is_iperf3_pid "$_p"; then
+				kill -9 "$_p" 2>/dev/null
+			fi
+		done
+	fi
+	active_srv_ns=""
+	cleanup_workdir
+	cleanup_all_ns
+}
+
+trap cleanup EXIT
+
+check_tool()
+{
+	if ! command -v "$1" > /dev/null 2>&1; then
+		echo "SKIP: Could not run test without $1"
+		exit $ksft_skip
+	fi
+}
+
+nsx()
+{
+	ip netns exec "$1" bash -c "$2"
+}
+
+is_iperf3_pid()
+{
+	[ "$(cat /proc/"$1"/comm 2>/dev/null)" = "iperf3" ]
+}
+
+# Bounded reap of the one-shot server: wait at most 5s for it to
+# exit, then reap the wrapper and REQUIRE the rcfile with its real
+# status. A stuck server can never hang the script.
+reap_iperf_server()
+{
+	local server_rc
+
+	for _ in $(seq 1 50); do
+		kill -0 "${iperf_pid}" 2>/dev/null || break
+		sleep 0.1
+	done
+	if kill -0 "${iperf_pid}" 2>/dev/null; then
+		echo "FAIL: iperf3 server did not exit within 5s" 1>&2
+		ret=1
+		return 1
+	fi
+	wait "${server_wrapper}"
+	server_wrapper=""
+	if [ ! -s "${rcfile}" ]; then
+		echo "FAIL: iperf3 server status file missing (${rcfile})" 1>&2
+		ret=1
+		return 1
+	fi
+	server_rc=$(cat "${rcfile}")
+	if ! [[ "$server_rc" =~ ^[0-9]+$ ]] || [ "$server_rc" -ne 0 ]; then
+		echo "FAIL: iperf3 server exited with rc='${server_rc}'" 1>&2
+		ret=1
+		return 1
+	fi
+	iperf_pid=""
+	cleanup_workdir
+	active_srv_ns=""
+	return 0
+}
+
+# Decimal-counter validation for the snapshot blocks: every value must
+# be a plain decimal number. A parse failure in read_tx_counters yields
+# empty/garbled fields, which this check turns into an immediate FAIL.
+valid_decimals()
+{
+	local v
+
+	for v in "$@"; do
+		[[ "$v" =~ ^[0-9]+$ ]] || return 1
+	done
+	return 0
+}
+
+setup_topo()
+{
+	setup_ns ns_dut ns_san ns_peer || exit $?
+
+	ip link add d_a netns "$ns_dut" type veth peer name p_a netns "$ns_peer"
+	ip link add d_b netns "$ns_dut" type veth peer name p_b netns "$ns_peer"
+	ip link add d_il netns "$ns_dut" type veth peer name s0 netns "$ns_san"
+
+	# HSR tags add 6 bytes per frame; give the LAN legs headroom.
+	for iface in d_a d_b; do
+		nsx "$ns_dut" "ip link set $iface mtu 1600; \
+			ip link set $iface up"
+	done
+	for iface in p_a p_b; do
+		nsx "$ns_peer" "ip link set $iface mtu 1600; \
+			ip link set $iface up"
+	done
+
+	nsx "$ns_dut" "ip link set d_il up"
+	nsx "$ns_san" "ip link set s0 up; ip addr add $san_ip/24 dev s0"
+
+	nsx "$ns_dut" "ip link add hsr0 type hsr \
+		slave1 d_a slave2 d_b interlink d_il proto 0; \
+		ip link set hsr0 up"
+	nsx "$ns_peer" "ip link add hsr1 type hsr \
+		slave1 p_a slave2 p_b proto 0; \
+		ip link set hsr1 up; ip addr add $peer_ip/24 dev hsr1"
+
+	# Let the nodes see each other's supervision frames.
+	sleep 2
+}
+
+check_feature()
+{
+	local ns="$1"
+	local iface="$2"
+	local feature="$3"
+	local want="$4"
+
+	if nsx "$ns" "ethtool -k $iface" | grep -q "^$feature: $want"; then
+		echo "INFO: $ns/$iface $feature is $want [ OK ]"
+	else
+		echo "FAIL: $ns/$iface $feature is not $want" 1>&2
+		ret=1
+	fi
+}
+
+# Off-or-absent variant: fails only when the feature is present AND on,
+# so devices that simply do not list the feature do not fail it.
+check_feature_not_on()
+{
+	local ns="$1"
+	local iface="$2"
+	local feature="$3"
+
+	if nsx "$ns" "ethtool -k $iface" | grep -q "^$feature: on"; then
+		echo "FAIL: $ns/$iface $feature is on" 1>&2
+		ret=1
+	else
+		echo "INFO: $ns/$iface $feature not on [ OK ]"
+	fi
+}
+
+do_gro_feature_checks()
+{
+	echo "INFO: Checking that enslavement disabled GRO."
+	check_feature "$ns_dut" d_a generic-receive-offload off
+	check_feature "$ns_dut" d_b generic-receive-offload off
+	check_feature "$ns_dut" d_il generic-receive-offload off
+	stop_if_error "GRO not disabled on enslaved devices."
+
+	echo "INFO: Checking that enslavement disabled HW-GRO."
+	check_feature "$ns_dut" d_a rx-gro-hw off
+	check_feature "$ns_dut" d_b rx-gro-hw off
+	check_feature "$ns_dut" d_il rx-gro-hw off
+	stop_if_error "HW-GRO not disabled on enslaved devices."
+
+	echo "INFO: Checking that the HSR master does not advertise GSO/TSO."
+	check_feature "$ns_dut" hsr0 generic-segmentation-offload off
+	check_feature "$ns_dut" hsr0 tcp-segmentation-offload off
+	check_feature_not_on "$ns_dut" hsr0 tx-udp-segmentation
+	check_feature_not_on "$ns_dut" hsr0 tx-gso-list
+	stop_if_error "HSR master still advertises GSO-family features."
+}
+
+alloc_workdir()
+{
+	# Allocated only here, long after the initial topology cleanup, so
+	# cleanup() at setup_topo() time can never remove it. mktemp failure
+	# is a hard test failure.
+	workdir=$(mktemp -d /tmp/hsr_gro_test.XXXXXX) || {
+		echo "FAIL: mktemp -d failed" 1>&2
+		exit 1
+	}
+	chmod 700 "${workdir}"
+	pidfile="${workdir}/iperf.pid"
+	rcfile="${workdir}/iperf.rc"
+}
+
+# Numeric, alive, comm == iperf3, and really owned by the given netns.
+valid_server_pid()
+{
+	local pns="$1" p="$2"
+
+	[[ "$p" =~ ^[0-9]+$ ]] || return 1
+	kill -0 "$p" 2>/dev/null || return 1
+	[ "$(cat /proc/"$p"/comm 2>/dev/null)" = "iperf3" ] || return 1
+	ip netns pids "$pns" 2>/dev/null | grep -qx "$p"
+}
+
+start_iperf_server()
+{
+	local srv_ns="$1"
+	local srv_ip="${2:-}"
+	local candidate_pid
+
+	# One-shot server, no -D: the wrapper records its exact PID and its
+	# real exit status (netns shares the PID namespace and the host fs).
+	alloc_workdir
+	# record the server namespace for cleanup() before
+	# anything can fail with the server running
+	active_srv_ns="$srv_ns"
+	( nsx "$srv_ns" "iperf3 -s -1 ${srv_ip:+-B $srv_ip} > /dev/null 2>&1 & \
+		echo \$! > ${pidfile}; \
+		wait \$!; \
+		echo \$? > ${rcfile}" ) &
+	server_wrapper=$!
+	# the wrapper writes the pidfile asynchronously; wait for it to
+	# appear instead of racing the read
+	for _ in $(seq 1 50); do
+		[ -s "${pidfile}" ] && break
+		sleep 0.1
+	done
+	if [ ! -s "${pidfile}" ]; then
+		echo "FAIL: iperf3 server did not publish a pid" \
+			"(no pidfile)" 1>&2
+		ret=1
+		return 1
+	fi
+	candidate_pid=$(<"${pidfile}")
+	if ! valid_server_pid "$srv_ns" "${candidate_pid}"; then
+		echo "FAIL: iperf3 server pid '${candidate_pid}'" \
+			"failed validation" 1>&2
+		ret=1
+		return 1
+	fi
+	# publish only after full validation
+	iperf_pid="${candidate_pid}"
+	sleep 1
+	return 0
+}
+
+# Print "<bytes> <packets>" for exactly one TX record of ns/dev; anything
+# else (missing, duplicated, non-numeric) is a hard FAIL.
+read_tx_counters()
+{
+	local ns="$1" dev="$2"
+	local out cnt
+
+	out=$(nsx "$ns" "ip -s link show $dev" | \
+		awk '/^ +TX:/{getline; print $1, $2}')
+	cnt=$(echo "$out" | grep -c '^[0-9]* [0-9]*$')
+	if [ "$cnt" -ne 1 ]; then
+		echo "FAIL: cannot parse TX counters of $ns/$dev" \
+			"(records=$cnt)" 1>&2
+		return 1
+	fi
+	echo "$out"
+	return 0
+}
+
+# Print "<bytes> <packets>" for exactly one RX record of ns/dev; the
+# same single-record discipline as read_tx_counters.
+read_rx_counters()
+{
+	local ns="$1" dev="$2"
+	local out cnt
+
+	out=$(nsx "$ns" "ip -s link show $dev" | \
+		awk '/^ +RX:/{getline; print $1, $2}')
+	cnt=$(echo "$out" | grep -c '^[0-9]* [0-9]*$')
+	if [ "$cnt" -ne 1 ]; then
+		echo "FAIL: cannot parse RX counters of $ns/$dev" \
+			"(records=$cnt)" 1>&2
+		return 1
+	fi
+	echo "$out"
+	return 0
+}
+
+eval_counter_delta()
+{
+	local name="$1" b0="$2" p0="$3" b1="$4" p1="$5" op="$6" limit="$7"
+	local bd pd
+
+	if ! [[ "$b0" =~ ^[0-9]+$ && "$b1" =~ ^[0-9]+$ && \
+		"$p0" =~ ^[0-9]+$ && "$p1" =~ ^[0-9]+$ ]]; then
+		echo "FAIL: non-numeric counter input for $name" 1>&2
+		ret=1
+		return 1
+	fi
+	bd=$((b1 - b0))
+	pd=$((p1 - p0))
+	if [ "$bd" -lt 0 ] || [ "$pd" -le 0 ]; then
+		echo "FAIL: counter delta invalid for $name" \
+			"(bytes=$bd pkts=$pd)" 1>&2
+		ret=1
+		return 1
+	fi
+	if [ "$op" = "gt" ]; then
+		if [ "$bd" -le $((pd * limit)) ]; then
+			echo "FAIL: $name bytes/packets $bd/$pd <= $limit" 1>&2
+			ret=1
+			return 1
+		fi
+	else
+		if [ "$bd" -gt $((pd * limit)) ]; then
+			echo "FAIL: $name bytes/packets $bd/$pd > $limit" 1>&2
+			ret=1
+			return 1
+		fi
+	fi
+	echo "INFO: $name counter delta bytes=$bd packets=$pd" \
+		"(op $op limit $limit) [ OK ]"
+	return 0
+}
+
+# LAN-slave plain-GSO regression: a plain SAN aggregate entering a PRP
+# LAN slave must be segmented at the forward entry, not dropped. PRP
+# drops slave-to-slave forwarding by design (prp_drop_frame), so the
+# consumer of LAN-slave SAN traffic is local delivery to the master.
+# Two independent oracles: the SAN-side proof that aggregates really
+# arrived at the DUT, and the volume + per-frame shape of the local
+# delivery. On the broken gate the aggregates are dropped and TCP
+# crawls on retransmitted single segments, separating the kernels by
+# an order of magnitude in delivered bytes.
+do_lansan_gso_test()
+{
+	local prp_ip="10.99.1.1" ls_ip="10.99.1.10"
+	local out san_b0 san_p0 san_b1 san_p1
+	local a_b0 a_p0 a_b1 a_p1 r_b0 r_p0 r_b1 r_p1
+
+	setup_ns ns_ls || exit $?
+
+	ip link add d_pa netns "$ns_dut" type veth peer name ls_a netns "$ns_ls"
+	ip link add d_pb netns "$ns_dut" type veth peer name ls_p netns "$ns_ls"
+
+	nsx "$ns_dut" "ip link set d_pa mtu 1600 up"
+	nsx "$ns_dut" "ip link set d_pb mtu 1600 up"
+	nsx "$ns_ls" "ip link set ls_a mtu 1600 up; \
+		ip link set ls_p mtu 1600 up; ip addr add $ls_ip/24 dev ls_a"
+
+	# PRP DUT with the SAN on a LAN slave: plain SAN aggregates are
+	# valid traffic on a PRP LAN and must not be dropped.
+	nsx "$ns_dut" "ip link add prp0 type hsr \
+		slave1 d_pa slave2 d_pb proto 1; \
+		ip link set prp0 up; ip addr add $prp_ip/24 dev prp0"
+
+	# Let supervision frames converge.
+	sleep 2
+
+	echo "INFO: Enabling TSO/GSO on the LAN-side SAN interface."
+	nsx "$ns_ls" "ethtool -K ls_a tso on gso on"
+	check_feature "$ns_ls" ls_a tcp-segmentation-offload on
+	stop_if_error "Could not enable TSO on the LAN-side SAN interface."
+
+	start_iperf_server "$ns_dut" "$prp_ip" || return
+
+	read -r r_b0 r_p0 <<EOF
+$(read_rx_counters "$ns_dut" prp0)
+EOF
+	read -r san_b0 san_p0 <<EOF
+$(read_tx_counters "$ns_ls" ls_a)
+EOF
+	if ! valid_decimals "$r_b0" "$r_p0" "$san_b0" "$san_p0"; then
+		echo "FAIL: baseline counter snapshot invalid" 1>&2
+		ret=1
+		return 1
+	fi
+
+	if ! out=$(nsx "$ns_ls" "timeout 60 iperf3 -c $prp_ip -M 1446 \
+		-b 2G -t 10" 2>&1); then
+		echo "FAIL: LAN-slave GSO local-delivery stream failed:" 1>&2
+		echo "$out" 1>&2
+		ret=1
+		return
+	fi
+
+	read -r r_b1 r_p1 <<EOF
+$(read_rx_counters "$ns_dut" prp0)
+EOF
+	read -r san_b1 san_p1 <<EOF
+$(read_tx_counters "$ns_ls" ls_a)
+EOF
+	if ! valid_decimals "$r_b1" "$r_p1" "$san_b1" "$san_p1"; then
+		echo "FAIL: final counter snapshot invalid" 1>&2
+		ret=1
+		return 1
+	fi
+
+	# Oracle 1 - aggregates really arrived: the SAN emitted
+	# super-packets toward the DUT.
+	eval_counter_delta "SAN ls_a TX" "$san_b0" "$san_p0" \
+		"$san_b1" "$san_p1" gt "$SAN_AVG_MIN"
+	[ "${ret:-0}" -eq 0 ] || return
+
+	# Oracle 2 - local delivery of those aggregates: volume and
+	# per-frame shape on the PRP master. The volume gate separates
+	# full delivery from the retransmit crawl the broken gate leaves;
+	# the average gate proves the bytes arrived per-frame.
+	if [ $((r_b1 - r_b0)) -lt 100000000 ]; then
+		echo "FAIL: LAN-slave local delivery degraded" \
+			"(prp0 RX delta $((r_b1 - r_b0)) bytes < 100000000)" 1>&2
+		ret=1
+		return
+	fi
+	if [ $((r_b1 - r_b0)) -gt $(( (r_p1 - r_p0) * LAN_AVG_MAX )) ]; then
+		echo "FAIL: local delivery not per-frame" \
+			"(avg $(( (r_b1 - r_b0) / (r_p1 - r_p0) )) > $LAN_AVG_MAX)" 1>&2
+		ret=1
+		return
+	fi
+	echo "INFO: LAN-slave local delivery prp0 RX delta" \
+		"$((r_b1 - r_b0)) bytes / $((r_p1 - r_p0)) pkts [ OK ]"
+	reap_iperf_server || return
+	echo "INFO: LAN-slave plain-GSO regression [ OK ]"
+}
+
+do_tso_stream_test()
+{
+	local out sender_retr
+	local san_b0 san_p0 san_b1 san_p1
+	local a_b0 a_p0 a_b1 a_p1 b_b0 b_p0 b_b1 b_p1
+
+	echo "INFO: Enabling TSO/GSO on the SAN interface."
+	nsx "$ns_san" "ethtool -K s0 tso on gso on"
+	check_feature "$ns_san" s0 tcp-segmentation-offload on
+	stop_if_error "Could not enable TSO on the SAN interface."
+
+	echo "INFO: Running 10s TCP stream SAN -> peer through the HSR DUT."
+	start_iperf_server "$ns_peer" || return
+
+	# Counter snapshots around the stream window. The SAN-side average
+	# must exceed SAN_AVG_MIN (aggregate proof that GSO super-packets
+	# really left the SAN); each DUT LAN leg must stay under LAN_AVG_MAX
+	# (aggregate proof that bulk output was segmented per-frame). These
+	# are aggregate discriminators, not a per-frame maximum proof.
+	san_b0=0; san_p0=0; a_b0=0; a_p0=0; b_b0=0; b_p0=0
+	read -r san_b0 san_p0 <<EOF
+$(read_tx_counters "$ns_san" s0)
+EOF
+	read -r a_b0 a_p0 <<EOF
+$(read_tx_counters "$ns_dut" d_a)
+EOF
+	read -r b_b0 b_p0 <<EOF
+$(read_tx_counters "$ns_dut" d_b)
+EOF
+	if ! valid_decimals "$san_b0" "$san_p0" "$a_b0" "$a_p0" \
+		"$b_b0" "$b_p0"; then
+		echo "FAIL: baseline TX counter snapshot invalid" 1>&2
+		ret=1
+		return 1
+	fi
+
+	# rate-capped: the PRIMARY discriminator is the counter inequality
+	# above, not max throughput; retransmits are informational only.
+	# Uncapped runs flap at VM/CI edge rates without indicating a
+	# functional problem.
+	if ! out=$(nsx "$ns_san" "timeout 60 iperf3 -c $peer_ip -M 1446 \
+		-b 2G -t 10" 2>&1); then
+		echo "FAIL: iperf3 client failed:" 1>&2
+		echo "$out" 1>&2
+		ret=1
+		return
+	fi
+
+	read -r san_b1 san_p1 <<EOF
+$(read_tx_counters "$ns_san" s0)
+EOF
+	read -r a_b1 a_p1 <<EOF
+$(read_tx_counters "$ns_dut" d_a)
+EOF
+	read -r b_b1 b_p1 <<EOF
+$(read_tx_counters "$ns_dut" d_b)
+EOF
+	if ! valid_decimals "$san_b1" "$san_p1" "$a_b1" "$a_p1" \
+		"$b_b1" "$b_p1"; then
+		echo "FAIL: final TX counter snapshot invalid" 1>&2
+		ret=1
+		return 1
+	fi
+
+	eval_counter_delta "SAN s0 TX" "$san_b0" "$san_p0" "$san_b1" "$san_p1" \
+		gt "$SAN_AVG_MIN"
+	eval_counter_delta "DUT d_a TX" "$a_b0" "$a_p0" "$a_b1" "$a_p1" \
+		le "$LAN_AVG_MAX"
+	eval_counter_delta "DUT d_b TX" "$b_b0" "$b_p0" "$b_b1" "$b_p1" \
+		le "$LAN_AVG_MAX"
+	[ "${ret:-0}" -eq 0 ] || return
+
+	# success path: bounded reap with the server's real status
+	reap_iperf_server || return
+
+	# secondary health signal only: anchored, single-match, numeric —
+	# any parse anomaly is a loud FAIL, but the value itself no longer
+	# gates (the counter inequalities above are the primary evidence).
+	sender_retr=$(echo "$out" | awk '/sec .* sender$/ {print $(NF-1)}')
+	if [ "$(echo "$sender_retr" | grep -Ec '^[0-9]+$')" -ne 1 ]; then
+		echo "FAIL: cannot parse sender retransmits reliably" 1>&2
+		echo "$out" 1>&2
+		ret=1
+		return
+	fi
+	echo "INFO: TCP stream done;" \
+		"sender retransmits=$sender_retr (secondary signal)"
+	echo "$out" | grep -E "sender|receiver"
+}
+
+check_prerequisites
+check_tool ethtool
+check_tool iperf3
+check_tool timeout
+
+# iproute2 must know the HSR interlink syntax.
+if ! ip link help hsr 2>&1 | grep -qi interlink; then
+	echo "SKIP: iproute2 has no HSR interlink support"
+	exit $ksft_skip
+fi
+
+setup_topo
+
+echo "INFO: Initial validation ping (SAN -> peer through the DUT)."
+do_ping "$ns_san" "$peer_ip"
+stop_if_error "Initial validation failed."
+
+do_gro_feature_checks
+do_tso_stream_test
+stop_if_error "GSO super-packet stream test failed."
+
+do_lansan_gso_test
+stop_if_error "LAN-slave plain-GSO regression failed."
+
+echo "INFO: All good."
+cleanup
+exit $ret

3/2 2026/08/07 17:40 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14162
							output: 30
							thoughts: 650
						

Thoughts:
**Analyzing the HSR GSO Super-Packet Patch**

Okay, so I'm looking at this patch that introduces `netif_disable_gro` and `dev_disable_gro` into the core networking code. This is a solid, functional addition to the device API, really.  The main thrust seems to be enabling HSR to properly handle GSO super-packets. The patch touches several HSR files: `hsr_device.c`, `hsr_forward.c`, and `hsr_slave.c`. I'm especially interested in the changes to `hsr_forward.c`.  Let's see...

Ah, yes, they've implemented GSO segmentation within `hsr_forward_skb`.  If the SKB isn't a GSO packet, it just forwards it as-is. Makes sense. But if it *is* a GSO packet, it's segmented using `__skb_gso_segment` and then each segment is forwarded individually. That's the correct approach. It looks like they're explicitly disabling GRO on HSR slave devices in `hsr_portdev_setup` too.  Makes sense, we can't have both GRO and segmentation.  The comment mentions that disabling GRO is "best-effort," which means the driver has to respect it. This is important to note and might need more discussion regarding hardware support.

This is a pretty significant architectural change, impacting both the core network device API (`dev_disable_gro`) and how HSR operates internally.

The key question in my mind is about testing. Can HSR be tested effectively in virtualized environments?  Good. They've added a selftest specifically for `hsr_gro_superpacket.sh`. That's a good sign – that means it's demonstrable. It seems like using veth devices should work, since HSR is essentially a software-defined protocol.  We should be able to replicate it like bonding or bridging.

Now, I should quickly verify the HSR configuration options. Let me go find `net/hsr/Kconfig` to confirm everything is where I expect it.



4/2 2026/08/07 17:40 tool grepper
0m Args:
{
  "Expression": "config HSR",
  "PathPrefix": "net/hsr/Kconfig"
}

Results:
{
  "Output": "net/hsr/Kconfig-5-\nnet/hsr/Kconfig:6:config HSR\nnet/hsr/Kconfig-7-\ttristate \"High-availability Seamless Redundancy (HSR \u0026 PRP)\"\n"
}

5/2 2026/08/07 17:40 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15366
							output: 40
							thoughts: 168
						

Thoughts:
**My Fuzzing Strategy for HSR**

Okay, so I'm looking at how to effectively fuzz the HSR implementation. The key config option, as I understand it, is `HSR`. Now, to find the most fruitful areas for fuzzing, I need to identify the crucial functions.

Right off the bat, `hsr_forward_skb` seems like a prime target. It's got the ring of a core processing function. Then there's `hsr_handle_frame`, which is the RX handler for HSR slave devices - also likely to be valuable. `hsr_dev_xmit` is the TX handler for the HSR master device, so that's definitely in scope. I see that `hsr_gso_effective_proto` is static, meaning I can't directly target it. However, it *is* called by `hsr_forward_skb`, so I need to keep that in mind during analysis, or by fuzzing `hsr_forward_skb` I will implicitly be testing `hsr_gso_effective_proto`. `dev_disable_gro` is exported, so it's callble by other modules, but it looks like a simple configuration function.

My next step is to examine `net/hsr/hsr_forward.c` and hunt for any exported or non-static functions within that file, as these are the ones I'll be able to directly target with my fuzzing efforts.



6/2 2026/08/07 17:40 tool grepper
0m Args:
{
  "Expression": "^void hsr_forward_skb",
  "PathPrefix": "net/hsr/hsr_forward.c"
}

Results:
{
  "Output": "net/hsr/hsr_forward.c=777=static __be16 hsr_gso_effective_proto(const struct sk_buff *skb)\n--\nnet/hsr/hsr_forward.c-814-\nnet/hsr/hsr_forward.c:815:void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)\nnet/hsr/hsr_forward.c-816-{\n"
}

7/2 2026/08/07 17:40 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16024
							output: 37
							thoughts: 26
						

Thoughts:
Here's my take on the thought process, as if it were my own:

**Investigating HSR Frame Handling**

Okay, so I'm digging into this HSR implementation and I've got a specific question burning in my mind. `hsr_forward_skb`... I noticed it's not declared as `static`. That immediately piques my interest. But what about the other major function involved in handling HSR frames, `hsr_handle_frame`? I need to clarify the scope and visibility of that one. Is it also accessible from outside the HSR module, or is it appropriately limited in its scope? Understanding the static/non-static status of these functions is key to understanding the module's structure and how it interfaces with the rest of the network stack. I need to figure out the implications of each function's visibility.



8/2 2026/08/07 17:40 tool grepper
0m Args:
{
  "Expression": "hsr_handle_frame",
  "PathPrefix": "net/hsr/hsr_slave.c"
}

Results:
{
  "Output": "net/hsr/hsr_slave.c=19=bool hsr_invalid_dan_ingress_frame(__be16 protocol)\n--\nnet/hsr/hsr_slave.c-23-\nnet/hsr/hsr_slave.c:24:static rx_handler_result_t hsr_handle_frame(struct sk_buff **pskb)\nnet/hsr/hsr_slave.c-25-{\n--\nnet/hsr/hsr_slave.c=85=bool hsr_port_exists(const struct net_device *dev)\nnet/hsr/hsr_slave.c-86-{\nnet/hsr/hsr_slave.c:87:\treturn rcu_access_pointer(dev-\u003erx_handler) == hsr_handle_frame;\nnet/hsr/hsr_slave.c-88-}\n--\nnet/hsr/hsr_slave.c=132=static int hsr_portdev_setup(struct hsr_priv *hsr, struct net_device *dev,\n--\nnet/hsr/hsr_slave.c-159-\nnet/hsr/hsr_slave.c:160:\tres = netdev_rx_handler_register(dev, hsr_handle_frame, port);\nnet/hsr/hsr_slave.c-161-\tif (res)\n"
}

9/2 2026/08/07 17:40 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16618
							output: 38
							thoughts: 35
						

Thoughts:
Here's a summary of my understanding, framed from my perspective as an expert:

**Internal HSR Frame Handling**

Okay, so I'm looking at the HSR (High-availability Seamless Redundancy) code and need to get a handle on how frames are managed. The key takeaway here is that `hsr_handle_frame` is declared as *static*. This immediately tells me it's a file-local function. It's not visible outside of the compilation unit it's defined in. That’s probably because it's an internal helper function, not part of the HSR API that's exposed to the rest of the network stack. It's doing something specific within the HSR device context. I'll need to look at where it's called and what arguments it takes to understand its purpose.

Then, there’s `hsr_dev_xmit`, which I'm seeing defined in `net/hsr/hsr_device.c`. That’s my primary point of interest. This function handles the actual transmission of frames from the HSR device. Given the name, I'd expect it to be the entry point for HSR devices to send frames. I should check if it handles any special HSR-specific header formatting or packet duplication, as HSR is about redundancy. It's likely involved in implementing the redundancy mechanisms. I bet this is where the HSR-specific logic resides for sending packets. I’ll dig into this function to understand the precise steps involved in transmitting HSR frames from a device perspective, including how it interfaces with the underlying network driver and, perhaps, the implications on performance.



10/2 2026/08/07 17:40 tool grepper
0m Args:
{
  "Expression": "hsr_dev_xmit",
  "PathPrefix": "net/hsr/hsr_device.c"
}

Results:
{
  "Output": "net/hsr/hsr_device.c=216=static netdev_features_t hsr_fix_features(struct net_device *dev,\n--\nnet/hsr/hsr_device.c-223-\nnet/hsr/hsr_device.c:224:static netdev_tx_t hsr_dev_xmit(struct sk_buff *skb, struct net_device *dev)\nnet/hsr/hsr_device.c-225-{\n--\nnet/hsr/hsr_device.c=598=static const struct net_device_ops hsr_device_ops = {\n--\nnet/hsr/hsr_device.c-601-\t.ndo_stop = hsr_dev_close,\nnet/hsr/hsr_device.c:602:\t.ndo_start_xmit = hsr_dev_xmit,\nnet/hsr/hsr_device.c-603-\t.ndo_change_rx_flags = hsr_change_rx_flags,\n--\nnet/hsr/hsr_device.c=664=bool is_hsr_master(struct net_device *dev)\nnet/hsr/hsr_device.c-665-{\nnet/hsr/hsr_device.c:666:\treturn (dev-\u003enetdev_ops-\u003endo_start_xmit == hsr_dev_xmit);\nnet/hsr/hsr_device.c-667-}\n"
}

11/2 2026/08/07 17:40 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17420
							output: 36
							thoughts: 69
						

Thoughts:
Here's my summary, as though I'm thinking through this:

**Focusing in on HSR Packet Transmission**

Okay, so I'm looking at how HSR packet transmission works, and I see `hsr_dev_xmit` is defined as static. That limits my ability to trace through the code, and suggests I need to find a better entry point, a symbol that's more accessible for deep analysis. Right, that makes `hsr_forward_skb` a much better focal point for further investigation. It's likely involved in the core forwarding logic, which is exactly what I'm interested in.

Now, while I'm at it, I should also make a note of `dev_disable_gro` and `netif_disable_gro`. They're probably in `net/core/dev.c` and `net/core/dev_api.c`. These are worth a look because GRO (Generic Receive Offload) can significantly impact packet processing, so any logic that interacts with it could be important for understanding performance or potential issues in the HSR context, especially if there are any subtle interactions with HSR's duplicate packet handling.



12/2 2026/08/07 17:40 tool grepper
0m Args:
{
  "Expression": "^void dev_disable_gro",
  "PathPrefix": "net/core/dev_api.c"
}

Results:
{
  "Output": "net/core/dev_api.c=270=EXPORT_SYMBOL(dev_disable_lro);\n--\nnet/core/dev_api.c-284- */\nnet/core/dev_api.c:285:void dev_disable_gro(struct net_device *dev)\nnet/core/dev_api.c-286-{\n"
}

13/2 2026/08/07 17:40 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17876
							output: 35
							thoughts: 19
						

Thoughts:
Here's my take on those thoughts:

**Dissecting `netif_disable_gro` in `net/core/dev.c`**

Alright, so I'm digging into `netif_disable_gro` within `net/core/dev.c`. Immediately, I'm thinking about the implications. This function, by its very name, is all about disabling Generic Receive Offload (GRO). That's a critical component, isn't it? It allows the network stack to coalesce multiple incoming packets into larger frames, reducing processing overhead, especially for high-bandwidth scenarios. So, what's triggering this disable? What are the conditions that necessitate sacrificing GRO's efficiency?

My mind is already racing with questions: Is this being called due to a specific hardware limitation? A software-related incompatibility with a particular protocol or driver? Are there potential performance bottlenecks that arise from using GRO in this specific context? I need to understand the surrounding code, the calling sites, the arguments passed... everything. The goal is to fully grasp the rationale behind disabling this key optimization. I need to figure out what tradeoffs the system is making here. What's the cost of this disable in terms of throughput, latency, and CPU cycles? Is it a permanent disable or a conditional one? Ultimately, I want to ensure that this is the *right* decision, a well-informed trade-off, and not a quick fix that introduces a hidden performance hit later. I'll be looking for any hints about the network device state or potential issues. This seems like a critical piece that could shed light on some underlying device behavior.



14/2 2026/08/07 17:40 tool grepper
0m Args:
{
  "Expression": "^void netif_disable_gro",
  "PathPrefix": "net/core/dev.c"
}

Results:
{
  "Output": "net/core/dev.c=1825=void netif_disable_lro(struct net_device *dev)\n--\nnet/core/dev.c-1842-\nnet/core/dev.c:1843:void netif_disable_gro(struct net_device *dev)\nnet/core/dev.c-1844-{\n"
}

15/2 2026/08/07 17:40 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18369
							output: 33
							thoughts: 63
						

Thoughts:
Okay, let's break this down.

**Diving into the GRO Disablement in HSR**

My primary concern right now revolves around the symbols `hsr_forward_skb`, `dev_disable_gro`, and `netif_disable_gro`. These are the key pieces of code I need to understand in the context of GRO (Generic Receive Offload) disablement, specifically as it relates to HSR (High-availability Seamless Redundancy). I need to determine *why* these are significant and, crucially, how they interact.

Beyond that, I need to identify if there are any other configuration options or symbols related to GRO disablement within the HSR context. My initial instinct is to check for anything related to `HSR` directly. It's the most obvious starting point.

Finally, a dependency analysis. Is there anything that `HSR` relies on, particularly in relation to this GRO disablement? Are there any upstream factors to consider? Knowing potential dependencies, and what those dependencies are is critical to a comprehensive understanding.



16/2 2026/08/07 17:40 tool read-file
0m Args:
{
  "File": "net/hsr/Kconfig",
  "FirstLine": 1,
  "LineCount": 20
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\t#\n   3:\t# IEC 62439-3 High-availability Seamless Redundancy\n   4:\t#\n   5:\t\n   6:\tconfig HSR\n   7:\t\ttristate \"High-availability Seamless Redundancy (HSR \u0026 PRP)\"\n   8:\t\thelp\n   9:\t\t  This enables IEC 62439 defined High-availability Seamless\n  10:\t\t  Redundancy (HSR) and Parallel Redundancy Protocol (PRP).\n  11:\t\n  12:\t\t  If you say Y here, then your Linux box will be able to act as a\n  13:\t\t  DANH (\"Doubly attached node implementing HSR\") or DANP (\"Doubly\n  14:\t\t  attached node implementing PRP\"). For this to work, your Linux box\n  15:\t\t  needs (at least) two physical Ethernet interfaces.\n  16:\t\n  17:\t\t  For DANH, it must be connected as a node in a ring network together\n  18:\t\t  with other HSR capable nodes. All Ethernet frames sent over the HSR\n  19:\t\t  device will be sent in both directions on the ring (over both slave\n  20:\t\t  ports), giving a redundant, instant fail-over network. Each HSR node\n"
}

17/2 2026/08/07 17:40 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19023
							output: 175
							thoughts: 312
						

Thoughts:
**My Assessment of the HSR Patch**

Okay, let's break this down. First, good, no new dependencies for the `HSR` module itself, that's always a plus. Now, looking at the changes… this patch is adding `netif_disable_gro` and `dev_disable_gro`. That immediately jumps out as core network device API modifications, which are always interesting. Specifically, the intent is to disable Generic Receive Offload (GRO) on a network device, and here, they're using it to shut down GRO on HSR slave devices. Smart. GRO can sometimes cause issues with complex protocols, so disabling it in specific contexts is a reasonable approach.

The crucial bit, though, is the modification to `hsr_forward_skb`. They're adapting it to handle GSO (Generic Segmentation Offload) packets by segmenting them. This suggests a fundamental shift in how HSR is dealing with potentially large packets, and directly impacts the core packet forwarding logic. That’s a key change to investigate. Then we have changes in `hsr_dev_xmit` and `send_hsr_supervision_frame` to adjust locking, which is a definite signal of potential concurrency issues they're addressing. Locking modifications always deserve scrutiny.

Functionally, we're looking at changes in the HSR protocol implementation and the network device API. Since HSR is commonly deployed in virtualized environments – think veth devices – these changes are definitely reachable and testable with virtual interfaces. That makes them perfect candidates for fuzzing. The key symbols to focus on during testing are `hsr_forward_skb`, `dev_disable_gro`, and `netif_disable_gro`. With the `HSR` config enabled, of course.

The patch is addressing the issue of GSO super-packets within the HSR protocol by segmenting them. Additionally it's ensuring that GRO, a potential performance optimization, isn't causing problems and is disabled on relevant HSR devices, which is a sound design choice. Since this deals with packet handling, it will be essential to test it to ensure it operates correctly in various scenarios to ensure network performance and stability are maintained. This patch needs a good fuzzing session, and a thorough review of the concurrency aspects of the locking adjustments.



18/2 2026/08/07 17:41 tool set-results
0m Args:
{
  "EnableConfigs": [
    "HSR"
  ],
  "FocusSymbols": [
    "hsr_forward_skb",
    "dev_disable_gro",
    "netif_disable_gro"
  ],
  "Reasoning": "The patch introduces functional changes to the HSR (High-availability Seamless Redundancy) protocol to handle GSO super-packets by segmenting them in `hsr_forward_skb`. It also adds `dev_disable_gro` and `netif_disable_gro` to the core network device API to disable Generic Receive Offload (GRO) on a device, and uses this to disable GRO on HSR slave devices. These changes affect packet processing and network device configuration, which are reachable in virtualized environments and should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "HSR"
  ],
  "FocusSymbols": [
    "hsr_forward_skb",
    "dev_disable_gro",
    "netif_disable_gro"
  ],
  "Reasoning": "The patch introduces functional changes to the HSR (High-availability Seamless Redundancy) protocol to handle GSO super-packets by segmenting them in `hsr_forward_skb`. It also adds `dev_disable_gro` and `netif_disable_gro` to the core network device API to disable Generic Receive Offload (GRO) on a device, and uses this to disable GRO on HSR slave devices. These changes affect packet processing and network device configuration, which are reachable in virtualized environments and should be fuzzed.",
  "WorthFuzzing": true
}

19/1 2026/08/07 17:41 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

20/2 2026/08/07 17:41 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch modifies HSR (High-availability Seamless Redundancy) to handle GSO (Generic Segmentation Offload) super-packets by segmenting them before forwarding, and disables GRO (Generic Receive Offload) on enslaved devices. It also adjusts locking around sequence number increments. The changes primarily involve network packet processing logic, skb segmentation (`__skb_gso_segment`), and reading packet headers using the standard `skb_header_pointer` function. No new kernel structures are exposed to user space, and no complex data structures are allocated without initialization. Any potential bugs introduced by these changes, such as out-of-bounds memory accesses, use-after-free, or locking issues, would be effectively caught by standard KASAN and LOCKDEP. There is no risk of uninitialized memory usage or information leaks that would require a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit c4da17e9661d9b3d3a54458b405e7fdf6ad162e2
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Aug 7 17:40:02 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 9981d637f8b54..eba2c26a49ba8 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -3434,6 +3434,8 @@ void dev_close(struct net_device *dev);
 void netif_close_many(struct list_head *head, bool unlink);
 void netif_disable_lro(struct net_device *dev);
 void dev_disable_lro(struct net_device *dev);
+void netif_disable_gro(struct net_device *dev);
+void dev_disable_gro(struct net_device *dev);
 int dev_loopback_xmit(struct net *net, struct sock *sk, struct sk_buff *newskb);
 u16 dev_pick_tx_zero(struct net_device *dev, struct sk_buff *skb,
 		     struct net_device *sb_dev);
diff --git a/net/core/dev.c b/net/core/dev.c
index 5933c5dab09ee..f20d5ab0cf729 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1840,6 +1840,21 @@ void netif_disable_lro(struct net_device *dev)
 	}
 }
 
+void netif_disable_gro(struct net_device *dev)
+{
+	struct net_device *lower_dev;
+	struct list_head *iter;
+
+	dev->wanted_features &= ~(NETIF_F_GRO | NETIF_F_GRO_HW);
+	netdev_update_features(dev);
+
+	netdev_for_each_lower_dev(dev, lower_dev, iter) {
+		netdev_lock_ops(lower_dev);
+		netif_disable_gro(lower_dev);
+		netdev_unlock_ops(lower_dev);
+	}
+}
+
 /**
  *	dev_disable_gro_hw - disable HW Generic Receive Offload on a device
  *	@dev: device
diff --git a/net/core/dev_api.c b/net/core/dev_api.c
index 437947dd08ed5..3ca2515ad0486 100644
--- a/net/core/dev_api.c
+++ b/net/core/dev_api.c
@@ -269,6 +269,27 @@ void dev_disable_lro(struct net_device *dev)
 }
 EXPORT_SYMBOL(dev_disable_lro);
 
+/**
+ * dev_disable_gro() - disable Generic Receive Offload on a device
+ * @dev: device
+ *
+ * Best-effort disable of Generic Receive Offload (GRO) on a net
+ * device.  Must be called under RTNL.  This is needed if received
+ * packets may be forwarded to another interface.
+ *
+ * The disable is best-effort: a device with a fixed-on feature (for
+ * example GRO_HW on a virtio-net device negotiated without
+ * VIRTIO_NET_F_CTRL_GUEST_OFFLOADS) keeps it enabled.  Callers that
+ * need a hard guarantee must inspect the resulting feature state.
+ */
+void dev_disable_gro(struct net_device *dev)
+{
+	netdev_lock_ops(dev);
+	netif_disable_gro(dev);
+	netdev_unlock_ops(dev);
+}
+EXPORT_SYMBOL(dev_disable_gro);
+
 /**
  * dev_set_promiscuity() - update promiscuity count on a device
  * @dev: device
diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c
index 5555b71ab19b5..248cbb142e21e 100644
--- a/net/hsr/hsr_device.c
+++ b/net/hsr/hsr_device.c
@@ -232,9 +232,7 @@ static netdev_tx_t hsr_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 		skb->dev = master->dev;
 		skb_reset_mac_header(skb);
 		skb_reset_mac_len(skb);
-		spin_lock_bh(&hsr->seqnr_lock);
 		hsr_forward_skb(skb, master);
-		spin_unlock_bh(&hsr->seqnr_lock);
 	} else {
 		dev_core_stats_tx_dropped_inc(dev);
 		dev_kfree_skb_any(skb);
@@ -335,6 +333,7 @@ static void send_hsr_supervision_frame(struct hsr_port *port,
 		hsr_stag->sequence_nr = htons(hsr->sequence_nr);
 		hsr->sequence_nr++;
 	}
+	spin_unlock_bh(&hsr->seqnr_lock);
 
 	hsr_stag->tlv.HSR_TLV_type = type;
 	/* HSRv0 has 6 unused bytes after the MAC */
@@ -356,14 +355,10 @@ static void send_hsr_supervision_frame(struct hsr_port *port,
 		ether_addr_copy(hsr_sp->macaddress_A, hsr->macaddress_redbox);
 	}
 
-	if (skb_put_padto(skb, ETH_ZLEN)) {
-		spin_unlock_bh(&hsr->seqnr_lock);
+	if (skb_put_padto(skb, ETH_ZLEN))
 		return;
-	}
 
 	hsr_forward_skb(skb, port);
-	spin_unlock_bh(&hsr->seqnr_lock);
-	return;
 }
 
 static void send_prp_supervision_frame(struct hsr_port *master,
@@ -390,6 +385,7 @@ static void send_prp_supervision_frame(struct hsr_port *master,
 	spin_lock_bh(&hsr->seqnr_lock);
 	hsr_stag->sequence_nr = htons(hsr->sup_sequence_nr);
 	hsr->sup_sequence_nr++;
+	spin_unlock_bh(&hsr->seqnr_lock);
 	hsr_stag->tlv.HSR_TLV_type = PRP_TLV_LIFE_CHECK_DD;
 	hsr_stag->tlv.HSR_TLV_length = sizeof(struct hsr_sup_payload);
 
@@ -397,13 +393,10 @@ static void send_prp_supervision_frame(struct hsr_port *master,
 	hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload));
 	ether_addr_copy(hsr_sp->macaddress_A, master->dev->dev_addr);
 
-	if (skb_put_padto(skb, ETH_ZLEN)) {
-		spin_unlock_bh(&hsr->seqnr_lock);
+	if (skb_put_padto(skb, ETH_ZLEN))
 		return;
-	}
 
 	hsr_forward_skb(skb, master);
-	spin_unlock_bh(&hsr->seqnr_lock);
 }
 
 /* Announce (supervision frame) timer function
@@ -659,7 +652,7 @@ void hsr_dev_setup(struct net_device *dev)
 	dev->needs_free_netdev = true;
 
 	dev->hw_features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA |
-			   NETIF_F_GSO_MASK | NETIF_F_HW_CSUM |
+			   NETIF_F_HW_CSUM |
 			   NETIF_F_HW_VLAN_CTAG_TX |
 			   NETIF_F_HW_VLAN_CTAG_FILTER;
 
diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
index 0774981a65c16..f42694cf43099 100644
--- a/net/hsr/hsr_forward.c
+++ b/net/hsr/hsr_forward.c
@@ -12,6 +12,7 @@
 #include <linux/skbuff.h>
 #include <linux/etherdevice.h>
 #include <linux/if_vlan.h>
+#include <net/gso.h>
 #include "hsr_main.h"
 #include "hsr_framereg.h"
 
@@ -621,9 +622,10 @@ static void handle_std_frame(struct sk_buff *skb,
 	if (port->type == HSR_PT_MASTER ||
 	    port->type == HSR_PT_INTERLINK) {
 		/* Sequence nr for the master/interlink node */
-		lockdep_assert_held(&hsr->seqnr_lock);
+		spin_lock_bh(&hsr->seqnr_lock);
 		frame->sequence_nr = hsr->sequence_nr;
 		hsr->sequence_nr++;
+		spin_unlock_bh(&hsr->seqnr_lock);
 	}
 }
 
@@ -731,7 +733,7 @@ static int fill_frame_info(struct hsr_frame_info *frame,
 }
 
 /* Must be called holding rcu read lock (because of the port parameter) */
-void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)
+static void hsr_forward_skb_one(struct sk_buff *skb, struct hsr_port *port)
 {
 	struct hsr_frame_info frame;
 
@@ -746,8 +748,8 @@ void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)
 	 * So check and increment stats for master port only here.
 	 */
 	if (port->type == HSR_PT_MASTER || port->type == HSR_PT_INTERLINK) {
-		port->dev->stats.tx_packets++;
-		port->dev->stats.tx_bytes += skb->len;
+		DEV_STATS_INC(port->dev, tx_packets);
+		DEV_STATS_ADD(port->dev, tx_bytes, skb->len);
 	}
 
 	kfree_skb(frame.skb_hsr);
@@ -757,6 +759,105 @@ void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)
 
 out_drop:
 	rcu_read_unlock();
-	port->dev->stats.tx_dropped++;
+	DEV_STATS_INC(port->dev, tx_dropped);
+	kfree_skb(skb);
+}
+
+/* GSO fan-out funnel: unfold super-packets before per-frame processing so
+ * each wire frame gets its own HSR/PRP tag and sequence number.
+ */
+/* Effective frame protocol of a (possibly VLAN-tagged) skb, or 0 when
+ * it cannot be determined or the tagging exceeds what HSR supports.
+ * HSR supports one 802.1Q C-tag only, matching fill_frame_info(): an
+ * accelerated tag must be a C-tag with a non-VLAN inner protocol; an
+ * in-band tag is unwrapped exactly once and a residual VLAN EtherType
+ * is rejected. Read-only; no state is kept beyond the immediate
+ * protocol value.
+ */
+static __be16 hsr_gso_effective_proto(const struct sk_buff *skb)
+{
+	struct ethhdr eh;
+	struct vlan_hdr vh;
+	const struct ethhdr *eth;
+	const struct vlan_hdr *vhdr;
+	__be16 proto;
+
+	if (skb_vlan_tag_present(skb)) {
+		/* HSR supports one 802.1Q C-tag only. */
+		if (skb->vlan_proto != htons(ETH_P_8021Q))
+			return 0;
+		if (eth_type_vlan(skb->protocol))
+			return 0;
+		return skb->protocol;
+	}
+
+	eth = skb_header_pointer(skb, 0, sizeof(eh), &eh);
+	if (!eth)
+		return 0;
+
+	proto = eth->h_proto;
+	if (!eth_type_vlan(proto))
+		return proto;
+	if (proto != htons(ETH_P_8021Q))
+		return 0;
+
+	vhdr = skb_header_pointer(skb, ETH_HLEN, sizeof(vh), &vh);
+	if (!vhdr)
+		return 0;
+
+	proto = vhdr->h_vlan_encapsulated_proto;
+	if (eth_type_vlan(proto))
+		return 0;
+
+	return proto;
+}
+
+void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)
+{
+	struct sk_buff *segs, *next;
+	__be16 proto;
+
+	if (likely(!skb_is_gso(skb))) {
+		hsr_forward_skb_one(skb, port);
+		return;
+	}
+
+	/* Conforming plain-protocol GSO super-packets carry trailer-free
+	 * sender payload and are segmented here: each segment is delivered
+	 * or forwarded as its own wire frame, on any ingress role.
+	 *
+	 * The gate is content-based, not port-based. An aggregate whose
+	 * effective protocol is ETH_P_HSR or ETH_P_PRP cannot be safely
+	 * segmented and is dropped, as is any skb whose header cannot be
+	 * read or whose tagging exceeds the single 802.1Q C-tag HSR
+	 * supports. With NETIF_F_HW_HSR_TAG_RM the lower has already
+	 * stripped the tag, so such aggregates arrive plain and are
+	 * segmented.
+	 */
+	proto = hsr_gso_effective_proto(skb);
+	if (!proto)
+		goto drop_gso; /* classification failure, fail-safe */
+	if (proto == htons(ETH_P_HSR) || proto == htons(ETH_P_PRP))
+		goto drop_gso;
+
+	/* features = 0: request full software segmentation. tx_path is true
+	 * only for locally generated traffic on the master; ingress from
+	 * the interlink follows RX checksum semantics.
+	 */
+	segs = __skb_gso_segment(skb, 0, port->type == HSR_PT_MASTER);
+	if (IS_ERR(segs) || unlikely(!segs))
+		goto drop_gso;
+
+	consume_skb(skb);
+	while (segs) {
+		next = segs->next;
+		segs->next = NULL;
+		hsr_forward_skb_one(segs, port);
+		segs = next;
+	}
+	return;
+
+drop_gso:
+	DEV_STATS_INC(port->dev, tx_dropped);
 	kfree_skb(skb);
 }
diff --git a/net/hsr/hsr_slave.c b/net/hsr/hsr_slave.c
index 01c73b4b50ddd..aa2154b19c0b9 100644
--- a/net/hsr/hsr_slave.c
+++ b/net/hsr/hsr_slave.c
@@ -73,16 +73,7 @@ static rx_handler_result_t hsr_handle_frame(struct sk_buff **pskb)
 	}
 	skb_reset_mac_len(skb);
 
-	/* Only the frames received over the interlink port will assign a
-	 * sequence number and require synchronisation vs other sender.
-	 */
-	if (port->type == HSR_PT_INTERLINK) {
-		spin_lock_bh(&hsr->seqnr_lock);
-		hsr_forward_skb(skb, port);
-		spin_unlock_bh(&hsr->seqnr_lock);
-	} else {
-		hsr_forward_skb(skb, port);
-	}
+	hsr_forward_skb(skb, port);
 
 finish_consume:
 	return RX_HANDLER_CONSUMED;
@@ -171,6 +162,11 @@ static int hsr_portdev_setup(struct hsr_priv *hsr, struct net_device *dev,
 		goto fail_rx_handler;
 	dev_disable_lro(dev);
 
+	/* GRO disabling is best-effort: devices with fixed-on
+	 * GRO/GRO_HW cannot be forced off.
+	 */
+	dev_disable_gro(dev);
+
 	return 0;
 
 fail_rx_handler:
diff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile
index 31fb9326cf533..0d105476e7c50 100644
--- a/tools/testing/selftests/net/hsr/Makefile
+++ b/tools/testing/selftests/net/hsr/Makefile
@@ -3,6 +3,7 @@
 top_srcdir = ../../../../..
 
 TEST_PROGS := \
+	hsr_gro_superpacket.sh \
 	hsr_ping.sh \
 	hsr_redbox.sh \
 	link_faults.sh \
diff --git a/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh b/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh
new file mode 100755
index 0000000000000..ebcbd9fc41ec4
--- /dev/null
+++ b/tools/testing/selftests/net/hsr/hsr_gro_superpacket.sh
@@ -0,0 +1,631 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test HSR handling of GRO/GSO super-packets:
+#
+#  1. Enslaving a device to an HSR master disables GRO on it
+#     (dev_disable_gro()).
+#  2. The HSR master does not advertise GSO/TSO features.
+#  3. A TCP stream from a TSO-enabled SAN (which therefore emits GSO
+#     super-packets) is unfolded at the HSR forward entry. Evidence:
+#     interface-counter deltas show super-packet-sized frames leaving
+#     the SAN and per-frame-sized traffic leaving the DUT's LAN ports.
+#
+# Topology (100.64.0.0/24):
+#
+#   ns_san                    ns_dut                      ns_peer
+#  +-----------+  interlink  +---------------+  LAN A/B  +-----------+
+#  | s0 [0.1]  |-------------| d_il   hsr0   |-----------| hsr1 [0.3]|
+#  +-----------+             | d_a / d_b     |           | p_a / p_b |
+#                            +---------------+           +-----------+
+#
+# SAN traffic reaches ns_peer only through hsr0's forward path
+# (interlink RX -> LAN A/B TX), so every SAN frame is tagged and
+# forwarded by the DUT.
+
+source ./hsr_common.sh
+
+san_ip="100.64.0.1"
+peer_ip="100.64.0.3"
+
+# Aggregate counter thresholds for the stream test (bytes/packets):
+# SAN_AVG_MIN proves GSO super-packets left the SAN; LAN_AVG_MAX is a
+# guard with margin, not the protocol maximum (see do_tso_stream_test).
+SAN_AVG_MIN=2048
+LAN_AVG_MAX=1514
+
+iperf_pid=""
+server_wrapper=""
+active_srv_ns=""
+workdir=""
+pidfile=""
+ns_dut=""
+ns_san=""
+ns_peer=""
+ns_ls=""
+rcfile=""
+
+# Delete the per-server private work directory and reset its
+# variables. Called after a successful reap and from the EXIT
+# trap, which owns all failure paths.
+cleanup_workdir()
+{
+	# remove only the known non-empty private directory
+	if [ -n "${workdir}" ] && [ -d "${workdir}" ]; then
+		rm -rf "${workdir}"
+	fi
+	workdir=""
+	pidfile=""
+	rcfile=""
+}
+
+cleanup()
+{
+	# Server cleanup targets only the namespace of the currently
+	# active server (recorded by start_iperf_server); after a
+	# successful reap nothing is active.
+	if [ -n "${active_srv_ns}" ]; then
+		# exact-PID kill only after RE-validating identity (guards
+		# against PID reuse between publication and cleanup)
+		if [ -n "${iperf_pid}" ] && valid_server_pid "${active_srv_ns}" "${iperf_pid}"; then
+			kill "${iperf_pid}" 2>/dev/null
+		fi
+		iperf_pid=""
+		if [ -n "${server_wrapper}" ]; then
+			# the wrapper waits on the server; reap it with a 5s
+			# bound so a live-but-unpublished server can never
+			# hang cleanup
+			for _ in $(seq 1 50); do
+				kill -0 "${server_wrapper}" 2>/dev/null || break
+				sleep 0.1
+			done
+			kill "${server_wrapper}" 2>/dev/null
+			wait "${server_wrapper}" 2>/dev/null
+			server_wrapper=""
+		fi
+		# last resort, namespace-scoped only: TERM the iperf3
+		# processes that actually live in the active server netns,
+		# poll for bounded exit, then SIGKILL any survivor before
+		# touching the namespace name. A blind pkill would scan
+		# the host PID space and hit unrelated tests.
+		local _p _still
+		for _p in $(ip netns pids "${active_srv_ns}" 2>/dev/null); do
+			if is_iperf3_pid "$_p"; then
+				kill "$_p" 2>/dev/null
+			fi
+		done
+		for _ in $(seq 1 50); do
+			_still=0
+			for _p in $(ip netns pids "${active_srv_ns}" 2>/dev/null); do
+				if is_iperf3_pid "$_p"; then
+					_still=1
+					break
+				fi
+			done
+			[ "$_still" -eq 0 ] && break
+			sleep 0.1
+		done
+		for _p in $(ip netns pids "${active_srv_ns}" 2>/dev/null); do
+			if is_iperf3_pid "$_p"; then
+				kill -9 "$_p" 2>/dev/null
+			fi
+		done
+	fi
+	active_srv_ns=""
+	cleanup_workdir
+	cleanup_all_ns
+}
+
+trap cleanup EXIT
+
+check_tool()
+{
+	if ! command -v "$1" > /dev/null 2>&1; then
+		echo "SKIP: Could not run test without $1"
+		exit $ksft_skip
+	fi
+}
+
+nsx()
+{
+	ip netns exec "$1" bash -c "$2"
+}
+
+is_iperf3_pid()
+{
+	[ "$(cat /proc/"$1"/comm 2>/dev/null)" = "iperf3" ]
+}
+
+# Bounded reap of the one-shot server: wait at most 5s for it to
+# exit, then reap the wrapper and REQUIRE the rcfile with its real
+# status. A stuck server can never hang the script.
+reap_iperf_server()
+{
+	local server_rc
+
+	for _ in $(seq 1 50); do
+		kill -0 "${iperf_pid}" 2>/dev/null || break
+		sleep 0.1
+	done
+	if kill -0 "${iperf_pid}" 2>/dev/null; then
+		echo "FAIL: iperf3 server did not exit within 5s" 1>&2
+		ret=1
+		return 1
+	fi
+	wait "${server_wrapper}"
+	server_wrapper=""
+	if [ ! -s "${rcfile}" ]; then
+		echo "FAIL: iperf3 server status file missing (${rcfile})" 1>&2
+		ret=1
+		return 1
+	fi
+	server_rc=$(cat "${rcfile}")
+	if ! [[ "$server_rc" =~ ^[0-9]+$ ]] || [ "$server_rc" -ne 0 ]; then
+		echo "FAIL: iperf3 server exited with rc='${server_rc}'" 1>&2
+		ret=1
+		return 1
+	fi
+	iperf_pid=""
+	cleanup_workdir
+	active_srv_ns=""
+	return 0
+}
+
+# Decimal-counter validation for the snapshot blocks: every value must
+# be a plain decimal number. A parse failure in read_tx_counters yields
+# empty/garbled fields, which this check turns into an immediate FAIL.
+valid_decimals()
+{
+	local v
+
+	for v in "$@"; do
+		[[ "$v" =~ ^[0-9]+$ ]] || return 1
+	done
+	return 0
+}
+
+setup_topo()
+{
+	setup_ns ns_dut ns_san ns_peer || exit $?
+
+	ip link add d_a netns "$ns_dut" type veth peer name p_a netns "$ns_peer"
+	ip link add d_b netns "$ns_dut" type veth peer name p_b netns "$ns_peer"
+	ip link add d_il netns "$ns_dut" type veth peer name s0 netns "$ns_san"
+
+	# HSR tags add 6 bytes per frame; give the LAN legs headroom.
+	for iface in d_a d_b; do
+		nsx "$ns_dut" "ip link set $iface mtu 1600; \
+			ip link set $iface up"
+	done
+	for iface in p_a p_b; do
+		nsx "$ns_peer" "ip link set $iface mtu 1600; \
+			ip link set $iface up"
+	done
+
+	nsx "$ns_dut" "ip link set d_il up"
+	nsx "$ns_san" "ip link set s0 up; ip addr add $san_ip/24 dev s0"
+
+	nsx "$ns_dut" "ip link add hsr0 type hsr \
+		slave1 d_a slave2 d_b interlink d_il proto 0; \
+		ip link set hsr0 up"
+	nsx "$ns_peer" "ip link add hsr1 type hsr \
+		slave1 p_a slave2 p_b proto 0; \
+		ip link set hsr1 up; ip addr add $peer_ip/24 dev hsr1"
+
+	# Let the nodes see each other's supervision frames.
+	sleep 2
+}
+
+check_feature()
+{
+	local ns="$1"
+	local iface="$2"
+	local feature="$3"
+	local want="$4"
+
+	if nsx "$ns" "ethtool -k $iface" | grep -q "^$feature: $want"; then
+		echo "INFO: $ns/$iface $feature is $want [ OK ]"
+	else
+		echo "FAIL: $ns/$iface $feature is not $want" 1>&2
+		ret=1
+	fi
+}
+
+# Off-or-absent variant: fails only when the feature is present AND on,
+# so devices that simply do not list the feature do not fail it.
+check_feature_not_on()
+{
+	local ns="$1"
+	local iface="$2"
+	local feature="$3"
+
+	if nsx "$ns" "ethtool -k $iface" | grep -q "^$feature: on"; then
+		echo "FAIL: $ns/$iface $feature is on" 1>&2
+		ret=1
+	else
+		echo "INFO: $ns/$iface $feature not on [ OK ]"
+	fi
+}
+
+do_gro_feature_checks()
+{
+	echo "INFO: Checking that enslavement disabled GRO."
+	check_feature "$ns_dut" d_a generic-receive-offload off
+	check_feature "$ns_dut" d_b generic-receive-offload off
+	check_feature "$ns_dut" d_il generic-receive-offload off
+	stop_if_error "GRO not disabled on enslaved devices."
+
+	echo "INFO: Checking that enslavement disabled HW-GRO."
+	check_feature "$ns_dut" d_a rx-gro-hw off
+	check_feature "$ns_dut" d_b rx-gro-hw off
+	check_feature "$ns_dut" d_il rx-gro-hw off
+	stop_if_error "HW-GRO not disabled on enslaved devices."
+
+	echo "INFO: Checking that the HSR master does not advertise GSO/TSO."
+	check_feature "$ns_dut" hsr0 generic-segmentation-offload off
+	check_feature "$ns_dut" hsr0 tcp-segmentation-offload off
+	check_feature_not_on "$ns_dut" hsr0 tx-udp-segmentation
+	check_feature_not_on "$ns_dut" hsr0 tx-gso-list
+	stop_if_error "HSR master still advertises GSO-family features."
+}
+
+alloc_workdir()
+{
+	# Allocated only here, long after the initial topology cleanup, so
+	# cleanup() at setup_topo() time can never remove it. mktemp failure
+	# is a hard test failure.
+	workdir=$(mktemp -d /tmp/hsr_gro_test.XXXXXX) || {
+		echo "FAIL: mktemp -d failed" 1>&2
+		exit 1
+	}
+	chmod 700 "${workdir}"
+	pidfile="${workdir}/iperf.pid"
+	rcfile="${workdir}/iperf.rc"
+}
+
+# Numeric, alive, comm == iperf3, and really owned by the given netns.
+valid_server_pid()
+{
+	local pns="$1" p="$2"
+
+	[[ "$p" =~ ^[0-9]+$ ]] || return 1
+	kill -0 "$p" 2>/dev/null || return 1
+	[ "$(cat /proc/"$p"/comm 2>/dev/null)" = "iperf3" ] || return 1
+	ip netns pids "$pns" 2>/dev/null | grep -qx "$p"
+}
+
+start_iperf_server()
+{
+	local srv_ns="$1"
+	local srv_ip="${2:-}"
+	local candidate_pid
+
+	# One-shot server, no -D: the wrapper records its exact PID and its
+	# real exit status (netns shares the PID namespace and the host fs).
+	alloc_workdir
+	# record the server namespace for cleanup() before
+	# anything can fail with the server running
+	active_srv_ns="$srv_ns"
+	( nsx "$srv_ns" "iperf3 -s -1 ${srv_ip:+-B $srv_ip} > /dev/null 2>&1 & \
+		echo \$! > ${pidfile}; \
+		wait \$!; \
+		echo \$? > ${rcfile}" ) &
+	server_wrapper=$!
+	# the wrapper writes the pidfile asynchronously; wait for it to
+	# appear instead of racing the read
+	for _ in $(seq 1 50); do
+		[ -s "${pidfile}" ] && break
+		sleep 0.1
+	done
+	if [ ! -s "${pidfile}" ]; then
+		echo "FAIL: iperf3 server did not publish a pid" \
+			"(no pidfile)" 1>&2
+		ret=1
+		return 1
+	fi
+	candidate_pid=$(<"${pidfile}")
+	if ! valid_server_pid "$srv_ns" "${candidate_pid}"; then
+		echo "FAIL: iperf3 server pid '${candidate_pid}'" \
+			"failed validation" 1>&2
+		ret=1
+		return 1
+	fi
+	# publish only after full validation
+	iperf_pid="${candidate_pid}"
+	sleep 1
+	return 0
+}
+
+# Print "<bytes> <packets>" for exactly one TX record of ns/dev; anything
+# else (missing, duplicated, non-numeric) is a hard FAIL.
+read_tx_counters()
+{
+	local ns="$1" dev="$2"
+	local out cnt
+
+	out=$(nsx "$ns" "ip -s link show $dev" | \
+		awk '/^ +TX:/{getline; print $1, $2}')
+	cnt=$(echo "$out" | grep -c '^[0-9]* [0-9]*$')
+	if [ "$cnt" -ne 1 ]; then
+		echo "FAIL: cannot parse TX counters of $ns/$dev" \
+			"(records=$cnt)" 1>&2
+		return 1
+	fi
+	echo "$out"
+	return 0
+}
+
+# Print "<bytes> <packets>" for exactly one RX record of ns/dev; the
+# same single-record discipline as read_tx_counters.
+read_rx_counters()
+{
+	local ns="$1" dev="$2"
+	local out cnt
+
+	out=$(nsx "$ns" "ip -s link show $dev" | \
+		awk '/^ +RX:/{getline; print $1, $2}')
+	cnt=$(echo "$out" | grep -c '^[0-9]* [0-9]*$')
+	if [ "$cnt" -ne 1 ]; then
+		echo "FAIL: cannot parse RX counters of $ns/$dev" \
+			"(records=$cnt)" 1>&2
+		return 1
+	fi
+	echo "$out"
+	return 0
+}
+
+eval_counter_delta()
+{
+	local name="$1" b0="$2" p0="$3" b1="$4" p1="$5" op="$6" limit="$7"
+	local bd pd
+
+	if ! [[ "$b0" =~ ^[0-9]+$ && "$b1" =~ ^[0-9]+$ && \
+		"$p0" =~ ^[0-9]+$ && "$p1" =~ ^[0-9]+$ ]]; then
+		echo "FAIL: non-numeric counter input for $name" 1>&2
+		ret=1
+		return 1
+	fi
+	bd=$((b1 - b0))
+	pd=$((p1 - p0))
+	if [ "$bd" -lt 0 ] || [ "$pd" -le 0 ]; then
+		echo "FAIL: counter delta invalid for $name" \
+			"(bytes=$bd pkts=$pd)" 1>&2
+		ret=1
+		return 1
+	fi
+	if [ "$op" = "gt" ]; then
+		if [ "$bd" -le $((pd * limit)) ]; then
+			echo "FAIL: $name bytes/packets $bd/$pd <= $limit" 1>&2
+			ret=1
+			return 1
+		fi
+	else
+		if [ "$bd" -gt $((pd * limit)) ]; then
+			echo "FAIL: $name bytes/packets $bd/$pd > $limit" 1>&2
+			ret=1
+			return 1
+		fi
+	fi
+	echo "INFO: $name counter delta bytes=$bd packets=$pd" \
+		"(op $op limit $limit) [ OK ]"
+	return 0
+}
+
+# LAN-slave plain-GSO regression: a plain SAN aggregate entering a PRP
+# LAN slave must be segmented at the forward entry, not dropped. PRP
+# drops slave-to-slave forwarding by design (prp_drop_frame), so the
+# consumer of LAN-slave SAN traffic is local delivery to the master.
+# Two independent oracles: the SAN-side proof that aggregates really
+# arrived at the DUT, and the volume + per-frame shape of the local
+# delivery. On the broken gate the aggregates are dropped and TCP
+# crawls on retransmitted single segments, separating the kernels by
+# an order of magnitude in delivered bytes.
+do_lansan_gso_test()
+{
+	local prp_ip="10.99.1.1" ls_ip="10.99.1.10"
+	local out san_b0 san_p0 san_b1 san_p1
+	local a_b0 a_p0 a_b1 a_p1 r_b0 r_p0 r_b1 r_p1
+
+	setup_ns ns_ls || exit $?
+
+	ip link add d_pa netns "$ns_dut" type veth peer name ls_a netns "$ns_ls"
+	ip link add d_pb netns "$ns_dut" type veth peer name ls_p netns "$ns_ls"
+
+	nsx "$ns_dut" "ip link set d_pa mtu 1600 up"
+	nsx "$ns_dut" "ip link set d_pb mtu 1600 up"
+	nsx "$ns_ls" "ip link set ls_a mtu 1600 up; \
+		ip link set ls_p mtu 1600 up; ip addr add $ls_ip/24 dev ls_a"
+
+	# PRP DUT with the SAN on a LAN slave: plain SAN aggregates are
+	# valid traffic on a PRP LAN and must not be dropped.
+	nsx "$ns_dut" "ip link add prp0 type hsr \
+		slave1 d_pa slave2 d_pb proto 1; \
+		ip link set prp0 up; ip addr add $prp_ip/24 dev prp0"
+
+	# Let supervision frames converge.
+	sleep 2
+
+	echo "INFO: Enabling TSO/GSO on the LAN-side SAN interface."
+	nsx "$ns_ls" "ethtool -K ls_a tso on gso on"
+	check_feature "$ns_ls" ls_a tcp-segmentation-offload on
+	stop_if_error "Could not enable TSO on the LAN-side SAN interface."
+
+	start_iperf_server "$ns_dut" "$prp_ip" || return
+
+	read -r r_b0 r_p0 <<EOF
+$(read_rx_counters "$ns_dut" prp0)
+EOF
+	read -r san_b0 san_p0 <<EOF
+$(read_tx_counters "$ns_ls" ls_a)
+EOF
+	if ! valid_decimals "$r_b0" "$r_p0" "$san_b0" "$san_p0"; then
+		echo "FAIL: baseline counter snapshot invalid" 1>&2
+		ret=1
+		return 1
+	fi
+
+	if ! out=$(nsx "$ns_ls" "timeout 60 iperf3 -c $prp_ip -M 1446 \
+		-b 2G -t 10" 2>&1); then
+		echo "FAIL: LAN-slave GSO local-delivery stream failed:" 1>&2
+		echo "$out" 1>&2
+		ret=1
+		return
+	fi
+
+	read -r r_b1 r_p1 <<EOF
+$(read_rx_counters "$ns_dut" prp0)
+EOF
+	read -r san_b1 san_p1 <<EOF
+$(read_tx_counters "$ns_ls" ls_a)
+EOF
+	if ! valid_decimals "$r_b1" "$r_p1" "$san_b1" "$san_p1"; then
+		echo "FAIL: final counter snapshot invalid" 1>&2
+		ret=1
+		return 1
+	fi
+
+	# Oracle 1 - aggregates really arrived: the SAN emitted
+	# super-packets toward the DUT.
+	eval_counter_delta "SAN ls_a TX" "$san_b0" "$san_p0" \
+		"$san_b1" "$san_p1" gt "$SAN_AVG_MIN"
+	[ "${ret:-0}" -eq 0 ] || return
+
+	# Oracle 2 - local delivery of those aggregates: volume and
+	# per-frame shape on the PRP master. The volume gate separates
+	# full delivery from the retransmit crawl the broken gate leaves;
+	# the average gate proves the bytes arrived per-frame.
+	if [ $((r_b1 - r_b0)) -lt 100000000 ]; then
+		echo "FAIL: LAN-slave local delivery degraded" \
+			"(prp0 RX delta $((r_b1 - r_b0)) bytes < 100000000)" 1>&2
+		ret=1
+		return
+	fi
+	if [ $((r_b1 - r_b0)) -gt $(( (r_p1 - r_p0) * LAN_AVG_MAX )) ]; then
+		echo "FAIL: local delivery not per-frame" \
+			"(avg $(( (r_b1 - r_b0) / (r_p1 - r_p0) )) > $LAN_AVG_MAX)" 1>&2
+		ret=1
+		return
+	fi
+	echo "INFO: LAN-slave local delivery prp0 RX delta" \
+		"$((r_b1 - r_b0)) bytes / $((r_p1 - r_p0)) pkts [ OK ]"
+	reap_iperf_server || return
+	echo "INFO: LAN-slave plain-GSO regression [ OK ]"
+}
+
+do_tso_stream_test()
+{
+	local out sender_retr
+	local san_b0 san_p0 san_b1 san_p1
+	local a_b0 a_p0 a_b1 a_p1 b_b0 b_p0 b_b1 b_p1
+
+	echo "INFO: Enabling TSO/GSO on the SAN interface."
+	nsx "$ns_san" "ethtool -K s0 tso on gso on"
+	check_feature "$ns_san" s0 tcp-segmentation-offload on
+	stop_if_error "Could not enable TSO on the SAN interface."
+
+	echo "INFO: Running 10s TCP stream SAN -> peer through the HSR DUT."
+	start_iperf_server "$ns_peer" || return
+
+	# Counter snapshots around the stream window. The SAN-side average
+	# must exceed SAN_AVG_MIN (aggregate proof that GSO super-packets
+	# really left the SAN); each DUT LAN leg must stay under LAN_AVG_MAX
+	# (aggregate proof that bulk output was segmented per-frame). These
+	# are aggregate discriminators, not a per-frame maximum proof.
+	san_b0=0; san_p0=0; a_b0=0; a_p0=0; b_b0=0; b_p0=0
+	read -r san_b0 san_p0 <<EOF
+$(read_tx_counters "$ns_san" s0)
+EOF
+	read -r a_b0 a_p0 <<EOF
+$(read_tx_counters "$ns_dut" d_a)
+EOF
+	read -r b_b0 b_p0 <<EOF
+$(read_tx_counters "$ns_dut" d_b)
+EOF
+	if ! valid_decimals "$san_b0" "$san_p0" "$a_b0" "$a_p0" \
+		"$b_b0" "$b_p0"; then
+		echo "FAIL: baseline TX counter snapshot invalid" 1>&2
+		ret=1
+		return 1
+	fi
+
+	# rate-capped: the PRIMARY discriminator is the counter inequality
+	# above, not max throughput; retransmits are informational only.
+	# Uncapped runs flap at VM/CI edge rates without indicating a
+	# functional problem.
+	if ! out=$(nsx "$ns_san" "timeout 60 iperf3 -c $peer_ip -M 1446 \
+		-b 2G -t 10" 2>&1); then
+		echo "FAIL: iperf3 client failed:" 1>&2
+		echo "$out" 1>&2
+		ret=1
+		return
+	fi
+
+	read -r san_b1 san_p1 <<EOF
+$(read_tx_counters "$ns_san" s0)
+EOF
+	read -r a_b1 a_p1 <<EOF
+$(read_tx_counters "$ns_dut" d_a)
+EOF
+	read -r b_b1 b_p1 <<EOF
+$(read_tx_counters "$ns_dut" d_b)
+EOF
+	if ! valid_decimals "$san_b1" "$san_p1" "$a_b1" "$a_p1" \
+		"$b_b1" "$b_p1"; then
+		echo "FAIL: final TX counter snapshot invalid" 1>&2
+		ret=1
+		return 1
+	fi
+
+	eval_counter_delta "SAN s0 TX" "$san_b0" "$san_p0" "$san_b1" "$san_p1" \
+		gt "$SAN_AVG_MIN"
+	eval_counter_delta "DUT d_a TX" "$a_b0" "$a_p0" "$a_b1" "$a_p1" \
+		le "$LAN_AVG_MAX"
+	eval_counter_delta "DUT d_b TX" "$b_b0" "$b_p0" "$b_b1" "$b_p1" \
+		le "$LAN_AVG_MAX"
+	[ "${ret:-0}" -eq 0 ] || return
+
+	# success path: bounded reap with the server's real status
+	reap_iperf_server || return
+
+	# secondary health signal only: anchored, single-match, numeric —
+	# any parse anomaly is a loud FAIL, but the value itself no longer
+	# gates (the counter inequalities above are the primary evidence).
+	sender_retr=$(echo "$out" | awk '/sec .* sender$/ {print $(NF-1)}')
+	if [ "$(echo "$sender_retr" | grep -Ec '^[0-9]+$')" -ne 1 ]; then
+		echo "FAIL: cannot parse sender retransmits reliably" 1>&2
+		echo "$out" 1>&2
+		ret=1
+		return
+	fi
+	echo "INFO: TCP stream done;" \
+		"sender retransmits=$sender_retr (secondary signal)"
+	echo "$out" | grep -E "sender|receiver"
+}
+
+check_prerequisites
+check_tool ethtool
+check_tool iperf3
+check_tool timeout
+
+# iproute2 must know the HSR interlink syntax.
+if ! ip link help hsr 2>&1 | grep -qi interlink; then
+	echo "SKIP: iproute2 has no HSR interlink support"
+	exit $ksft_skip
+fi
+
+setup_topo
+
+echo "INFO: Initial validation ping (SAN -> peer through the DUT)."
+do_ping "$ns_san" "$peer_ip"
+stop_if_error "Initial validation failed."
+
+do_gro_feature_checks
+do_tso_stream_test
+stop_if_error "GSO super-packet stream test failed."
+
+do_lansan_gso_test
+stop_if_error "LAN-slave plain-GSO regression failed."
+
+echo "INFO: All good."
+cleanup
+exit $ret

21/3 2026/08/07 17:41 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14159
							output: 205
							thoughts: 1626
						

Thoughts:
**KMSAN Evaluation: GRO/GSO Patch for HSR**

Okay, so I've got this patch here that's messing with GRO and GSO within the HSR framework. Let's break this down. My initial thought is to figure out if we need to run a KMSAN fuzzing session on this thing. We're looking at changes that:

1.  Disable GRO on devices enslaved to an HSR master. `netif_disable_gro` and `dev_disable_gro` are the key functions added here. Seems straightforward.
2.  Modify how HSR handles GSO super-packets by segmenting them before forwarding. This is the more interesting part. It's done through the new `hsr_forward_skb` function and the usage of `__skb_gso_segment`. They're also adjusting the locking around the sequence number increments within the HSR code.

Now, let's look closer at the code. I see they're doing `skb_header_pointer` calls, and there are some buffers, like `eh` and `vh` to hold the Ethernet and VLAN headers. No big deal, pretty standard practice. The patch calls `__skb_gso_segment`.

The obvious question: Does this introduce or expose any uninitialized memory risks?

Right off the bat, I want to rule out any immediate KMSAN flags. There are a few areas that concern me, so I want to be thorough.

*   `skb_header_pointer` - It seems safe because it either returns a pointer to initialized data within the `skb` or it fills the given buffer.
*   `__skb_gso_segment` - I'm a little more concerned here because it creates new skbs. But `__skb_gso_segment` is a core networking function, so I'm not overly worried about it. It's a well-tested part of the kernel.

I need to be certain. So, there is no new kernel data exposed to user space. No new complex structures are allocated without initialization. We are dealing with standard network packet processing, the GRO/GSO features, and some locking.

I take a look at the newly added function, `hsr_gso_effective_proto`. It reads header data using `skb_header_pointer`. If the header data is in the linear part, it returns a pointer; otherwise, it copies the header into a local buffer. I see a familiar pattern using `skb_header_pointer`:

```c
	eth = skb_header_pointer(skb, 0, sizeof(eh), &eh);
	if (!eth)
		return 0;
	proto = eth->h_proto;
```

This pattern is well-understood, standard, and safe. So, I see no potential KMSAN issues here.

Essentially, all the memory access is safe, initialized memory, standard network operations. I have to say, based on my expert assessment, there's no smoking gun here that warrants a KMSAN run. Any memory-related issues, like out-of-bounds accesses or locking problems, would be caught by KASAN and LOCKDEP. KMSAN is overkill in this case.

Final answer: `NeedsKMSAN=false`. The standard tools should be sufficient. The patch fixes a bug.



22/3 2026/08/07 17:41 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch modifies HSR (High-availability Seamless Redundancy) to handle GSO (Generic Segmentation Offload) super-packets by segmenting them before forwarding, and disables GRO (Generic Receive Offload) on enslaved devices. It also adjusts locking around sequence number increments. The changes primarily involve network packet processing logic, skb segmentation (`__skb_gso_segment`), and reading packet headers using the standard `skb_header_pointer` function. No new kernel structures are exposed to user space, and no complex data structures are allocated without initialization. Any potential bugs introduced by these changes, such as out-of-bounds memory accesses, use-after-free, or locking issues, would be effectively caught by standard KASAN and LOCKDEP. There is no risk of uninitialized memory usage or information leaks that would require a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch modifies HSR (High-availability Seamless Redundancy) to handle GSO (Generic Segmentation Offload) super-packets by segmenting them before forwarding, and disables GRO (Generic Receive Offload) on enslaved devices. It also adjusts locking around sequence number increments. The changes primarily involve network packet processing logic, skb segmentation (`__skb_gso_segment`), and reading packet headers using the standard `skb_header_pointer` function. No new kernel structures are exposed to user space, and no complex data structures are allocated without initialization. Any potential bugs introduced by these changes, such as out-of-bounds memory accesses, use-after-free, or locking issues, would be effectively caught by standard KASAN and LOCKDEP. There is no risk of uninitialized memory usage or information leaks that would require a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)