DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH dpdk] net: fix L2 ptype assignment in VLAN loop
@ 2026-04-22 10:28 Robin Jarry
  2026-04-22 10:35 ` Robin Jarry
                   ` (4 more replies)
  0 siblings, 5 replies; 26+ messages in thread
From: Robin Jarry @ 2026-04-22 10:28 UTC (permalink / raw)
  To: dev, Gregory Etelson; +Cc: stable

Since commit 1f250674085a ("net: fix packet type for stacked VLAN"),
rte_net_get_ptype() uses |= to set the L2 ptype inside the VLAN
parsing loop. Since pkt_type is already initialized with
RTE_PTYPE_L2_ETHER (0x1), or-ing it with RTE_PTYPE_L2_ETHER_VLAN
(0x6) results in RTE_PTYPE_L2_ETHER_QINQ (0x7). This causes single
VLAN frames to be misidentified as QinQ.

This was detected while testing DPDK 25.11.1 in grout. The net/tap
driver calls rte_net_get_ptype() in tap_verify_csum() to determine
the L2 header length. With the wrong ptype, l2_len is set to 22
(ether + QinQ = 14 + 8) instead of 18 (ether + VLAN = 14 + 4),
shifting the IP header pointer by 4 bytes. The checksum is then
computed on garbage data, causing valid packets to be dropped.

Use a simple assignment to replace the L2 ptype instead. Add VLAN
and QinQ test packets in cksum_autotest to prevent regressions.

Fixes: 1f250674085a ("net: fix packet type for stacked VLAN")
Cc: stable@dpdk.org

Signed-off-by: Robin Jarry <rjarry@redhat.com>
---
 app/test/test_cksum.c | 29 +++++++++++++++++++++++++++++
 lib/net/rte_net.c     |  2 +-
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/app/test/test_cksum.c b/app/test/test_cksum.c
index ea443382a128..3cc42eccedd9 100644
--- a/app/test/test_cksum.c
+++ b/app/test/test_cksum.c
@@ -75,6 +75,27 @@ static const char test_cksum_ipv6_udp[] = {
 	0x00, 0x35, 0x00, 0x09, 0x87, 0x70, 0x78,
 };
 
+/* generated in scapy with Ether()/Dot1Q(vlan=42)/IP()/UDP()/Raw('x') */
+static const char test_cksum_vlan_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+	0x08, 0x00, 0x45, 0x00, 0x00, 0x1d, 0x00, 0x01,
+	0x00, 0x00, 0x40, 0x11, 0x7c, 0xcd, 0x7f, 0x00,
+	0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x89, 0x6f, 0x78,
+};
+
+/* generated in scapy with Ether()/Dot1AD(vlan=42)/Dot1Q(vlan=43)/IP()/UDP()/Raw('x') */
+static const char test_cksum_qinq_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0xa8, 0x00, 0x2a,
+	0x81, 0x00, 0x00, 0x2b, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
 /* generated in scapy with Ether()/IP(options='\x00')/UDP()/Raw('x')) */
 static const char test_cksum_ipv4_opts_udp[] = {
 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
@@ -252,6 +273,14 @@ test_cksum(void)
 			  sizeof(test_cksum_ipv6_udp)) < 0)
 		GOTO_FAIL("checksum error on ipv6_udp");
 
+	if (test_l4_cksum(pktmbuf_pool, test_cksum_vlan_ipv4_udp,
+			  sizeof(test_cksum_vlan_ipv4_udp)) < 0)
+		GOTO_FAIL("checksum error on vlan_ipv4_udp");
+
+	if (test_l4_cksum(pktmbuf_pool, test_cksum_qinq_ipv4_udp,
+			  sizeof(test_cksum_qinq_ipv4_udp)) < 0)
+		GOTO_FAIL("checksum error on qinq_ipv4_udp");
+
 	if (test_l4_cksum(pktmbuf_pool, test_cksum_ipv4_opts_udp,
 			  sizeof(test_cksum_ipv4_opts_udp)) < 0)
 		GOTO_FAIL("checksum error on ipv4_opts_udp");
diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
index 458b4814a9c9..ea5ba7019089 100644
--- a/lib/net/rte_net.c
+++ b/lib/net/rte_net.c
@@ -359,7 +359,7 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
 
 		if (++vlan_depth > RTE_NET_VLAN_MAX_DEPTH)
 			return 0;
-		pkt_type |=
+		pkt_type =
 			proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
 				 RTE_PTYPE_L2_ETHER_VLAN :
 				 RTE_PTYPE_L2_ETHER_QINQ;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk] net: fix L2 ptype assignment in VLAN loop
  2026-04-22 10:28 [PATCH dpdk] net: fix L2 ptype assignment in VLAN loop Robin Jarry
@ 2026-04-22 10:35 ` Robin Jarry
  2026-04-22 10:38 ` [PATCH dpdk v2] " Robin Jarry
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 26+ messages in thread
From: Robin Jarry @ 2026-04-22 10:35 UTC (permalink / raw)
  To: dev, Gregory Etelson; +Cc: stable

Robin Jarry, Apr 22, 2026 at 12:28:
> Since commit 1f250674085a ("net: fix packet type for stacked VLAN"),
> rte_net_get_ptype() uses |= to set the L2 ptype inside the VLAN
> parsing loop. Since pkt_type is already initialized with
> RTE_PTYPE_L2_ETHER (0x1), or-ing it with RTE_PTYPE_L2_ETHER_VLAN
> (0x6) results in RTE_PTYPE_L2_ETHER_QINQ (0x7). This causes single
> VLAN frames to be misidentified as QinQ.
>
> This was detected while testing DPDK 25.11.1 in grout. The net/tap
> driver calls rte_net_get_ptype() in tap_verify_csum() to determine
> the L2 header length. With the wrong ptype, l2_len is set to 22
> (ether + QinQ = 14 + 8) instead of 18 (ether + VLAN = 14 + 4),
> shifting the IP header pointer by 4 bytes. The checksum is then
> computed on garbage data, causing valid packets to be dropped.
>
> Use a simple assignment to replace the L2 ptype instead. Add VLAN
> and QinQ test packets in cksum_autotest to prevent regressions.
>
> Fixes: 1f250674085a ("net: fix packet type for stacked VLAN")
> Cc: stable@dpdk.org
>
> Signed-off-by: Robin Jarry <rjarry@redhat.com>
> ---
>  app/test/test_cksum.c | 29 +++++++++++++++++++++++++++++
>  lib/net/rte_net.c     |  2 +-
>  2 files changed, 30 insertions(+), 1 deletion(-)
>
> diff --git a/app/test/test_cksum.c b/app/test/test_cksum.c
> index ea443382a128..3cc42eccedd9 100644
> --- a/app/test/test_cksum.c
> +++ b/app/test/test_cksum.c
> @@ -75,6 +75,27 @@ static const char test_cksum_ipv6_udp[] = {
>  	0x00, 0x35, 0x00, 0x09, 0x87, 0x70, 0x78,
>  };
>  
> +/* generated in scapy with Ether()/Dot1Q(vlan=42)/IP()/UDP()/Raw('x') */
> +static const char test_cksum_vlan_ipv4_udp[] = {
> +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
> +	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
> +	0x08, 0x00, 0x45, 0x00, 0x00, 0x1d, 0x00, 0x01,
> +	0x00, 0x00, 0x40, 0x11, 0x7c, 0xcd, 0x7f, 0x00,
> +	0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x35,
> +	0x00, 0x35, 0x00, 0x09, 0x89, 0x6f, 0x78,
> +};
> +
> +/* generated in scapy with Ether()/Dot1AD(vlan=42)/Dot1Q(vlan=43)/IP()/UDP()/Raw('x') */
> +static const char test_cksum_qinq_ipv4_udp[] = {
> +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
> +	0x00, 0x00, 0x00, 0x00, 0x88, 0xa8, 0x00, 0x2a,
> +	0x81, 0x00, 0x00, 0x2b, 0x08, 0x00, 0x45, 0x00,
> +	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
> +	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
> +	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
> +	0x89, 0x6f, 0x78,
> +};
> +
>  /* generated in scapy with Ether()/IP(options='\x00')/UDP()/Raw('x')) */
>  static const char test_cksum_ipv4_opts_udp[] = {
>  	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
> @@ -252,6 +273,14 @@ test_cksum(void)
>  			  sizeof(test_cksum_ipv6_udp)) < 0)
>  		GOTO_FAIL("checksum error on ipv6_udp");
>  
> +	if (test_l4_cksum(pktmbuf_pool, test_cksum_vlan_ipv4_udp,
> +			  sizeof(test_cksum_vlan_ipv4_udp)) < 0)
> +		GOTO_FAIL("checksum error on vlan_ipv4_udp");
> +
> +	if (test_l4_cksum(pktmbuf_pool, test_cksum_qinq_ipv4_udp,
> +			  sizeof(test_cksum_qinq_ipv4_udp)) < 0)
> +		GOTO_FAIL("checksum error on qinq_ipv4_udp");

The tests are useless, they don't check that the L2 ptype is correct.
I'll send a v2 with proper ptype tests.


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH dpdk v2] net: fix L2 ptype assignment in VLAN loop
  2026-04-22 10:28 [PATCH dpdk] net: fix L2 ptype assignment in VLAN loop Robin Jarry
  2026-04-22 10:35 ` Robin Jarry
@ 2026-04-22 10:38 ` Robin Jarry
  2026-04-22 13:16   ` Thomas Monjalon
  2026-04-22 13:23   ` David Marchand
  2026-04-22 13:32 ` [PATCH dpdk v3] net: fix VLAN packet type Robin Jarry
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 26+ messages in thread
From: Robin Jarry @ 2026-04-22 10:38 UTC (permalink / raw)
  To: dev, Gregory Etelson; +Cc: stable

Since commit 1f250674085a ("net: fix packet type for stacked VLAN"),
rte_net_get_ptype() uses |= to set the L2 ptype inside the VLAN
parsing loop. Since pkt_type is already initialized with
RTE_PTYPE_L2_ETHER (0x1), or-ing it with RTE_PTYPE_L2_ETHER_VLAN
(0x6) results in RTE_PTYPE_L2_ETHER_QINQ (0x7). This causes single
VLAN frames to be misidentified as QinQ.

This was detected while testing DPDK 25.11.1 in grout. The net/tap
driver calls rte_net_get_ptype() in tap_verify_csum() to determine
the L2 header length. With the wrong ptype, l2_len is set to 22
(ether + QinQ = 14 + 8) instead of 18 (ether + VLAN = 14 + 4),
shifting the IP header pointer by 4 bytes. The checksum is then
computed on garbage data, causing valid packets to be dropped.

Use a simple assignment to replace the L2 ptype instead. Add a ptype
unit test covering plain Ethernet, VLAN and QinQ L2 detection to
prevent regressions.

Fixes: 1f250674085a ("net: fix packet type for stacked VLAN")
Cc: stable@dpdk.org

Signed-off-by: Robin Jarry <rjarry@redhat.com>
---

Notes:
    v2: added new ptype tests

 app/test/meson.build      |   1 +
 app/test/test_net_ptype.c | 117 ++++++++++++++++++++++++++++++++++++++
 lib/net/rte_net.c         |   2 +-
 3 files changed, 119 insertions(+), 1 deletion(-)
 create mode 100644 app/test/test_net_ptype.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 7d458f9c079a..9f4afb040a46 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -135,6 +135,7 @@ source_file_deps = {
     'test_mp_secondary.c': ['hash'],
     'test_net_ether.c': ['net'],
     'test_net_ip6.c': ['net'],
+    'test_net_ptype.c': ['net'],
     'test_pcapng.c': ['net_null', 'net', 'ethdev', 'pcapng', 'bus_vdev'],
     'test_pdcp.c': ['eventdev', 'pdcp', 'net', 'timer', 'security'],
     'test_pdump.c': ['pdump'] + sample_packet_forward_deps,
diff --git a/app/test/test_net_ptype.c b/app/test/test_net_ptype.c
new file mode 100644
index 000000000000..9619c1d54a8e
--- /dev/null
+++ b/app/test/test_net_ptype.c
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2026 Red Hat, Inc.
+ */
+
+#include <string.h>
+
+#include <rte_mbuf.h>
+#include <rte_net.h>
+
+#include <rte_test.h>
+#include "test.h"
+
+#define MEMPOOL_CACHE_SIZE 0
+#define MBUF_DATA_SIZE 256
+#define NB_MBUF 128
+
+/* Ether()/IP()/UDP()/Raw('x') */
+static const char pkt_ether_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1Q(vlan=42)/IP()/UDP()/Raw('x') */
+static const char pkt_vlan_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+	0x08, 0x00, 0x45, 0x00, 0x00, 0x1d, 0x00, 0x01,
+	0x00, 0x00, 0x40, 0x11, 0x7c, 0xcd, 0x7f, 0x00,
+	0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1AD(vlan=42)/Dot1Q(vlan=43)/IP()/UDP()/Raw('x') */
+static const char pkt_qinq_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0xa8, 0x00, 0x2a,
+	0x81, 0x00, 0x00, 0x2b, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
+static int
+test_get_ptype(struct rte_mempool *pool, const char *pktdata, size_t len,
+	       uint32_t expected_l2, uint8_t expected_l2_len)
+{
+	struct rte_net_hdr_lens hdr_lens;
+	struct rte_mbuf *m;
+	uint32_t ptype;
+	uint32_t l2;
+	char *data;
+
+	m = rte_pktmbuf_alloc(pool);
+	RTE_TEST_ASSERT_NOT_NULL(m, "cannot allocate mbuf");
+
+	data = rte_pktmbuf_append(m, len);
+	if (data == NULL) {
+		rte_pktmbuf_free(m);
+		RTE_TEST_ASSERT_NOT_NULL(data, "cannot append data");
+	}
+
+	memcpy(data, pktdata, len);
+
+	ptype = rte_net_get_ptype(m, &hdr_lens, RTE_PTYPE_ALL_MASK);
+	l2 = ptype & RTE_PTYPE_L2_MASK;
+
+	rte_pktmbuf_free(m);
+
+	RTE_TEST_ASSERT_EQUAL(l2, expected_l2,
+		"unexpected L2 ptype: got 0x%x, expected 0x%x",
+		l2, expected_l2);
+	RTE_TEST_ASSERT_EQUAL(hdr_lens.l2_len, expected_l2_len,
+		"unexpected l2_len: got %u, expected %u",
+		hdr_lens.l2_len, expected_l2_len);
+
+	return 0;
+}
+
+static int
+test_net_ptype(void)
+{
+	struct rte_mempool *pool;
+
+	pool = rte_pktmbuf_pool_create("test_ptype_mbuf_pool",
+			NB_MBUF, MEMPOOL_CACHE_SIZE, 0, MBUF_DATA_SIZE,
+			SOCKET_ID_ANY);
+	RTE_TEST_ASSERT_NOT_NULL(pool, "cannot allocate mbuf pool");
+
+	if (test_get_ptype(pool, pkt_ether_ipv4_udp,
+			   sizeof(pkt_ether_ipv4_udp),
+			   RTE_PTYPE_L2_ETHER, 14))
+		goto fail;
+
+	if (test_get_ptype(pool, pkt_vlan_ipv4_udp,
+			   sizeof(pkt_vlan_ipv4_udp),
+			   RTE_PTYPE_L2_ETHER_VLAN, 18))
+		goto fail;
+
+	if (test_get_ptype(pool, pkt_qinq_ipv4_udp,
+			   sizeof(pkt_qinq_ipv4_udp),
+			   RTE_PTYPE_L2_ETHER_QINQ, 22))
+		goto fail;
+
+	rte_mempool_free(pool);
+	return 0;
+
+fail:
+	rte_mempool_free(pool);
+	return -1;
+}
+
+REGISTER_FAST_TEST(net_ptype_autotest, NOHUGE_OK, ASAN_OK, test_net_ptype);
diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
index 458b4814a9c9..ea5ba7019089 100644
--- a/lib/net/rte_net.c
+++ b/lib/net/rte_net.c
@@ -359,7 +359,7 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
 
 		if (++vlan_depth > RTE_NET_VLAN_MAX_DEPTH)
 			return 0;
-		pkt_type |=
+		pkt_type =
 			proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
 				 RTE_PTYPE_L2_ETHER_VLAN :
 				 RTE_PTYPE_L2_ETHER_QINQ;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v2] net: fix L2 ptype assignment in VLAN loop
  2026-04-22 10:38 ` [PATCH dpdk v2] " Robin Jarry
