app/testpmd: fix inner header parsing for tunnel TSO

Message ID 20251103164001.437863-1-shperetz@nvidia.com (mailing list archive)
State New
Delegated to: Stephen Hemminger
Headers
Series app/testpmd: fix inner header parsing for tunnel TSO |

Checks

Context Check Description
ci/checkpatch success coding style OK
ci/loongarch-compilation success Compilation OK
ci/loongarch-unit-testing success Unit Testing PASS
ci/iol-mellanox-Functional success Functional Testing PASS
ci/Intel-compilation success Compilation OK
ci/iol-marvell-Functional success Functional Testing PASS
ci/iol-sample-apps-testing pending Testing pending
ci/intel-Testing success Testing PASS
ci/iol-mellanox-Performance success Performance Testing PASS
ci/iol-intel-Functional success Functional Testing PASS
ci/github-robot: build success github build: passed
ci/github-robot-post success github post: success
ci/iol-intel-Performance success Performance Testing PASS
ci/iol-unit-amd64-testing fail Testing issues
ci/iol-broadcom-Performance success Performance Testing PASS
ci/iol-unit-arm64-testing success Testing PASS
ci/iol-compile-amd64-testing warning Testing issues
ci/intel-Functional success Functional PASS
ci/aws-unit-testing success Unit Testing PASS
ci/iol-compile-arm64-testing success Testing PASS

Commit Message

Shani Peretz Nov. 3, 2025, 4:40 p.m. UTC
The csumonly forwarding mode had two issues preventing TSO from working
correctly:

First, inner L2 length calculation was incorrect. The inner_l2_len field
from rte_net_get_ptype() includes the outer L4 header and tunnel header
lengths for UDP-based tunnels (VXLAN, GENEVE), but the code was using
it directly as the inner Ethernet header length.
Fixed by subtracting these from inner_l2_len to get the inner Ethernet
header length.

Second, inner L4 protocol detection failed when outer and inner L3 types
differed. The parse_l4_proto() function used RTE_ETH_IS_IPV4_HDR()
which checks the outer L3 type, even when parsing inner headers.
For packets with IPv6 outer and IPv4 inner headers (or vice versa),
this caused the inner L4 protocol to be undetected, preventing TSO.

Fixed by checking the appropriate L3 type mask (outer vs inner) based
on the parse_inner parameter.

Fixes: a738c43ffaee ("app/testpmd: fix tunnel inner info")
Cc: stable@dpdk.org

Signed-off-by: Shani Peretz <shperetz@nvidia.com>
---
 app/test-pmd/csumonly.c | 51 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 45 insertions(+), 6 deletions(-)
  

Patch