@ 2026-04-22 13:16   ` Thomas Monjalon
  2026-04-22 13:18     ` Robin Jarry
  2026-04-22 13:23   ` David Marchand
  1 sibling, 1 reply; 26+ messages in thread
From: Thomas Monjalon @ 2026-04-22 13:16 UTC (permalink / raw)
  To: Robin Jarry; +Cc: dev, Gregory Etelson, stable

22/04/2026 12:38, Robin Jarry:
> Since commit 1f250674085a ("net: fix packet type for stacked VLAN"),
> rte_net_get_ptype() uses |= to set the L2 ptype inside the VLAN
> parsing loop. Since pkt_type is already initialized with
> RTE_PTYPE_L2_ETHER (0x1), or-ing it with RTE_PTYPE_L2_ETHER_VLAN
> (0x6) results in RTE_PTYPE_L2_ETHER_QINQ (0x7). This causes single
> VLAN frames to be misidentified as QinQ.

Thanks for finding this major regression.

Minor nit about the title (for whoever will merge it):
we don't really care the problem is in a loop.
I think this title is easier to read:
	net: fix VLAN packet type



^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v2] net: fix L2 ptype assignment in VLAN loop
  2026-04-22 13:16   ` Thomas Monjalon
@ 2026-04-22 13:18     ` Robin Jarry
  0 siblings, 0 replies; 26+ messages in thread
From: Robin Jarry @ 2026-04-22 13:18 UTC (permalink / raw)
  To: Thomas Monjalon; +Cc: dev, Gregory Etelson, stable

Thomas Monjalon, Apr 22, 2026 at 15:16:
> 22/04/2026 12:38, Robin Jarry:
>> Since commit 1f250674085a ("net: fix packet type for stacked VLAN"),
>> rte_net_get_ptype() uses |= to set the L2 ptype inside the VLAN
>> parsing loop. Since pkt_type is already initialized with
>> RTE_PTYPE_L2_ETHER (0x1), or-ing it with RTE_PTYPE_L2_ETHER_VLAN
>> (0x6) results in RTE_PTYPE_L2_ETHER_QINQ (0x7). This causes single
>> VLAN frames to be misidentified as QinQ.
>
> Thanks for finding this major regression.
>
> Minor nit about the title (for whoever will merge it):
> we don't really care the problem is in a loop.
> I think this title is easier to read:
> 	net: fix VLAN packet type

I will send a v3 with a different approach. Currently my fix breaks the
detection of QinQ if it is followed by a plain vlan header.

I'll adjust the title.


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v2] net: fix L2 ptype assignment in VLAN loop
  2026-04-22 10:38 ` [PATCH dpdk v2] " Robin Jarry
  2026-04-22 13:16   ` Thomas Monjalon
@ 2026-04-22 13:23   ` David Marchand
  1 sibling, 0 replies; 26+ messages in thread
From: David Marchand @ 2026-04-22 13:23 UTC (permalink / raw)
  To: Robin Jarry; +Cc: dev, Gregory Etelson, stable

On Wed, 22 Apr 2026 at 12:39, Robin Jarry <rjarry@redhat.com> wrote:
> +static int
> +test_get_ptype(struct rte_mempool *pool, const char *pktdata, size_t len,
> +              uint32_t expected_l2, uint8_t expected_l2_len)
> +{
> +       struct rte_net_hdr_lens hdr_lens;
> +       struct rte_mbuf *m;
> +       uint32_t ptype;
> +       uint32_t l2;
> +       char *data;
> +
> +       m = rte_pktmbuf_alloc(pool);
> +       RTE_TEST_ASSERT_NOT_NULL(m, "cannot allocate mbuf");
> +
> +       data = rte_pktmbuf_append(m, len);
> +       if (data == NULL) {
> +               rte_pktmbuf_free(m);
> +               RTE_TEST_ASSERT_NOT_NULL(data, "cannot append data");
> +       }
> +
> +       memcpy(data, pktdata, len);
> +
> +       ptype = rte_net_get_ptype(m, &hdr_lens, RTE_PTYPE_ALL_MASK);
> +       l2 = ptype & RTE_PTYPE_L2_MASK;
> +
> +       rte_pktmbuf_free(m);
> +
> +       RTE_TEST_ASSERT_EQUAL(l2, expected_l2,
> +               "unexpected L2 ptype: got 0x%x, expected 0x%x",
> +               l2, expected_l2);
> +       RTE_TEST_ASSERT_EQUAL(hdr_lens.l2_len, expected_l2_len,
> +               "unexpected l2_len: got %u, expected %u",
> +               hdr_lens.l2_len, expected_l2_len);

Since this test asks for RTE_PTYPE_ALL_MASK, let's validate the full
returned ptype, and the returned offsets.


> +
> +       return 0;


-- 
David Marchand


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH dpdk v3] net: fix VLAN packet type
  2026-04-22 10:28 [PATCH dpdk] net: fix L2 ptype assignment in VLAN loop Robin Jarry
  2026-04-22 10:35 ` Robin Jarry
  2026-04-22 10:38 ` [PATCH dpdk v2] " Robin Jarry
@ 2026-04-22 13:32 ` Robin Jarry
  2026-04-23  9:19   ` Kevin Traynor
  2026-04-23 11:24 ` [PATCH dpdk v4] " Robin Jarry
  2026-05-18 13:27 ` [PATCH dpdk v5 0/5] Fix and improve VLAN/MPLS parsing in rte_net_get_ptype Robin Jarry
  4 siblings, 1 reply; 26+ messages in thread
From: Robin Jarry @ 2026-04-22 13:32 UTC (permalink / raw)
  To: dev, Gregory Etelson; +Cc: stable

Since commit 1f250674085a ("net: fix packet type for stacked VLAN"),
rte_net_get_ptype() uses |= to set the L2 ptype inside the VLAN
parsing loop. Since pkt_type is already initialized with
RTE_PTYPE_L2_ETHER (0x1), or-ing it with RTE_PTYPE_L2_ETHER_VLAN
(0x6) results in RTE_PTYPE_L2_ETHER_QINQ (0x7). This causes single
VLAN frames to be misidentified as QinQ.

This was detected while testing DPDK 25.11.1 in grout. The net/tap
driver calls rte_net_get_ptype() in tap_verify_csum() to determine the
L2 header length. With the wrong ptype, l2_len is set to 22 (ether
+ QinQ = 14 + 8) instead of 18 (ether + VLAN = 14 + 4), shifting the IP
header pointer by 4 bytes. The checksum is then computed on garbage
data, causing valid packets to be dropped.

Initialize pkt_type to 0 and defer the RTE_PTYPE_L2_ETHER assignment to
the l3 label, only if no VLAN/QinQ type was set in the loop. This avoids
the bitwise-or conflict between the L2 ptype constants entirely.

Add a new net_ptype_autotest unit test that verifies the ptype and
header lengths (l2_len, l3_len, l4_len) returned by rte_net_get_ptype()
for plain Ethernet, single VLAN, stacked VLAN (two 802.1Q tags), and
QinQ (802.1ad + 802.1Q) frames, with both IPv4/IPv6 and UDP/TCP
combinations.

Fixes: 1f250674085a ("net: fix packet type for stacked VLAN")
Cc: stable@dpdk.org

Signed-off-by: Robin Jarry <rjarry@redhat.com>
---

Notes:
    v3:
    
    * changed the approach: initialize pkt_type=0 and only set it to
      RTE_PTYPE_L2_ETHER if neither of VLAN nor QINQ matched.
    * extended the unit tests to check for header lengths and added ipv6 / tcp
      cases.
    
    v2: added new ptype tests

 app/test/meson.build      |   1 +
 app/test/test_net_ptype.c | 231 ++++++++++++++++++++++++++++++++++++++
 lib/net/rte_net.c         |   4 +-
 3 files changed, 235 insertions(+), 1 deletion(-)
 create mode 100644 app/test/test_net_ptype.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 7d458f9c079a..9f4afb040a46 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -135,6 +135,7 @@ source_file_deps = {
     'test_mp_secondary.c': ['hash'],
     'test_net_ether.c': ['net'],
     'test_net_ip6.c': ['net'],
+    'test_net_ptype.c': ['net'],
     'test_pcapng.c': ['net_null', 'net', 'ethdev', 'pcapng', 'bus_vdev'],
     'test_pdcp.c': ['eventdev', 'pdcp', 'net', 'timer', 'security'],
     'test_pdump.c': ['pdump'] + sample_packet_forward_deps,
diff --git a/app/test/test_net_ptype.c b/app/test/test_net_ptype.c
new file mode 100644
index 000000000000..bfe85da13543
--- /dev/null
+++ b/app/test/test_net_ptype.c
@@ -0,0 +1,231 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2026 Red Hat, Inc.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <rte_mbuf.h>
+#include <rte_net.h>
+
+#include <rte_test.h>
+#include "test.h"
+
+#define MEMPOOL_CACHE_SIZE 0
+#define MBUF_DATA_SIZE 256
+#define NB_MBUF 128
+
+/* Ether()/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_ether_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
+/* Ether()/IP()/TCP() */
+static const uint8_t pkt_ether_ipv4_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x40, 0x06,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x14, 0x00, 0x50, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02,
+	0x20, 0x00, 0x91, 0x7c, 0x00, 0x00,
+};
+
+/* Ether()/IPv6()/UDP()/Raw('x') */
+static const uint8_t pkt_ether_ipv6_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x86, 0xdd, 0x60, 0x00,
+	0x00, 0x00, 0x00, 0x09, 0x11, 0x40, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x87, 0x70, 0x78,
+};
+
+/* Ether()/IPv6()/TCP() */
+static const uint8_t pkt_ether_ipv6_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x86, 0xdd, 0x60, 0x00,
+	0x00, 0x00, 0x00, 0x14, 0x06, 0x40, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x14,
+	0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x50, 0x02, 0x20, 0x00, 0x8f, 0x7d,
+	0x00, 0x00,
+};
+
+/* Ether()/IP(options='\x00')/UDP()/Raw('x') -- ihl=6 */
+static const uint8_t pkt_ether_ipv4_opts_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x46, 0x00,
+	0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7b, 0xc9, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1Q(vlan=42)/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_vlan_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+	0x08, 0x00, 0x45, 0x00, 0x00, 0x1d, 0x00, 0x01,
+	0x00, 0x00, 0x40, 0x11, 0x7c, 0xcd, 0x7f, 0x00,
+	0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1Q(vlan=42)/IPv6()/TCP() */
+static const uint8_t pkt_vlan_ipv6_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+	0x86, 0xdd, 0x60, 0x00, 0x00, 0x00, 0x00, 0x14,
+	0x06, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x01, 0x00, 0x14, 0x00, 0x50, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02,
+	0x20, 0x00, 0x8f, 0x7d, 0x00, 0x00,
+};
+
+/* Ether()/Dot1AD(vlan=42)/Dot1Q(vlan=43)/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_qinq_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0xa8, 0x00, 0x2a,
+	0x81, 0x00, 0x00, 0x2b, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1Q(vlan=42)/Dot1Q(vlan=43)/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_vlan_vlan_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+	0x81, 0x00, 0x00, 0x2b, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1AD(vlan=42)/Dot1Q(vlan=43)/IPv6()/TCP() */
+static const uint8_t pkt_qinq_ipv6_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0xa8, 0x00, 0x2a,
+	0x81, 0x00, 0x00, 0x2b, 0x86, 0xdd, 0x60, 0x00,
+	0x00, 0x00, 0x00, 0x14, 0x06, 0x40, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x14,
+	0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x50, 0x02, 0x20, 0x00, 0x8f, 0x7d,
+	0x00, 0x00,
+};
+
+static int
+test_get_ptype(struct rte_mempool *pool, const char *name,
+	       const uint8_t *pktdata, size_t len, uint32_t expected_ptype,
+	       uint8_t expected_l2_len, uint16_t expected_l3_len,
+	       uint8_t expected_l4_len)
+{
+	struct rte_net_hdr_lens hdr_lens;
+	struct rte_mbuf *m;
+	uint32_t ptype;
+	char *data;
+
+	RTE_LOG(INFO, EAL, "%s: %s\n", __func__, name);
+
+	m = rte_pktmbuf_alloc(pool);
+	RTE_TEST_ASSERT_NOT_NULL(m, "cannot allocate mbuf");
+
+	data = rte_pktmbuf_append(m, len);
+	if (data == NULL) {
+		rte_pktmbuf_free(m);
+		RTE_TEST_ASSERT_NOT_NULL(data, "cannot append data");
+	}
+
+	memcpy(data, pktdata, len);
+
+	memset(&hdr_lens, 0, sizeof(hdr_lens));
+	ptype = rte_net_get_ptype(m, &hdr_lens, RTE_PTYPE_ALL_MASK);
+
+	rte_pktmbuf_free(m);
+
+	RTE_TEST_ASSERT_EQUAL(ptype, expected_ptype,
+		"unexpected ptype: got 0x%x, expected 0x%x",
+		ptype, expected_ptype);
+	RTE_TEST_ASSERT_EQUAL(hdr_lens.l2_len, expected_l2_len,
+		"unexpected l2_len: got %u, expected %u",
+		hdr_lens.l2_len, expected_l2_len);
+	RTE_TEST_ASSERT_EQUAL(hdr_lens.l3_len, expected_l3_len,
+		"unexpected l3_len: got %u, expected %u",
+		hdr_lens.l3_len, expected_l3_len);
+	RTE_TEST_ASSERT_EQUAL(hdr_lens.l4_len, expected_l4_len,
+		"unexpected l4_len: got %u, expected %u",
+		hdr_lens.l4_len, expected_l4_len);
+
+	return 0;
+}
+
+#define test_case(pool, pkt, expected_ptype, l2, l3, l4) \
+	test_get_ptype(pool, #pkt, pkt, sizeof(pkt), expected_ptype, l2, l3, l4)
+
+static int
+test_net_ptype(void)
+{
+	struct rte_mempool *pool;
+	int ret = 0;
+
+	pool = rte_pktmbuf_pool_create("test_ptype_mbuf_pool",
+			NB_MBUF, MEMPOOL_CACHE_SIZE, 0, MBUF_DATA_SIZE,
+			SOCKET_ID_ANY);
+	RTE_TEST_ASSERT_NOT_NULL(pool, "cannot allocate mbuf pool");
+
+	ret |= test_case(pool, pkt_ether_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 14, 20, 8);
+	ret |= test_case(pool, pkt_ether_ipv4_tcp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_TCP,
+			 14, 20, 20);
+	ret |= test_case(pool, pkt_ether_ipv6_udp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_UDP,
+			 14, 40, 8);
+	ret |= test_case(pool, pkt_ether_ipv6_tcp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_TCP,
+			 14, 40, 20);
+	ret |= test_case(pool, pkt_ether_ipv4_opts_udp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV4_EXT | RTE_PTYPE_L4_UDP,
+			 14, 24, 8);
+	ret |= test_case(pool, pkt_vlan_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER_VLAN | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 18, 20, 8);
+	ret |= test_case(pool, pkt_vlan_ipv6_tcp,
+			 RTE_PTYPE_L2_ETHER_VLAN | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_TCP,
+			 18, 40, 20);
+	ret |= test_case(pool, pkt_qinq_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER_QINQ | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 22, 20, 8);
+	ret |= test_case(pool, pkt_vlan_vlan_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER_VLAN | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 22, 20, 8);
+	ret |= test_case(pool, pkt_qinq_ipv6_tcp,
+			 RTE_PTYPE_L2_ETHER_QINQ | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_TCP,
+			 22, 40, 20);
+
+	rte_mempool_free(pool);
+
+	return ret;
+}
+
+REGISTER_FAST_TEST(net_ptype_autotest, NOHUGE_OK, ASAN_OK, test_net_ptype);
diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
index 458b4814a9c9..0228f1eb2f18 100644
--- a/lib/net/rte_net.c
+++ b/lib/net/rte_net.c
@@ -331,8 +331,8 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
 	struct rte_net_hdr_lens local_hdr_lens;
 	const struct rte_ether_hdr *eh;
 	struct rte_ether_hdr eh_copy;
-	uint32_t pkt_type = RTE_PTYPE_L2_ETHER;
 	uint32_t off = 0, vlan_depth = 0;
+	uint32_t pkt_type = 0;
 	uint16_t proto;
 	int ret;
 
@@ -392,6 +392,8 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
 	}
 
 l3:
+	if (pkt_type == 0)
+		pkt_type = RTE_PTYPE_L2_ETHER;
 	if ((layers & RTE_PTYPE_L3_MASK) == 0)
 		return pkt_type;
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v3] net: fix VLAN packet type
  2026-04-22 13:32 ` [PATCH dpdk v3] net: fix VLAN packet type Robin Jarry
@ 2026-04-23  9:19   ` Kevin Traynor
  2026-04-23  9:49     ` Robin Jarry
  0 siblings, 1 reply; 26+ messages in thread
From: Kevin Traynor @ 2026-04-23  9:19 UTC (permalink / raw)
  To: Robin Jarry, dev, Gregory Etelson; +Cc: stable

On 4/22/26 2:32 PM, Robin Jarry wrote:
> Since commit 1f250674085a ("net: fix packet type for stacked VLAN"),
> rte_net_get_ptype() uses |= to set the L2 ptype inside the VLAN
> parsing loop. Since pkt_type is already initialized with
> RTE_PTYPE_L2_ETHER (0x1), or-ing it with RTE_PTYPE_L2_ETHER_VLAN
> (0x6) results in RTE_PTYPE_L2_ETHER_QINQ (0x7). This causes single
> VLAN frames to be misidentified as QinQ.
> 
> This was detected while testing DPDK 25.11.1 in grout. The net/tap
> driver calls rte_net_get_ptype() in tap_verify_csum() to determine the
> L2 header length. With the wrong ptype, l2_len is set to 22 (ether
> + QinQ = 14 + 8) instead of 18 (ether + VLAN = 14 + 4), shifting the IP
> header pointer by 4 bytes. The checksum is then computed on garbage
> data, causing valid packets to be dropped.
> 
> Initialize pkt_type to 0 and defer the RTE_PTYPE_L2_ETHER assignment to
> the l3 label, only if no VLAN/QinQ type was set in the loop. This avoids
> the bitwise-or conflict between the L2 ptype constants entirely.
> 
> Add a new net_ptype_autotest unit test that verifies the ptype and
> header lengths (l2_len, l3_len, l4_len) returned by rte_net_get_ptype()
> for plain Ethernet, single VLAN, stacked VLAN (two 802.1Q tags), and
> QinQ (802.1ad + 802.1Q) frames, with both IPv4/IPv6 and UDP/TCP
> combinations.
> 
> Fixes: 1f250674085a ("net: fix packet type for stacked VLAN")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Robin Jarry <rjarry@redhat.com>

Hi Robin,

Thanks for reporting this.

> ---
> 
> Notes:
>     v3:
>     
>     * changed the approach: initialize pkt_type=0 and only set it to
>       RTE_PTYPE_L2_ETHER if neither of VLAN nor QINQ matched.
>     * extended the unit tests to check for header lengths and added ipv6 / tcp
>       cases.
>     
>     v2: added new ptype tests
> 
>  app/test/meson.build      |   1 +
>  app/test/test_net_ptype.c | 231 ++++++++++++++++++++++++++++++++++++++
>  lib/net/rte_net.c         |   4 +-
>  3 files changed, 235 insertions(+), 1 deletion(-)
>  create mode 100644 app/test/test_net_ptype.c
> 

<snip>
> diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
> index 458b4814a9c9..0228f1eb2f18 100644
> --- a/lib/net/rte_net.c
> +++ b/lib/net/rte_net.c
> @@ -331,8 +331,8 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
>  	struct rte_net_hdr_lens local_hdr_lens;
>  	const struct rte_ether_hdr *eh;
>  	struct rte_ether_hdr eh_copy;
> -	uint32_t pkt_type = RTE_PTYPE_L2_ETHER;
>  	uint32_t off = 0, vlan_depth = 0;
> +	uint32_t pkt_type = 0;
>  	uint16_t proto;
>  	int ret;
>  
> @@ -392,6 +392,8 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
>  	}
>  

Not shown in diff, but for:

		pkt_type |=
			proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
				 RTE_PTYPE_L2_ETHER_VLAN :
				 RTE_PTYPE_L2_ETHER_QINQ;

It seems to be produce the right result, but I'm not sure we should be
treating the ptype L2 defines as bitmasks. Maybe I'm wrong and it was
planned, but it looks like a coincidence it works now because QINQ (0x7)
happens to be a superset of VLAN (0x6) for the OR.

Perhaps you could check vlan_depth and assign VLAN or QINQ based on that?

thanks,
Kevin.

>  l3:
> +	if (pkt_type == 0)
> +		pkt_type = RTE_PTYPE_L2_ETHER;
>  	if ((layers & RTE_PTYPE_L3_MASK) == 0)
>  		return pkt_type;
>  


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v3] net: fix VLAN packet type
  2026-04-23  9:19   ` Kevin Traynor
@ 2026-04-23  9:49     ` Robin Jarry
  2026-04-23 10:59       ` Kevin Traynor
  0 siblings, 1 reply; 26+ messages in thread
From: Robin Jarry @ 2026-04-23  9:49 UTC (permalink / raw)
  To: Kevin Traynor, dev, Gregory Etelson; +Cc: stable

Kevin Traynor, Apr 23, 2026 at 11:19:
> Not shown in diff, but for:
>
> 		pkt_type |=
> 			proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
> 				 RTE_PTYPE_L2_ETHER_VLAN :
> 				 RTE_PTYPE_L2_ETHER_QINQ;
>
> It seems to be produce the right result, but I'm not sure we should be
> treating the ptype L2 defines as bitmasks. Maybe I'm wrong and it was
> planned, but it looks like a coincidence it works now because QINQ (0x7)
> happens to be a superset of VLAN (0x6) for the OR.
>
> Perhaps you could check vlan_depth and assign VLAN or QINQ based on that?

I hadn't considered this. It would make sense to have something like:

l3:
	switch (vlan_depth) {
	case 0:
		pkt_type = RTE_PTYPE_L2_ETHER;
		break;
	case 1:
		pkt_type = RTE_PTYPE_L2_ETHER_VLAN;
		break;
	default:
		pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
		break;
	}

Mind that this will report RTE_PTYPE_L2_ETHER_QINQ even if we have
double stacked 802.1Q tags (ether type 0x8100) and no 802.1ad S-tag
(QinQ 0x88A8). I don't think this is an issue, but I'll let you judge.