diff --git a/app/test-pmd/csumonly.c b/app/test-pmd/csumonly.c
index a6e872e5a4..17e2af9a65 100644
--- a/app/test-pmd/csumonly.c
+++ b/app/test-pmd/csumonly.c
@@ -529,8 +529,24 @@  static uint8_t
 parse_l4_proto(const struct rte_mbuf *m, uint32_t off, uint32_t ptype, bool parse_inner)
 {
 	int frag = 0, ret;
+	bool is_ipv4, is_ipv6;
+
+	if (parse_inner) {
+		/* Check inner L3 type for tunneled packets */
+		uint32_t inner_l3 = ptype & RTE_PTYPE_INNER_L3_MASK;
+		is_ipv4 = (inner_l3 == RTE_PTYPE_INNER_L3_IPV4) ||
+			  (inner_l3 == RTE_PTYPE_INNER_L3_IPV4_EXT) ||
+			  (inner_l3 == RTE_PTYPE_INNER_L3_IPV4_EXT_UNKNOWN);
+		is_ipv6 = (inner_l3 == RTE_PTYPE_INNER_L3_IPV6) ||
+			  (inner_l3 == RTE_PTYPE_INNER_L3_IPV6_EXT) ||
+			  (inner_l3 == RTE_PTYPE_INNER_L3_IPV6_EXT_UNKNOWN);
+	} else {
+		/* Check outer L3 type */
+		is_ipv4 = RTE_ETH_IS_IPV4_HDR(ptype);
+		is_ipv6 = RTE_ETH_IS_IPV6_HDR(ptype);
+	}
 
-	if (RTE_ETH_IS_IPV4_HDR(ptype)) {
+	if (is_ipv4) {
 		const struct rte_ipv4_hdr *ip4h;
 		struct rte_ipv4_hdr ip4h_copy;
 		ip4h = rte_pktmbuf_read(m, off, sizeof(*ip4h), &ip4h_copy);
@@ -538,7 +554,7 @@  parse_l4_proto(const struct rte_mbuf *m, uint32_t off, uint32_t ptype, bool pars
 			return 0;
 
 		return ip4h->next_proto_id;
-	} else if (RTE_ETH_IS_IPV6_HDR(ptype)) {
+	} else if (is_ipv6) {
 		const struct rte_ipv6_hdr *ip6h;
 		struct rte_ipv6_hdr ip6h_copy;
 		ip6h = rte_pktmbuf_read(m, off, sizeof(*ip6h), &ip6h_copy);
@@ -552,9 +568,15 @@  parse_l4_proto(const struct rte_mbuf *m, uint32_t off, uint32_t ptype, bool pars
 			return ip6h->proto;
 
 		off += sizeof(struct rte_ipv6_hdr);
+		/* Check offset before parsing extension headers. */
+		if (off >= m->pkt_len)
+			return 0;
 		ret = rte_net_skip_ip6_ext(ip6h->proto, m, &off, &frag);
 		if (ret < 0)
 			return 0;
+		/* Ensure offset is valid after skipping IPv6 extension headers. */
+		if (off > m->pkt_len)
+			return 0;
 		return ret;
 	}
 
@@ -703,7 +725,21 @@  pkt_burst_checksum_forward(struct fwd_stream *fs)
 		if (txp->parse_tunnel && RTE_ETH_IS_TUNNEL_PKT(ptype) != 0) {
 			info.is_tunnel = 1;
 			update_tunnel_outer(&info);
-			info.l2_len = hdr_lens.inner_l2_len;
+			/* For tunnels with inner L2 (e.g., VXLAN, GENEVE with Ethernet),
+			 * inner_l2_len includes outer L4 + tunnel headers.
+			 * Subtract them to get only inner L2 length.
+			 * For tunnels without inner L2, inner_l2_len already contains
+			 * only headers before inner L3, no adjustment needed.
+			 */
+			if (ptype & RTE_PTYPE_INNER_L2_MASK) {
+				if (unlikely(hdr_lens.inner_l2_len <
+					     hdr_lens.l4_len + hdr_lens.tunnel_len))
+					info.l2_len = 0;
+				else
+					info.l2_len = hdr_lens.inner_l2_len -
+						hdr_lens.l4_len - hdr_lens.tunnel_len;
+			} else
+				info.l2_len = hdr_lens.inner_l2_len;
 			info.l3_len = hdr_lens.inner_l3_len;
 			info.l4_len = hdr_lens.inner_l4_len;
 			eth_hdr = (struct rte_ether_hdr *)((char *)l3_hdr +
@@ -714,11 +750,14 @@  pkt_burst_checksum_forward(struct fwd_stream *fs)
 		}
 		/* update l3_hdr and outer_l3_hdr if a tunnel was parsed */
 		if (info.is_tunnel) {
-			uint16_t l3_off = info.outer_l2_len +  info.outer_l3_len + info.l2_len;
+			uint16_t l3_off = info.outer_l2_len +  info.outer_l3_len +
+				hdr_lens.l4_len + hdr_lens.tunnel_len + info.l2_len;
 
 			outer_l3_hdr = l3_hdr;
-			l3_hdr = (char *)l3_hdr + info.outer_l3_len + info.l2_len;
-			info.l4_proto = parse_l4_proto(m, l3_off, ptype, true);
+			l3_hdr = (char *)eth_hdr + info.l2_len;
+			/* Validate offset is within packet bounds before parsing */
+			if (l3_off < info.pkt_len)
+				info.l4_proto = parse_l4_proto(m, l3_off, ptype, true);
 		}
 		/* step 2: depending on user command line configuration,
 		 * recompute checksum either in software or flag the