I can send a v4 with this if that suits you.


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v3] net: fix VLAN packet type
  2026-04-23  9:49     ` Robin Jarry
@ 2026-04-23 10:59       ` Kevin Traynor
  2026-04-23 11:11         ` Robin Jarry
  0 siblings, 1 reply; 26+ messages in thread
From: Kevin Traynor @ 2026-04-23 10:59 UTC (permalink / raw)
  To: Robin Jarry, dev, Gregory Etelson; +Cc: stable

On 4/23/26 10:49 AM, Robin Jarry wrote:
> Kevin Traynor, Apr 23, 2026 at 11:19:
>> Not shown in diff, but for:
>>
>> 		pkt_type |=
>> 			proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
>> 				 RTE_PTYPE_L2_ETHER_VLAN :
>> 				 RTE_PTYPE_L2_ETHER_QINQ;
>>
>> It seems to be produce the right result, but I'm not sure we should be
>> treating the ptype L2 defines as bitmasks. Maybe I'm wrong and it was
>> planned, but it looks like a coincidence it works now because QINQ (0x7)
>> happens to be a superset of VLAN (0x6) for the OR.
>>
>> Perhaps you could check vlan_depth and assign VLAN or QINQ based on that?
> 
> I hadn't considered this. It would make sense to have something like:
> 
> l3:
> 	switch (vlan_depth) {
> 	case 0:
> 		pkt_type = RTE_PTYPE_L2_ETHER;
> 		break;
> 	case 1:
> 		pkt_type = RTE_PTYPE_L2_ETHER_VLAN;
> 		break;
> 	default:
> 		pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
> 		break;
> 	}
> 
> Mind that this will report RTE_PTYPE_L2_ETHER_QINQ even if we have
> double stacked 802.1Q tags (ether type 0x8100) and no 802.1ad S-tag
> (QinQ 0x88A8). I don't think this is an issue, but I'll let you judge.
> 

I'm not sure about this.

> I can send a v4 with this if that suits you.
> 

Setting pkt_type later has some implications for early returns (now i
notice that's present in v3 too)

Would this work ?

@@ -358,10 +358,12 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
                struct rte_vlan_hdr vh_copy;

+               if (vlan_depth == 0) {
+                       pkt_type =
+                               proto ==
rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
+                                        RTE_PTYPE_L2_ETHER_VLAN :
+                                        RTE_PTYPE_L2_ETHER_QINQ;
+               }
                if (++vlan_depth > RTE_NET_VLAN_MAX_DEPTH)
                        return 0;
-               pkt_type |=
-                       proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
-                                RTE_PTYPE_L2_ETHER_VLAN :
-                                RTE_PTYPE_L2_ETHER_QINQ;
                vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
                if (unlikely(vh == NULL))






^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v3] net: fix VLAN packet type
  2026-04-23 10:59       ` Kevin Traynor
@ 2026-04-23 11:11         ` Robin Jarry
  0 siblings, 0 replies; 26+ messages in thread
From: Robin Jarry @ 2026-04-23 11:11 UTC (permalink / raw)
  To: Kevin Traynor, dev, Gregory Etelson; +Cc: stable

Kevin Traynor, Apr 23, 2026 at 12:59:
> Setting pkt_type later has some implications for early returns (now i
> notice that's present in v3 too)
>
> Would this work ?
>
> @@ -358,10 +358,12 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
>                 struct rte_vlan_hdr vh_copy;
>
> +               if (vlan_depth == 0) {
> +                       pkt_type =
> +                               proto ==
> rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
> +                                        RTE_PTYPE_L2_ETHER_VLAN :
> +                                        RTE_PTYPE_L2_ETHER_QINQ;
> +               }
>                 if (++vlan_depth > RTE_NET_VLAN_MAX_DEPTH)
>                         return 0;
> -               pkt_type |=
> -                       proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
> -                                RTE_PTYPE_L2_ETHER_VLAN :
> -                                RTE_PTYPE_L2_ETHER_QINQ;
>                 vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
>                 if (unlikely(vh == NULL))

Yes, that works. I'll send a v4.


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH dpdk v4] net: fix VLAN packet type
  2026-04-22 10:28 [PATCH dpdk] net: fix L2 ptype assignment in VLAN loop Robin Jarry
                   ` (2 preceding siblings ...)
  2026-04-22 13:32 ` [PATCH dpdk v3] net: fix VLAN packet type Robin Jarry
@ 2026-04-23 11:24 ` Robin Jarry
  2026-04-24 16:18   ` Kevin Traynor
  2026-04-25  8:40   ` David Marchand
  2026-05-18 13:27 ` [PATCH dpdk v5 0/5] Fix and improve VLAN/MPLS parsing in rte_net_get_ptype Robin Jarry
  4 siblings, 2 replies; 26+ messages in thread
From: Robin Jarry @ 2026-04-23 11:24 UTC (permalink / raw)
  To: dev, Gregory Etelson; +Cc: stable

Since commit 1f250674085a ("net: fix packet type for stacked VLAN"),
rte_net_get_ptype() uses |= to set the L2 ptype inside the VLAN
parsing loop. Since pkt_type is already initialized with
RTE_PTYPE_L2_ETHER (0x1), or-ing it with RTE_PTYPE_L2_ETHER_VLAN
(0x6) results in RTE_PTYPE_L2_ETHER_QINQ (0x7). This causes single
VLAN frames to be misidentified as QinQ.

This was detected while testing DPDK 25.11.1 in grout. The net/tap
driver calls rte_net_get_ptype() in tap_verify_csum() to determine the
L2 header length. With the wrong ptype, l2_len is set to 22 (ether
+ QinQ = 14 + 8) instead of 18 (ether + VLAN = 14 + 4), shifting the IP
header pointer by 4 bytes. The checksum is then computed on garbage
data, causing valid packets to be dropped.

Set pkt_type to RTE_PTYPE_L2_ETHER_VLAN or RTE_PTYPE_L2_ETHER_QINQ only
for the first level of "VLAN" tag seen. This avoids the bitwise-or
conflict between the L2 ptype constants entirely.

Add a new net_ptype_autotest unit test that verifies the ptype and
header lengths (l2_len, l3_len, l4_len) returned by rte_net_get_ptype()
for plain Ethernet, single VLAN, stacked VLAN (two 802.1Q tags), and
QinQ (802.1ad + 802.1Q) frames, with both IPv4/IPv6 and UDP/TCP
combinations.

Fixes: 1f250674085a ("net: fix packet type for stacked VLAN")
Cc: stable@dpdk.org

Signed-off-by: Robin Jarry <rjarry@redhat.com>
---

Notes:
    v4:
    
    * changed the approach again. Only set VLAN or QINQ ptype for the first
      encountered vlan/qinq ether type.
    * unit tests not changed
    
    v3:
    
    * changed the approach: initialize pkt_type=0 and only set it to
      RTE_PTYPE_L2_ETHER if neither of VLAN nor QINQ matched.
    * extended the unit tests to check for header lengths and added ipv6 / tcp
      cases.
    
    v2: added new ptype tests
    
    v3:
    
    * changed the approach: initialize pkt_type=0 and only set it to
      RTE_PTYPE_L2_ETHER if neither of VLAN nor QINQ matched.
    * extended the unit tests to check for header lengths and added ipv6 / tcp
      cases.
    
    v2: added new ptype tests

 app/test/meson.build      |   1 +
 app/test/test_net_ptype.c | 231 ++++++++++++++++++++++++++++++++++++++
 lib/net/rte_net.c         |  10 +-
 3 files changed, 238 insertions(+), 4 deletions(-)
 create mode 100644 app/test/test_net_ptype.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 7d458f9c079a..9f4afb040a46 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -135,6 +135,7 @@ source_file_deps = {
     'test_mp_secondary.c': ['hash'],
     'test_net_ether.c': ['net'],
     'test_net_ip6.c': ['net'],
+    'test_net_ptype.c': ['net'],
     'test_pcapng.c': ['net_null', 'net', 'ethdev', 'pcapng', 'bus_vdev'],
     'test_pdcp.c': ['eventdev', 'pdcp', 'net', 'timer', 'security'],
     'test_pdump.c': ['pdump'] + sample_packet_forward_deps,
diff --git a/app/test/test_net_ptype.c b/app/test/test_net_ptype.c
new file mode 100644
index 000000000000..bfe85da13543
--- /dev/null
+++ b/app/test/test_net_ptype.c
@@ -0,0 +1,231 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2026 Red Hat, Inc.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <rte_mbuf.h>
+#include <rte_net.h>
+
+#include <rte_test.h>
+#include "test.h"
+
+#define MEMPOOL_CACHE_SIZE 0
+#define MBUF_DATA_SIZE 256
+#define NB_MBUF 128
+
+/* Ether()/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_ether_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
+/* Ether()/IP()/TCP() */
+static const uint8_t pkt_ether_ipv4_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x40, 0x06,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x14, 0x00, 0x50, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02,
+	0x20, 0x00, 0x91, 0x7c, 0x00, 0x00,
+};
+
+/* Ether()/IPv6()/UDP()/Raw('x') */
+static const uint8_t pkt_ether_ipv6_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x86, 0xdd, 0x60, 0x00,
+	0x00, 0x00, 0x00, 0x09, 0x11, 0x40, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x87, 0x70, 0x78,
+};
+
+/* Ether()/IPv6()/TCP() */
+static const uint8_t pkt_ether_ipv6_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x86, 0xdd, 0x60, 0x00,
+	0x00, 0x00, 0x00, 0x14, 0x06, 0x40, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x14,
+	0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x50, 0x02, 0x20, 0x00, 0x8f, 0x7d,
+	0x00, 0x00,
+};
+
+/* Ether()/IP(options='\x00')/UDP()/Raw('x') -- ihl=6 */
+static const uint8_t pkt_ether_ipv4_opts_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x46, 0x00,
+	0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7b, 0xc9, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1Q(vlan=42)/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_vlan_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+	0x08, 0x00, 0x45, 0x00, 0x00, 0x1d, 0x00, 0x01,
+	0x00, 0x00, 0x40, 0x11, 0x7c, 0xcd, 0x7f, 0x00,
+	0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1Q(vlan=42)/IPv6()/TCP() */
+static const uint8_t pkt_vlan_ipv6_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+	0x86, 0xdd, 0x60, 0x00, 0x00, 0x00, 0x00, 0x14,
+	0x06, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x01, 0x00, 0x14, 0x00, 0x50, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02,
+	0x20, 0x00, 0x8f, 0x7d, 0x00, 0x00,
+};
+
+/* Ether()/Dot1AD(vlan=42)/Dot1Q(vlan=43)/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_qinq_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0xa8, 0x00, 0x2a,
+	0x81, 0x00, 0x00, 0x2b, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1Q(vlan=42)/Dot1Q(vlan=43)/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_vlan_vlan_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+	0x81, 0x00, 0x00, 0x2b, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1AD(vlan=42)/Dot1Q(vlan=43)/IPv6()/TCP() */
+static const uint8_t pkt_qinq_ipv6_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0xa8, 0x00, 0x2a,
+	0x81, 0x00, 0x00, 0x2b, 0x86, 0xdd, 0x60, 0x00,
+	0x00, 0x00, 0x00, 0x14, 0x06, 0x40, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x14,
+	0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x50, 0x02, 0x20, 0x00, 0x8f, 0x7d,
+	0x00, 0x00,
+};
+
+static int
+test_get_ptype(struct rte_mempool *pool, const char *name,
+	       const uint8_t *pktdata, size_t len, uint32_t expected_ptype,
+	       uint8_t expected_l2_len, uint16_t expected_l3_len,
+	       uint8_t expected_l4_len)
+{
+	struct rte_net_hdr_lens hdr_lens;
+	struct rte_mbuf *m;
+	uint32_t ptype;
+	char *data;
+
+	RTE_LOG(INFO, EAL, "%s: %s\n", __func__, name);
+
+	m = rte_pktmbuf_alloc(pool);
+	RTE_TEST_ASSERT_NOT_NULL(m, "cannot allocate mbuf");
+
+	data = rte_pktmbuf_append(m, len);
+	if (data == NULL) {
+		rte_pktmbuf_free(m);
+		RTE_TEST_ASSERT_NOT_NULL(data, "cannot append data");
+	}
+
+	memcpy(data, pktdata, len);
+
+	memset(&hdr_lens, 0, sizeof(hdr_lens));
+	ptype = rte_net_get_ptype(m, &hdr_lens, RTE_PTYPE_ALL_MASK);
+
+	rte_pktmbuf_free(m);
+
+	RTE_TEST_ASSERT_EQUAL(ptype, expected_ptype,
+		"unexpected ptype: got 0x%x, expected 0x%x",
+		ptype, expected_ptype);
+	RTE_TEST_ASSERT_EQUAL(hdr_lens.l2_len, expected_l2_len,
+		"unexpected l2_len: got %u, expected %u",
+		hdr_lens.l2_len, expected_l2_len);
+	RTE_TEST_ASSERT_EQUAL(hdr_lens.l3_len, expected_l3_len,
+		"unexpected l3_len: got %u, expected %u",
+		hdr_lens.l3_len, expected_l3_len);
+	RTE_TEST_ASSERT_EQUAL(hdr_lens.l4_len, expected_l4_len,
+		"unexpected l4_len: got %u, expected %u",
+		hdr_lens.l4_len, expected_l4_len);
+
+	return 0;
+}
+
+#define test_case(pool, pkt, expected_ptype, l2, l3, l4) \
+	test_get_ptype(pool, #pkt, pkt, sizeof(pkt), expected_ptype, l2, l3, l4)
+
+static int
+test_net_ptype(void)
+{
+	struct rte_mempool *pool;
+	int ret = 0;
+
+	pool = rte_pktmbuf_pool_create("test_ptype_mbuf_pool",
+			NB_MBUF, MEMPOOL_CACHE_SIZE, 0, MBUF_DATA_SIZE,
+			SOCKET_ID_ANY);
+	RTE_TEST_ASSERT_NOT_NULL(pool, "cannot allocate mbuf pool");
+
+	ret |= test_case(pool, pkt_ether_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 14, 20, 8);
+	ret |= test_case(pool, pkt_ether_ipv4_tcp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_TCP,
+			 14, 20, 20);
+	ret |= test_case(pool, pkt_ether_ipv6_udp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_UDP,
+			 14, 40, 8);
+	ret |= test_case(pool, pkt_ether_ipv6_tcp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_TCP,
+			 14, 40, 20);
+	ret |= test_case(pool, pkt_ether_ipv4_opts_udp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV4_EXT | RTE_PTYPE_L4_UDP,
+			 14, 24, 8);
+	ret |= test_case(pool, pkt_vlan_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER_VLAN | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 18, 20, 8);
+	ret |= test_case(pool, pkt_vlan_ipv6_tcp,
+			 RTE_PTYPE_L2_ETHER_VLAN | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_TCP,
+			 18, 40, 20);
+	ret |= test_case(pool, pkt_qinq_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER_QINQ | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 22, 20, 8);
+	ret |= test_case(pool, pkt_vlan_vlan_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER_VLAN | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 22, 20, 8);
+	ret |= test_case(pool, pkt_qinq_ipv6_tcp,
+			 RTE_PTYPE_L2_ETHER_QINQ | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_TCP,
+			 22, 40, 20);
+
+	rte_mempool_free(pool);
+
+	return ret;
+}
+
+REGISTER_FAST_TEST(net_ptype_autotest, NOHUGE_OK, ASAN_OK, test_net_ptype);
diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
index 458b4814a9c9..a871318b21c2 100644
--- a/lib/net/rte_net.c
+++ b/lib/net/rte_net.c
@@ -357,12 +357,14 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
 		const struct rte_vlan_hdr *vh;
 		struct rte_vlan_hdr vh_copy;
 
+		if (vlan_depth == 0) {
+			pkt_type =
+				proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
+					 RTE_PTYPE_L2_ETHER_VLAN :
+					 RTE_PTYPE_L2_ETHER_QINQ;
+		}
 		if (++vlan_depth > RTE_NET_VLAN_MAX_DEPTH)
 			return 0;
-		pkt_type |=
-			proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
-				 RTE_PTYPE_L2_ETHER_VLAN :
-				 RTE_PTYPE_L2_ETHER_QINQ;
 		vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
 		if (unlikely(vh == NULL))
 			return pkt_type;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v4] net: fix VLAN packet type
  2026-04-23 11:24 ` [PATCH dpdk v4] " Robin Jarry
@ 2026-04-24 16:18   ` Kevin Traynor
  2026-04-25  8:40   ` David Marchand
  1 sibling, 0 replies; 26+ messages in thread
From: Kevin Traynor @ 2026-04-24 16:18 UTC (permalink / raw)
  To: Robin Jarry, dev, Gregory Etelson
  Cc: stable, Thomas Monjalon, David Marchand, luca.boccassi@gmail.com,
	Shani Peretz

On 4/23/26 12:24 PM, Robin Jarry wrote:
> Since commit 1f250674085a ("net: fix packet type for stacked VLAN"),
> rte_net_get_ptype() uses |= to set the L2 ptype inside the VLAN
> parsing loop. Since pkt_type is already initialized with
> RTE_PTYPE_L2_ETHER (0x1), or-ing it with RTE_PTYPE_L2_ETHER_VLAN
> (0x6) results in RTE_PTYPE_L2_ETHER_QINQ (0x7). This causes single
> VLAN frames to be misidentified as QinQ.
> 
> This was detected while testing DPDK 25.11.1 in grout. The net/tap
> driver calls rte_net_get_ptype() in tap_verify_csum() to determine the
> L2 header length. With the wrong ptype, l2_len is set to 22 (ether
> + QinQ = 14 + 8) instead of 18 (ether + VLAN = 14 + 4), shifting the IP
> header pointer by 4 bytes. The checksum is then computed on garbage
> data, causing valid packets to be dropped.
> 
> Set pkt_type to RTE_PTYPE_L2_ETHER_VLAN or RTE_PTYPE_L2_ETHER_QINQ only
> for the first level of "VLAN" tag seen. This avoids the bitwise-or
> conflict between the L2 ptype constants entirely.
> 
> Add a new net_ptype_autotest unit test that verifies the ptype and
> header lengths (l2_len, l3_len, l4_len) returned by rte_net_get_ptype()
> for plain Ethernet, single VLAN, stacked VLAN (two 802.1Q tags), and
> QinQ (802.1ad + 802.1Q) frames, with both IPv4/IPv6 and UDP/TCP
> combinations.
> 
> Fixes: 1f250674085a ("net: fix packet type for stacked VLAN")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Robin Jarry <rjarry@redhat.com>
> ---
> 
Thanks Robin, LGTM.

Acked-by: Kevin Traynor <ktraynor@redhat.com>



^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v4] net: fix VLAN packet type
  2026-04-23 11:24 ` [PATCH dpdk v4] " Robin Jarry
  2026-04-24 16:18   ` Kevin Traynor
@ 2026-04-25  8:40   ` David Marchand
  2026-04-27 10:47     ` Robin Jarry
  2026-05-15 11:17     ` Kevin Traynor
  1 sibling, 2 replies; 26+ messages in thread
From: David Marchand @ 2026-04-25  8:40 UTC (permalink / raw)
  To: Robin Jarry
  Cc: dev, Gregory Etelson, stable, Thomas Monjalon, Kevin Traynor,
	Luca Boccassi

On Thu, 23 Apr 2026 at 13:25, Robin Jarry <rjarry@redhat.com> wrote:
> diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
> index 458b4814a9c9..a871318b21c2 100644
> --- a/lib/net/rte_net.c
> +++ b/lib/net/rte_net.c
> @@ -357,12 +357,14 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
>                 const struct rte_vlan_hdr *vh;
>                 struct rte_vlan_hdr vh_copy;
>
> +               if (vlan_depth == 0) {
> +                       pkt_type =
> +                               proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
> +                                        RTE_PTYPE_L2_ETHER_VLAN :
> +                                        RTE_PTYPE_L2_ETHER_QINQ;
> +               }

This code is becoming too complex.
The original usecase with more than 2 stacked vlan is a bit strange,
but the max depth limit seems just arbitrary (why 8?).
We have clear boundaries, with the size of the packet (see below,
check on vh == NULL).

The offending commit also allows stacking mpls on top of vlan, without
mentioning it.
I think we want this behavior, but still was it intended?

In the end, reverting the previous fix then just advancing off and
breaking once proto is not a vlan/qinq type gives a much simpler fix
when compared to v25.11.

(ignoring indent changes with -w)

$ git diff -w v25.11 lib/net/rte_net.c
diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
index c70b57fdc0..f58d699c83 100644
--- a/lib/net/rte_net.c
+++ b/lib/net/rte_net.c
@@ -349,30 +349,28 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
        if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4))
                goto l3; /* fast path if packet is IPv4 */

-       if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN)) {
+       if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ||
+                       proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
                const struct rte_vlan_hdr *vh;
                struct rte_vlan_hdr vh_copy;

+               if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN))
                        pkt_type = RTE_PTYPE_L2_ETHER_VLAN;
+               else
+                       pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
+
+               do {
                        vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
                        if (unlikely(vh == NULL))
                                return pkt_type;
                        off += sizeof(*vh);
                        hdr_lens->l2_len += sizeof(*vh);
                        proto = vh->eth_proto;
-       } else if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
-               const struct rte_vlan_hdr *vh;
-               struct rte_vlan_hdr vh_copy;
+               } while (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ||
+                               proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ));
+       }

-               pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
-               vh = rte_pktmbuf_read(m, off + sizeof(*vh), sizeof(*vh),
-                       &vh_copy);
-               if (unlikely(vh == NULL))
-                       return pkt_type;
-               off += 2 * sizeof(*vh);
-               hdr_lens->l2_len += 2 * sizeof(*vh);
-               proto = vh->eth_proto;
-       } else if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
+       if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
                (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLSM))) {
                unsigned int i;
                const struct rte_mpls_hdr *mh;


This is untested, but what do you think?


-- 
David Marchand


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v4] net: fix VLAN packet type
  2026-04-25  8:40   ` David Marchand
@ 2026-04-27 10:47     ` Robin Jarry
  2026-04-27 15:53       ` Thomas Monjalon
  2026-05-15 11:17     ` Kevin Traynor
  1 sibling, 1 reply; 26+ messages in thread
From: Robin Jarry @ 2026-04-27 10:47 UTC (permalink / raw)
  To: David Marchand
  Cc: dev, Gregory Etelson, stable, Thomas Monjalon, Kevin Traynor,
	Luca Boccassi

David Marchand, Apr 25, 2026 at 10:40:
> On Thu, 23 Apr 2026 at 13:25, Robin Jarry <rjarry@redhat.com> wrote:
>> diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
>> index 458b4814a9c9..a871318b21c2 100644
>> --- a/lib/net/rte_net.c
>> +++ b/lib/net/rte_net.c
>> @@ -357,12 +357,14 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
>>                 const struct rte_vlan_hdr *vh;
>>                 struct rte_vlan_hdr vh_copy;
>>
>> +               if (vlan_depth == 0) {
>> +                       pkt_type =
>> +                               proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
>> +                                        RTE_PTYPE_L2_ETHER_VLAN :
>> +                                        RTE_PTYPE_L2_ETHER_QINQ;
>> +               }
>
> This code is becoming too complex.
> The original usecase with more than 2 stacked vlan is a bit strange,
> but the max depth limit seems just arbitrary (why 8?).
> We have clear boundaries, with the size of the packet (see below,
> check on vh == NULL).
>
> The offending commit also allows stacking mpls on top of vlan, without
> mentioning it.
> I think we want this behavior, but still was it intended?
>
> In the end, reverting the previous fix then just advancing off and
> breaking once proto is not a vlan/qinq type gives a much simpler fix
> when compared to v25.11.
>
> (ignoring indent changes with -w)
>
> $ git diff -w v25.11 lib/net/rte_net.c
> diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
> index c70b57fdc0..f58d699c83 100644
> --- a/lib/net/rte_net.c
> +++ b/lib/net/rte_net.c
> @@ -349,30 +349,28 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
>         if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4))
>                 goto l3; /* fast path if packet is IPv4 */
>
> -       if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN)) {
> +       if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ||
> +                       proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
>                 const struct rte_vlan_hdr *vh;
>                 struct rte_vlan_hdr vh_copy;
>
> +               if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN))
>                         pkt_type = RTE_PTYPE_L2_ETHER_VLAN;
> +               else
> +                       pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
> +
> +               do {
>                         vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
>                         if (unlikely(vh == NULL))
>                                 return pkt_type;
>                         off += sizeof(*vh);
>                         hdr_lens->l2_len += sizeof(*vh);
>                         proto = vh->eth_proto;
> -       } else if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
> -               const struct rte_vlan_hdr *vh;
> -               struct rte_vlan_hdr vh_copy;
> +               } while (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ||
> +                               proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ));
> +       }
>
> -               pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
> -               vh = rte_pktmbuf_read(m, off + sizeof(*vh), sizeof(*vh),
> -                       &vh_copy);
> -               if (unlikely(vh == NULL))
> -                       return pkt_type;
> -               off += 2 * sizeof(*vh);
> -               hdr_lens->l2_len += 2 * sizeof(*vh);
> -               proto = vh->eth_proto;
> -       } else if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
> +       if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
>                 (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLSM))) {
>                 unsigned int i;
>                 const struct rte_mpls_hdr *mh;
>
>
> This is untested, but what do you think?

That looks correct. But you don't impose a limit in the number of VLANs?

I don't see any good reason to support more than 2 stacked tags.


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v4] net: fix VLAN packet type
  2026-04-27 10:47     ` Robin Jarry
@ 2026-04-27 15:53       ` Thomas Monjalon
  2026-04-30 10:12         ` David Marchand
  0 siblings, 1 reply; 26+ messages in thread
From: Thomas Monjalon @ 2026-04-27 15:53 UTC (permalink / raw)
  To: David Marchand, Robin Jarry
  Cc: dev, Gregory Etelson, stable, Kevin Traynor, Luca Boccassi

27/04/2026 12:47, Robin Jarry:
> David Marchand, Apr 25, 2026 at 10:40:
> > On Thu, 23 Apr 2026 at 13:25, Robin Jarry <rjarry@redhat.com> wrote:
> > This is untested, but what do you think?
> 
> That looks correct. But you don't impose a limit in the number of VLANs?
> 
> I don't see any good reason to support more than 2 stacked tags.

Look in the mailing list.
I remember it was to limit a risk of infinite loop.



^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v4] net: fix VLAN packet type
  2026-04-27 15:53       ` Thomas Monjalon
@ 2026-04-30 10:12         ` David Marchand
  2026-04-30 11:06           ` Morten Brørup
  0 siblings, 1 reply; 26+ messages in thread
From: David Marchand @ 2026-04-30 10:12 UTC (permalink / raw)
  To: Thomas Monjalon
  Cc: Robin Jarry, dev, Gregory Etelson, stable, Kevin Traynor,
	Luca Boccassi

On Mon, 27 Apr 2026 at 17:53, Thomas Monjalon <thomas@monjalon.net> wrote:
>
> 27/04/2026 12:47, Robin Jarry:
> > David Marchand, Apr 25, 2026 at 10:40:
> > > On Thu, 23 Apr 2026 at 13:25, Robin Jarry <rjarry@redhat.com> wrote:
> > > This is untested, but what do you think?
> >
> > That looks correct. But you don't impose a limit in the number of VLANs?
> >
> > I don't see any good reason to support more than 2 stacked tags.
>
> Look in the mailing list.
> I remember it was to limit a risk of infinite loop.

Defensive programming (iiuc why it was introduced) seems a bad idea.
It imposes a restriction for no good reason.
The same can be achieved by checking the offset vs packet length
(rte_pktmbuf_read), and no arbitrary limit needed.

I am for simply reverting the patch in LTS releases.
We can come up with a new change in main for Gregory usecase, but it
does not seem like something we want to backport in LTS.


-- 
David Marchand


^ permalink raw reply	[flat|nested] 26+ messages in thread

* RE: [PATCH dpdk v4] net: fix VLAN packet type
  2026-04-30 10:12         ` David Marchand
@ 2026-04-30 11:06           ` Morten Brørup
  0 siblings, 0 replies; 26+ messages in thread
From: Morten Brørup @ 2026-04-30 11:06 UTC (permalink / raw)
  To: dev
  Cc: Robin Jarry, Gregory Etelson, stable, Kevin Traynor,
	Luca Boccassi, David Marchand, Thomas Monjalon

> From: David Marchand [mailto:david.marchand@redhat.com]
> Sent: Thursday, 30 April 2026 12.12
> 
> On Mon, 27 Apr 2026 at 17:53, Thomas Monjalon <thomas@monjalon.net>
> wrote:
> >
> > 27/04/2026 12:47, Robin Jarry:
> > > David Marchand, Apr 25, 2026 at 10:40:
> > > > On Thu, 23 Apr 2026 at 13:25, Robin Jarry <rjarry@redhat.com>
> wrote:
> > > > This is untested, but what do you think?
> > >
> > > That looks correct. But you don't impose a limit in the number of
> VLANs?
> > >
> > > I don't see any good reason to support more than 2 stacked tags.
> >
> > Look in the mailing list.
> > I remember it was to limit a risk of infinite loop.
> 
> Defensive programming (iiuc why it was introduced) seems a bad idea.
> It imposes a restriction for no good reason.
> The same can be achieved by checking the offset vs packet length
> (rte_pktmbuf_read), and no arbitrary limit needed.
> 
> I am for simply reverting the patch in LTS releases.
> We can come up with a new change in main for Gregory usecase, but it
> does not seem like something we want to backport in LTS.
> 

I just took a look at rte_net_get_ptype().

The MPLS parser is also broken.
It simply skips 5 MPLS labels without checking if they are MPLS labels at all.

And there's no PPPoE parser.


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v4] net: fix VLAN packet type
  2026-04-25  8:40   ` David Marchand
  2026-04-27 10:47     ` Robin Jarry
@ 2026-05-15 11:17     ` Kevin Traynor
  1 sibling, 0 replies; 26+ messages in thread
From: Kevin Traynor @ 2026-05-15 11:17 UTC (permalink / raw)
  To: David Marchand, Robin Jarry
  Cc: dev, Gregory Etelson, stable, Thomas Monjalon, Luca Boccassi

On 4/25/26 10:40 AM, David Marchand wrote:
> On Thu, 23 Apr 2026 at 13:25, Robin Jarry <rjarry@redhat.com> wrote:
>> diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
>> index 458b4814a9c9..a871318b21c2 100644
>> --- a/lib/net/rte_net.c
>> +++ b/lib/net/rte_net.c
>> @@ -357,12 +357,14 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
>>                 const struct rte_vlan_hdr *vh;
>>                 struct rte_vlan_hdr vh_copy;
>>
>> +               if (vlan_depth == 0) {
>> +                       pkt_type =
>> +                               proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
>> +                                        RTE_PTYPE_L2_ETHER_VLAN :
>> +                                        RTE_PTYPE_L2_ETHER_QINQ;
>> +               }
> 
> This code is becoming too complex.
> The original usecase with more than 2 stacked vlan is a bit strange,
> but the max depth limit seems just arbitrary (why 8?).
> We have clear boundaries, with the size of the packet (see below,
> check on vh == NULL).
> 
> The offending commit also allows stacking mpls on top of vlan, without
> mentioning it.
> I think we want this behavior, but still was it intended?
> 
> In the end, reverting the previous fix then just advancing off and
> breaking once proto is not a vlan/qinq type gives a much simpler fix
> when compared to v25.11.
> 
> (ignoring indent changes with -w)
> 
> $ git diff -w v25.11 lib/net/rte_net.c
> diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
> index c70b57fdc0..f58d699c83 100644
> --- a/lib/net/rte_net.c
> +++ b/lib/net/rte_net.c
> @@ -349,30 +349,28 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
>         if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4))
>                 goto l3; /* fast path if packet is IPv4 */
> 
> -       if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN)) {
> +       if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ||
> +                       proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
>                 const struct rte_vlan_hdr *vh;
>                 struct rte_vlan_hdr vh_copy;
> 
> +               if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN))
>                         pkt_type = RTE_PTYPE_L2_ETHER_VLAN;
> +               else
> +                       pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
> +
> +               do {
>                         vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
>                         if (unlikely(vh == NULL))
>                                 return pkt_type;
>                         off += sizeof(*vh);
>                         hdr_lens->l2_len += sizeof(*vh);
>                         proto = vh->eth_proto;
> -       } else if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
> -               const struct rte_vlan_hdr *vh;
> -               struct rte_vlan_hdr vh_copy;
> +               } while (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ||
> +                               proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ));
> +       }
> 
> -               pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
> -               vh = rte_pktmbuf_read(m, off + sizeof(*vh), sizeof(*vh),
> -                       &vh_copy);
> -               if (unlikely(vh == NULL))
> -                       return pkt_type;
> -               off += 2 * sizeof(*vh);
> -               hdr_lens->l2_len += 2 * sizeof(*vh);
> -               proto = vh->eth_proto;
> -       } else if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
> +       if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
>                 (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLSM))) {
>                 unsigned int i;
>                 const struct rte_mpls_hdr *mh;
> 
> 
> This is untested, but what do you think?
> 
> 

This version LGTM. Maybe we should consider returning NULL on invalid
packets


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH dpdk v5 0/5] Fix and improve VLAN/MPLS parsing in rte_net_get_ptype
  2026-04-22 10:28 [PATCH dpdk] net: fix L2 ptype assignment in VLAN loop Robin Jarry
                   ` (3 preceding siblings ...)
  2026-04-23 11:24 ` [PATCH dpdk v4] " Robin Jarry
@ 2026-05-18 13:27 ` Robin Jarry
  2026-05-18 13:27   ` [PATCH dpdk v5 1/5] Revert "net: fix packet type for stacked VLAN" Robin Jarry
                     ` (4 more replies)
  4 siblings, 5 replies; 26+ messages in thread
From: Robin Jarry @ 2026-05-18 13:27 UTC (permalink / raw)
  To: dev

Revert commit 1f250674085a ("net: fix packet type for stacked VLAN")
which introduced a regression causing single VLAN frames to be
misidentified as QinQ due to incorrect use of |= on the ptype.

Replace the separate VLAN and QinQ code paths with a single loop
that handles arbitrarily stacked VLAN tags. Fix the MPLS path to
use the bottom of stack bit instead of a hardcoded label limit,
and parse the L3 protocol of the MPLS payload by inspecting the
first nibble.

Add unit tests for rte_net_get_ptype covering Ethernet, VLAN, QinQ,
MPLS, IPv4/IPv6, and truncated packet scenarios.

v5:

* Split the series in multiple patches.
* Revert the commit that introduced the bug.
* Add support for stacked VLAN/QINQ as suggested by David.
* Fix MPLS bottom of stack detection. Try to guess MPLS payload.
* Add more test cases.

v4:

* changed the approach again. Only set VLAN or QINQ ptype for the first
  encountered vlan/qinq ether type.
* unit tests not changed

v3:

* changed the approach: initialize pkt_type=0 and only set it to
  RTE_PTYPE_L2_ETHER if neither of VLAN nor QINQ matched.
* extended the unit tests to check for header lengths and added ipv6 / tcp
  cases.

v2: added new ptype tests

v3:

* changed the approach: initialize pkt_type=0 and only set it to
  RTE_PTYPE_L2_ETHER if neither of VLAN nor QINQ matched.
* extended the unit tests to check for header lengths and added ipv6 / tcp
  cases.

v2: added new ptype tests

Robin Jarry (5):
  Revert "net: fix packet type for stacked VLAN"
  net: support multiple stacked VLAN tags
  net: add unit tests for rte_net_get_ptype
  net: parse L3 protocol after MPLS labels
  net: add truncated packet tests for rte_net_get_ptype

 app/test/meson.build      |   1 +
 app/test/test_net_ptype.c | 326 ++++++++++++++++++++++++++++++++++++++
 lib/net/rte_net.c         |  72 +++++----
 3 files changed, 369 insertions(+), 30 deletions(-)
 create mode 100644 app/test/test_net_ptype.c

-- 
2.54.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH dpdk v5 1/5] Revert "net: fix packet type for stacked VLAN"
  2026-05-18 13:27 ` [PATCH dpdk v5 0/5] Fix and improve VLAN/MPLS parsing in rte_net_get_ptype Robin Jarry
@ 2026-05-18 13:27   ` Robin Jarry
  2026-05-18 13:27   ` [PATCH dpdk v5 2/5] net: support multiple stacked VLAN tags Robin Jarry
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 26+ messages in thread
From: Robin Jarry @ 2026-05-18 13:27 UTC (permalink / raw)
  To: dev, Gregory Etelson; +Cc: stable

This reverts commit 1f250674085aeb4ffd15ac2519a68efc04faf7ac.

rte_net_get_ptype() now uses |= to set the L2 ptype inside the VLAN
parsing loop. Since pkt_type is already initialized with
RTE_PTYPE_L2_ETHER (0x1), or-ing it with RTE_PTYPE_L2_ETHER_VLAN (0x6)
results in RTE_PTYPE_L2_ETHER_QINQ (0x7). This causes single VLAN frames
to be misidentified as QinQ.

This was detected while testing DPDK 25.11.1 in grout. The net/tap
driver calls rte_net_get_ptype() in tap_verify_csum() to determine the
L2 header length. With the wrong ptype, l2_len is set to 22 (ether
+ QinQ = 14 + 8) instead of 18 (ether + VLAN = 14 + 4), shifting the IP
header pointer by 4 bytes. The checksum is then computed on garbage
data, causing valid packets to be dropped.

Bugzilla ID: 1941
Fixes: 1f250674085a ("net: fix packet type for stacked VLAN")
Cc: stable@dpdk.org
Signed-off-by: Robin Jarry <rjarry@redhat.com>
---
 lib/net/rte_net.c | 29 +++++++++++++++--------------
 1 file changed, 15 insertions(+), 14 deletions(-)

diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
index 458b4814a9c9..c70b57fdc0f8 100644
--- a/lib/net/rte_net.c
+++ b/lib/net/rte_net.c
@@ -320,9 +320,6 @@ rte_net_skip_ip6_ext(uint16_t proto, const struct rte_mbuf *m, uint32_t *off,
 	return -1;
 }
 
-/* limit number of supported VLAN headers */
-#define RTE_NET_VLAN_MAX_DEPTH 8
-
 /* parse mbuf data to get packet type */
 RTE_EXPORT_SYMBOL(rte_net_get_ptype)
 uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
@@ -332,7 +329,7 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
 	const struct rte_ether_hdr *eh;
 	struct rte_ether_hdr eh_copy;
 	uint32_t pkt_type = RTE_PTYPE_L2_ETHER;
-	uint32_t off = 0, vlan_depth = 0;
+	uint32_t off = 0;
 	uint16_t proto;
 	int ret;
 
@@ -352,26 +349,30 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
 	if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4))
 		goto l3; /* fast path if packet is IPv4 */
 
-	while (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ||
-	       proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
+	if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN)) {
 		const struct rte_vlan_hdr *vh;
 		struct rte_vlan_hdr vh_copy;
 
-		if (++vlan_depth > RTE_NET_VLAN_MAX_DEPTH)
-			return 0;
-		pkt_type |=
-			proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ?
-				 RTE_PTYPE_L2_ETHER_VLAN :
-				 RTE_PTYPE_L2_ETHER_QINQ;
+		pkt_type = RTE_PTYPE_L2_ETHER_VLAN;
 		vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
 		if (unlikely(vh == NULL))
 			return pkt_type;
 		off += sizeof(*vh);
 		hdr_lens->l2_len += sizeof(*vh);
 		proto = vh->eth_proto;
-	}
+	} else if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
+		const struct rte_vlan_hdr *vh;
+		struct rte_vlan_hdr vh_copy;
 
-	if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
+		pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
+		vh = rte_pktmbuf_read(m, off + sizeof(*vh), sizeof(*vh),
+			&vh_copy);
+		if (unlikely(vh == NULL))
+			return pkt_type;
+		off += 2 * sizeof(*vh);
+		hdr_lens->l2_len += 2 * sizeof(*vh);
+		proto = vh->eth_proto;
+	} else if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
 		(proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLSM))) {
 		unsigned int i;
 		const struct rte_mpls_hdr *mh;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH dpdk v5 2/5] net: support multiple stacked VLAN tags
  2026-05-18 13:27 ` [PATCH dpdk v5 0/5] Fix and improve VLAN/MPLS parsing in rte_net_get_ptype Robin Jarry
  2026-05-18 13:27   ` [PATCH dpdk v5 1/5] Revert "net: fix packet type for stacked VLAN" Robin Jarry
@ 2026-05-18 13:27   ` Robin Jarry
  2026-05-18 13:27   ` [PATCH dpdk v5 3/5] net: add unit tests for rte_net_get_ptype Robin Jarry
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 26+ messages in thread
From: Robin Jarry @ 2026-05-18 13:27 UTC (permalink / raw)
  To: dev; +Cc: David Marchand

The VLAN and QinQ code paths in rte_net_get_ptype handle at most two
tags with duplicated logic. Replace them with a single loop that
consumes all consecutive VLAN/QinQ headers regardless of depth.

Bugzilla ID: 1941
Suggested-by: David Marchand <david.marchand@redhat.com>
Signed-off-by: Robin Jarry <rjarry@redhat.com>
---
 lib/net/rte_net.c | 35 ++++++++++++++++-------------------
 1 file changed, 16 insertions(+), 19 deletions(-)

diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
index c70b57fdc0f8..d3cded961fb5 100644
--- a/lib/net/rte_net.c
+++ b/lib/net/rte_net.c
@@ -349,29 +349,26 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
 	if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4))
 		goto l3; /* fast path if packet is IPv4 */
 
-	if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN)) {
+	if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN)) ||
+		(proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ))) {
 		const struct rte_vlan_hdr *vh;
 		struct rte_vlan_hdr vh_copy;
 
-		pkt_type = RTE_PTYPE_L2_ETHER_VLAN;
-		vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
-		if (unlikely(vh == NULL))
-			return pkt_type;
-		off += sizeof(*vh);
-		hdr_lens->l2_len += sizeof(*vh);
-		proto = vh->eth_proto;
-	} else if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
-		const struct rte_vlan_hdr *vh;
-		struct rte_vlan_hdr vh_copy;
+		if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN))
+			pkt_type = RTE_PTYPE_L2_ETHER_VLAN;
+		else
+			pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
+
+		do {
+			vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
+			if (unlikely(vh == NULL))
+				return pkt_type;
+			off += sizeof(*vh);
+			hdr_lens->l2_len += sizeof(*vh);
+			proto = vh->eth_proto;
+		} while (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ||
+			proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ));
 
-		pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
-		vh = rte_pktmbuf_read(m, off + sizeof(*vh), sizeof(*vh),
-			&vh_copy);
-		if (unlikely(vh == NULL))
-			return pkt_type;
-		off += 2 * sizeof(*vh);
-		hdr_lens->l2_len += 2 * sizeof(*vh);
-		proto = vh->eth_proto;
 	} else if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
 		(proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLSM))) {
 		unsigned int i;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH dpdk v5 3/5] net: add unit tests for rte_net_get_ptype
  2026-05-18 13:27 ` [PATCH dpdk v5 0/5] Fix and improve VLAN/MPLS parsing in rte_net_get_ptype Robin Jarry
  2026-05-18 13:27   ` [PATCH dpdk v5 1/5] Revert "net: fix packet type for stacked VLAN" Robin Jarry
  2026-05-18 13:27   ` [PATCH dpdk v5 2/5] net: support multiple stacked VLAN tags Robin Jarry
@ 2026-05-18 13:27   ` Robin Jarry
  2026-05-18 13:27   ` [PATCH dpdk v5 4/5] net: parse L3 protocol after MPLS labels Robin Jarry
  2026-05-18 13:27   ` [PATCH dpdk v5 5/5] net: add truncated packet tests for rte_net_get_ptype Robin Jarry
  4 siblings, 0 replies; 26+ messages in thread
From: Robin Jarry @ 2026-05-18 13:27 UTC (permalink / raw)
  To: dev

There is no test coverage for rte_net_get_ptype. Add a test suite that
exercises plain Ethernet, single VLAN, QinQ, double VLAN, IPv4 with
options, IPv6, TCP and UDP combinations.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
---
 app/test/meson.build      |   1 +
 app/test/test_net_ptype.c | 231 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 232 insertions(+)
 create mode 100644 app/test/test_net_ptype.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 7d458f9c079a..9f4afb040a46 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -135,6 +135,7 @@ source_file_deps = {
     'test_mp_secondary.c': ['hash'],
     'test_net_ether.c': ['net'],
     'test_net_ip6.c': ['net'],
+    'test_net_ptype.c': ['net'],
     'test_pcapng.c': ['net_null', 'net', 'ethdev', 'pcapng', 'bus_vdev'],
     'test_pdcp.c': ['eventdev', 'pdcp', 'net', 'timer', 'security'],
     'test_pdump.c': ['pdump'] + sample_packet_forward_deps,
diff --git a/app/test/test_net_ptype.c b/app/test/test_net_ptype.c
new file mode 100644
index 000000000000..bfe85da13543
--- /dev/null
+++ b/app/test/test_net_ptype.c
@@ -0,0 +1,231 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2026 Red Hat, Inc.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <rte_mbuf.h>
+#include <rte_net.h>
+
+#include <rte_test.h>
+#include "test.h"
+
+#define MEMPOOL_CACHE_SIZE 0
+#define MBUF_DATA_SIZE 256
+#define NB_MBUF 128
+
+/* Ether()/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_ether_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
+/* Ether()/IP()/TCP() */
+static const uint8_t pkt_ether_ipv4_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x40, 0x06,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x14, 0x00, 0x50, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02,
+	0x20, 0x00, 0x91, 0x7c, 0x00, 0x00,
+};
+
+/* Ether()/IPv6()/UDP()/Raw('x') */
+static const uint8_t pkt_ether_ipv6_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x86, 0xdd, 0x60, 0x00,
+	0x00, 0x00, 0x00, 0x09, 0x11, 0x40, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x87, 0x70, 0x78,
+};
+
+/* Ether()/IPv6()/TCP() */
+static const uint8_t pkt_ether_ipv6_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x86, 0xdd, 0x60, 0x00,
+	0x00, 0x00, 0x00, 0x14, 0x06, 0x40, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x14,
+	0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x50, 0x02, 0x20, 0x00, 0x8f, 0x7d,
+	0x00, 0x00,
+};
+
+/* Ether()/IP(options='\x00')/UDP()/Raw('x') -- ihl=6 */
+static const uint8_t pkt_ether_ipv4_opts_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x46, 0x00,
+	0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7b, 0xc9, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1Q(vlan=42)/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_vlan_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+	0x08, 0x00, 0x45, 0x00, 0x00, 0x1d, 0x00, 0x01,
+	0x00, 0x00, 0x40, 0x11, 0x7c, 0xcd, 0x7f, 0x00,
+	0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1Q(vlan=42)/IPv6()/TCP() */
+static const uint8_t pkt_vlan_ipv6_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+	0x86, 0xdd, 0x60, 0x00, 0x00, 0x00, 0x00, 0x14,
+	0x06, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x01, 0x00, 0x14, 0x00, 0x50, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02,
+	0x20, 0x00, 0x8f, 0x7d, 0x00, 0x00,
+};
+
+/* Ether()/Dot1AD(vlan=42)/Dot1Q(vlan=43)/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_qinq_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0xa8, 0x00, 0x2a,
+	0x81, 0x00, 0x00, 0x2b, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1Q(vlan=42)/Dot1Q(vlan=43)/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_vlan_vlan_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+	0x81, 0x00, 0x00, 0x2b, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x7c, 0xcd, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00,
+	0x00, 0x01, 0x00, 0x35, 0x00, 0x35, 0x00, 0x09,
+	0x89, 0x6f, 0x78,
+};
+
+/* Ether()/Dot1AD(vlan=42)/Dot1Q(vlan=43)/IPv6()/TCP() */
+static const uint8_t pkt_qinq_ipv6_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0xa8, 0x00, 0x2a,
+	0x81, 0x00, 0x00, 0x2b, 0x86, 0xdd, 0x60, 0x00,
+	0x00, 0x00, 0x00, 0x14, 0x06, 0x40, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x14,
+	0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x50, 0x02, 0x20, 0x00, 0x8f, 0x7d,
+	0x00, 0x00,
+};
+
+static int
+test_get_ptype(struct rte_mempool *pool, const char *name,
+	       const uint8_t *pktdata, size_t len, uint32_t expected_ptype,
+	       uint8_t expected_l2_len, uint16_t expected_l3_len,
+	       uint8_t expected_l4_len)
+{
+	struct rte_net_hdr_lens hdr_lens;
+	struct rte_mbuf *m;
+	uint32_t ptype;
+	char *data;
+
+	RTE_LOG(INFO, EAL, "%s: %s\n", __func__, name);
+
+	m = rte_pktmbuf_alloc(pool);
+	RTE_TEST_ASSERT_NOT_NULL(m, "cannot allocate mbuf");
+
+	data = rte_pktmbuf_append(m, len);
+	if (data == NULL) {
+		rte_pktmbuf_free(m);
+		RTE_TEST_ASSERT_NOT_NULL(data, "cannot append data");
+	}
+
+	memcpy(data, pktdata, len);
+
+	memset(&hdr_lens, 0, sizeof(hdr_lens));
+	ptype = rte_net_get_ptype(m, &hdr_lens, RTE_PTYPE_ALL_MASK);
+
+	rte_pktmbuf_free(m);
+
+	RTE_TEST_ASSERT_EQUAL(ptype, expected_ptype,
+		"unexpected ptype: got 0x%x, expected 0x%x",
+		ptype, expected_ptype);
+	RTE_TEST_ASSERT_EQUAL(hdr_lens.l2_len, expected_l2_len,
+		"unexpected l2_len: got %u, expected %u",
+		hdr_lens.l2_len, expected_l2_len);
+	RTE_TEST_ASSERT_EQUAL(hdr_lens.l3_len, expected_l3_len,
+		"unexpected l3_len: got %u, expected %u",
+		hdr_lens.l3_len, expected_l3_len);
+	RTE_TEST_ASSERT_EQUAL(hdr_lens.l4_len, expected_l4_len,
+		"unexpected l4_len: got %u, expected %u",
+		hdr_lens.l4_len, expected_l4_len);
+
+	return 0;
+}
+
+#define test_case(pool, pkt, expected_ptype, l2, l3, l4) \
+	test_get_ptype(pool, #pkt, pkt, sizeof(pkt), expected_ptype, l2, l3, l4)
+
+static int
+test_net_ptype(void)
+{
+	struct rte_mempool *pool;
+	int ret = 0;
+
+	pool = rte_pktmbuf_pool_create("test_ptype_mbuf_pool",
+			NB_MBUF, MEMPOOL_CACHE_SIZE, 0, MBUF_DATA_SIZE,
+			SOCKET_ID_ANY);
+	RTE_TEST_ASSERT_NOT_NULL(pool, "cannot allocate mbuf pool");
+
+	ret |= test_case(pool, pkt_ether_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 14, 20, 8);
+	ret |= test_case(pool, pkt_ether_ipv4_tcp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_TCP,
+			 14, 20, 20);
+	ret |= test_case(pool, pkt_ether_ipv6_udp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_UDP,
+			 14, 40, 8);
+	ret |= test_case(pool, pkt_ether_ipv6_tcp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_TCP,
+			 14, 40, 20);
+	ret |= test_case(pool, pkt_ether_ipv4_opts_udp,
+			 RTE_PTYPE_L2_ETHER | RTE_PTYPE_L3_IPV4_EXT | RTE_PTYPE_L4_UDP,
+			 14, 24, 8);
+	ret |= test_case(pool, pkt_vlan_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER_VLAN | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 18, 20, 8);
+	ret |= test_case(pool, pkt_vlan_ipv6_tcp,
+			 RTE_PTYPE_L2_ETHER_VLAN | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_TCP,
+			 18, 40, 20);
+	ret |= test_case(pool, pkt_qinq_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER_QINQ | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 22, 20, 8);
+	ret |= test_case(pool, pkt_vlan_vlan_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER_VLAN | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 22, 20, 8);
+	ret |= test_case(pool, pkt_qinq_ipv6_tcp,
+			 RTE_PTYPE_L2_ETHER_QINQ | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_TCP,
+			 22, 40, 20);
+
+	rte_mempool_free(pool);
+
+	return ret;
+}
+
+REGISTER_FAST_TEST(net_ptype_autotest, NOHUGE_OK, ASAN_OK, test_net_ptype);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH dpdk v5 4/5] net: parse L3 protocol after MPLS labels
  2026-05-18 13:27 ` [PATCH dpdk v5 0/5] Fix and improve VLAN/MPLS parsing in rte_net_get_ptype Robin Jarry
                     ` (2 preceding siblings ...)
  2026-05-18 13:27   ` [PATCH dpdk v5 3/5] net: add unit tests for rte_net_get_ptype Robin Jarry
@ 2026-05-18 13:27   ` Robin Jarry
  2026-05-18 18:00     ` Stephen Hemminger
  2026-05-18 13:27   ` [PATCH dpdk v5 5/5] net: add truncated packet tests for rte_net_get_ptype Robin Jarry
  4 siblings, 1 reply; 26+ messages in thread
From: Robin Jarry @ 2026-05-18 13:27 UTC (permalink / raw)
  To: dev

rte_net_get_ptype stops at the MPLS layer and never identifies the L3
protocol of the payload. Also, the label parsing uses a fixed maximum of
5 headers instead of checking the bottom of stack bit.

Use the bottom of stack bit to consume all labels and inspect the first
nibble of the payload to determine if it is IPv4 or IPv6.

Add test cases to verify this works. Ensure that an unknown protocol
after MPLS (e.g. ARP) does not produce a bogus L3 type.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
---
 app/test/test_net_ptype.c | 57 +++++++++++++++++++++++++++++++++++++++
 lib/net/rte_net.c         | 34 ++++++++++++++++-------
 2 files changed, 81 insertions(+), 10 deletions(-)

diff --git a/app/test/test_net_ptype.c b/app/test/test_net_ptype.c
index bfe85da13543..cc7026077191 100644
--- a/app/test/test_net_ptype.c
+++ b/app/test/test_net_ptype.c
@@ -118,6 +118,51 @@ static const uint8_t pkt_vlan_vlan_ipv4_udp[] = {
 	0x89, 0x6f, 0x78,
 };
 
+/* Ether(type=MPLS)/MPLS(label=42,s=1)/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_mpls_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0x47, 0x00, 0x02,
+	0xa1, 0x40, 0x45, 0x00, 0x00, 0x1d, 0x00, 0x01,
+	0x00, 0x00, 0x40, 0x11, 0x7c, 0xcd, 0x7f, 0x00,
+	0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x89, 0x6f, 0x78,
+};
+
+/* Ether(type=MPLS)/MPLS(label=42,s=0)/MPLS(label=43,s=1)/IPv6()/TCP() */
+static const uint8_t pkt_mpls2_ipv6_tcp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0x47, 0x00, 0x02,
+	0xa0, 0x40, 0x00, 0x02, 0xb1, 0x40, 0x60, 0x00,
+	0x00, 0x00, 0x00, 0x14, 0x06, 0x40, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x14,
+	0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x50, 0x02, 0x20, 0x00, 0x8f, 0x7d,
+	0x00, 0x00,
+};
+
+/* Ether(type=MPLSM)/MPLS(label=42,s=1)/IP()/UDP()/Raw('x') */
+static const uint8_t pkt_mplsm_ipv4_udp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0x48, 0x00, 0x02,
+	0xa1, 0x40, 0x45, 0x00, 0x00, 0x1d, 0x00, 0x01,
+	0x00, 0x00, 0x40, 0x11, 0x7c, 0xcd, 0x7f, 0x00,
+	0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x00, 0x35,
+	0x00, 0x35, 0x00, 0x09, 0x89, 0x6f, 0x78,
+};
+
+/* Ether(type=MPLS)/MPLS(label=42,s=1)/ARP() -- unknown L3 after MPLS */
+static const uint8_t pkt_mpls_arp[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0x47, 0x00, 0x02,
+	0xa1, 0x40, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04,
+	0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x7f, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x7f, 0x00, 0x00, 0x02,
+};
+
 /* Ether()/Dot1AD(vlan=42)/Dot1Q(vlan=43)/IPv6()/TCP() */
 static const uint8_t pkt_qinq_ipv6_tcp[] = {
 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
@@ -222,6 +267,18 @@ test_net_ptype(void)
 	ret |= test_case(pool, pkt_qinq_ipv6_tcp,
 			 RTE_PTYPE_L2_ETHER_QINQ | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_TCP,
 			 22, 40, 20);
+	ret |= test_case(pool, pkt_mpls_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER_MPLS | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 18, 20, 8);
+	ret |= test_case(pool, pkt_mpls2_ipv6_tcp,
+			 RTE_PTYPE_L2_ETHER_MPLS | RTE_PTYPE_L3_IPV6 | RTE_PTYPE_L4_TCP,
+			 22, 40, 20);
+	ret |= test_case(pool, pkt_mplsm_ipv4_udp,
+			 RTE_PTYPE_L2_ETHER_MPLS | RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_UDP,
+			 18, 20, 8);
+	ret |= test_case(pool, pkt_mpls_arp,
+			 RTE_PTYPE_L2_ETHER_MPLS,
+			 18, 0, 0);
 
 	rte_mempool_free(pool);
 
diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
index d3cded961fb5..3bb5fbc16d43 100644
--- a/lib/net/rte_net.c
+++ b/lib/net/rte_net.c
@@ -371,22 +371,36 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
 
 	} else if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
 		(proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLSM))) {
-		unsigned int i;
 		const struct rte_mpls_hdr *mh;
 		struct rte_mpls_hdr mh_copy;
+		const uint8_t *nimble;
+		uint8_t nimble_copy;
 
-#define MAX_MPLS_HDR 5
-		for (i = 0; i < MAX_MPLS_HDR; i++) {
-			mh = rte_pktmbuf_read(m, off + (i * sizeof(*mh)),
-				sizeof(*mh), &mh_copy);
+		pkt_type = RTE_PTYPE_L2_ETHER_MPLS;
+
+		/* consume all labels until bottom of stack is reached */
+		do {
+			mh = rte_pktmbuf_read(m, off, sizeof(*mh), &mh_copy);
 			if (unlikely(mh == NULL))
 				return pkt_type;
-		}
-		if (i == MAX_MPLS_HDR)
+			off += sizeof(*mh);
+			hdr_lens->l2_len += sizeof(*mh);
+		} while (!mh->bs);
+
+		/* try to guess what is the payload based on the first 4 bits */
+		nimble = rte_pktmbuf_read(m, off, sizeof(*nimble), &nimble_copy);
+		if (nimble == NULL)
 			return pkt_type;
-		pkt_type = RTE_PTYPE_L2_ETHER_MPLS;
-		hdr_lens->l2_len += (sizeof(*mh) * i);
-		return pkt_type;
+		switch (*nimble & 0xf0) {
+		case 0x40:
+			proto = RTE_BE16(RTE_ETHER_TYPE_IPV4);
+			break;
+		case 0x60:
+			proto = RTE_BE16(RTE_ETHER_TYPE_IPV6);
+			break;
+		default:
+			return pkt_type;
+		}
 	}
 
 l3:
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH dpdk v5 5/5] net: add truncated packet tests for rte_net_get_ptype
  2026-05-18 13:27 ` [PATCH dpdk v5 0/5] Fix and improve VLAN/MPLS parsing in rte_net_get_ptype Robin Jarry
                     ` (3 preceding siblings ...)
  2026-05-18 13:27   ` [PATCH dpdk v5 4/5] net: parse L3 protocol after MPLS labels Robin Jarry
@ 2026-05-18 13:27   ` Robin Jarry
  4 siblings, 0 replies; 26+ messages in thread
From: Robin Jarry @ 2026-05-18 13:27 UTC (permalink / raw)
  To: dev

Ensure rte_net_get_ptype handles truncated packets gracefully by
stopping at the last layer it can fully parse.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
---
 app/test/test_net_ptype.c | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/app/test/test_net_ptype.c b/app/test/test_net_ptype.c
index cc7026077191..332ace5dd929 100644
--- a/app/test/test_net_ptype.c
+++ b/app/test/test_net_ptype.c
@@ -163,6 +163,32 @@ static const uint8_t pkt_mpls_arp[] = {
 	0x00, 0x00, 0x7f, 0x00, 0x00, 0x02,
 };
 
+/* Ether()/Dot1Q() -- VLAN header truncated */
+static const uint8_t pkt_vlan_trunc[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x2a,
+};
+
+/* Ether(type=MPLS) -- MPLS header truncated */
+static const uint8_t pkt_mpls_trunc[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0x47, 0x00, 0x02,
+};
+
+/* Ether(type=MPLS)/MPLS(label=42,s=1) -- no payload after label */
+static const uint8_t pkt_mpls_no_payload[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x88, 0x47, 0x00, 0x02,
+	0xa1, 0x40,
+};
+
+/* Ether()/IP() -- IPv4 header truncated */
+static const uint8_t pkt_ipv4_trunc[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1d, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+};
+
 /* Ether()/Dot1AD(vlan=42)/Dot1Q(vlan=43)/IPv6()/TCP() */
 static const uint8_t pkt_qinq_ipv6_tcp[] = {
 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
@@ -279,6 +305,18 @@ test_net_ptype(void)
 	ret |= test_case(pool, pkt_mpls_arp,
 			 RTE_PTYPE_L2_ETHER_MPLS,
 			 18, 0, 0);
+	ret |= test_case(pool, pkt_vlan_trunc,
+			 RTE_PTYPE_L2_ETHER_VLAN,
+			 14, 0, 0);
+	ret |= test_case(pool, pkt_mpls_trunc,
+			 RTE_PTYPE_L2_ETHER_MPLS,
+			 14, 0, 0);
+	ret |= test_case(pool, pkt_mpls_no_payload,
+			 RTE_PTYPE_L2_ETHER_MPLS,
+			 18, 0, 0);
+	ret |= test_case(pool, pkt_ipv4_trunc,
+			 RTE_PTYPE_L2_ETHER,
+			 14, 0, 0);
 
 	rte_mempool_free(pool);
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH dpdk v5 4/5] net: parse L3 protocol after MPLS labels
  2026-05-18 13:27   ` [PATCH dpdk v5 4/5] net: parse L3 protocol after MPLS labels Robin Jarry
@ 2026-05-18 18:00     ` Stephen Hemminger
  0 siblings, 0 replies; 26+ messages in thread
From: Stephen Hemminger @ 2026-05-18 18:00 UTC (permalink / raw)
  To: Robin Jarry; +Cc: dev

On Mon, 18 May 2026 15:27:18 +0200
Robin Jarry <rjarry@redhat.com> wrote:

> rte_net_get_ptype stops at the MPLS layer and never identifies the L3
> protocol of the payload. Also, the label parsing uses a fixed maximum of
> 5 headers instead of checking the bottom of stack bit.
> 
> Use the bottom of stack bit to consume all labels and inspect the first
> nibble of the payload to determine if it is IPv4 or IPv6.
> 
> Add test cases to verify this works. Ensure that an unknown protocol
> after MPLS (e.g. ARP) does not produce a bogus L3 type.
> 
> Signed-off-by: Robin Jarry <rjarry@redhat.com>
> ---
Re: [PATCH dpdk v5 4/5] net: parse L3 protocol after MPLS labels

Info:

The local variable name `nimble` (and its companion `nimble_copy`) appears
to be a typo for `nibble`. A nibble is the standard term for 4 bits, which
is what the code reads and masks against `0xf0`. The commit message itself
uses the correct spelling ("inspect the first nibble of the payload"), so
only the code is affected.

	const uint8_t *nimble;
	uint8_t nimble_copy;
	...
	nimble = rte_pktmbuf_read(m, off, sizeof(*nimble), &nimble_copy);
	if (nimble == NULL)
		return pkt_type;
	switch (*nimble & 0xf0) {

Suggest renaming to `nibble`/`nibble_copy` for clarity.

^ permalink raw reply	[flat|nested] 26+ messages in thread

end of thread, other threads:[~2026-05-18 18:00 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-22 10:28 [PATCH dpdk] net: fix L2 ptype assignment in VLAN loop Robin Jarry
2026-04-22 10:35 ` Robin Jarry
2026-04-22 10:38 ` [PATCH dpdk v2] " Robin Jarry
2026-04-22 13:16   ` Thomas Monjalon
2026-04-22 13:18     ` Robin Jarry
2026-04-22 13:23   ` David Marchand
2026-04-22 13:32 ` [PATCH dpdk v3] net: fix VLAN packet type Robin Jarry
2026-04-23  9:19   ` Kevin Traynor
2026-04-23  9:49     ` Robin Jarry
2026-04-23 10:59       ` Kevin Traynor
2026-04-23 11:11         ` Robin Jarry
2026-04-23 11:24 ` [PATCH dpdk v4] " Robin Jarry
2026-04-24 16:18   ` Kevin Traynor
2026-04-25  8:40   ` David Marchand
2026-04-27 10:47     ` Robin Jarry
2026-04-27 15:53       ` Thomas Monjalon
2026-04-30 10:12         ` David Marchand
2026-04-30 11:06           ` Morten Brørup
2026-05-15 11:17     ` Kevin Traynor
2026-05-18 13:27 ` [PATCH dpdk v5 0/5] Fix and improve VLAN/MPLS parsing in rte_net_get_ptype Robin Jarry
2026-05-18 13:27   ` [PATCH dpdk v5 1/5] Revert "net: fix packet type for stacked VLAN" Robin Jarry
2026-05-18 13:27   ` [PATCH dpdk v5 2/5] net: support multiple stacked VLAN tags Robin Jarry
2026-05-18 13:27   ` [PATCH dpdk v5 3/5] net: add unit tests for rte_net_get_ptype Robin Jarry
2026-05-18 13:27   ` [PATCH dpdk v5 4/5] net: parse L3 protocol after MPLS labels Robin Jarry
2026-05-18 18:00     ` Stephen Hemminger
2026-05-18 13:27   ` [PATCH dpdk v5 5/5] net: add truncated packet tests for rte_net_get_ptype Robin Jarry

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox