DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 4/9] net/gve: validate buf ID before processing Rx packet
From: Joshua Washington @ 2026-07-01 18:35 UTC (permalink / raw)
  To: Jeroen de Borst, Joshua Washington, Ankit Garg
  Cc: dev, stable, Jasper Tran O'Leary
In-Reply-To: <20260701183528.2443032-1-joshwash@google.com>

The buffer id is part of the RX completion descriptor for packets in the
DQ format. This value can technically go up to 64K, while the max RX
ring size is 4K, meaning that there could similarly be an expected 4K RX
buffer IDs. Validate that the RX buffer ID is valid before attempting to
access it in the sw_ring to prevent a potential out of bounds in the
event of a hardware error.

Fixes: 1aed73b23ac0 ("net/gve: support out-of-order completions on DQ Rx")
Cc: stable@dpdk.org
Signed-off-by: Joshua Washington <joshwash@google.com>
Reviewed-by: Jasper Tran O'Leary <jtranoleary@google.com>
---
 drivers/net/gve/gve_rx_dqo.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/net/gve/gve_rx_dqo.c b/drivers/net/gve/gve_rx_dqo.c
index cc343f3fd8..c4e2d32067 100644
--- a/drivers/net/gve/gve_rx_dqo.c
+++ b/drivers/net/gve/gve_rx_dqo.c
@@ -200,6 +200,11 @@ gve_rx_burst_dqo(void *rx_queue, struct rte_mbuf **rx_pkts, uint16_t nb_pkts)
 		}
 
 		rx_buf_id = rte_le_to_cpu_16(rx_desc->buf_id);
+		if (unlikely(rx_buf_id >= rxq->nb_rx_desc)) {
+			PMD_DRV_DP_LOG(ERR, "Invalid buf_id %d", rx_buf_id);
+			continue;
+		}
+
 		rxm = rxq->sw_ring[rx_buf_id];
 		gve_completed_buf_list_push(rxq, rx_buf_id);
 
-- 
2.55.0.rc0.799.gd6f94ed593-goog


^ permalink raw reply related

* [PATCH 3/9] net/gve: copy data to QPL buffer when mbuf read does not
From: Joshua Washington @ 2026-07-01 18:35 UTC (permalink / raw)
  To: Jeroen de Borst, Joshua Washington, Junfeng Guo, Xiaoyun Li
  Cc: dev, stable, Jasper Tran O'Leary
In-Reply-To: <20260701183528.2443032-1-joshwash@google.com>

The rte_pktmbuf_read method does not guarantee that data will be copied
from an mbuf. If the requested data is all contiguous, the method will
instead return a pointer to the memory location within the buffer that
should be read from, leaving the destination buffer empty.

This is problematic for TSO/multi-segment TX packets which only make use
of two mbufs. If all data in the second mbuf is contiguous, the data
will not be read to QPL memory.

Update the QPL copy logic to copy if the rte_pktmbuf_read does not.

Fixes: a46583cf43c8 ("net/gve: support Rx/Tx")
Cc: stable@dpdk.org
Signed-off-by: Joshua Washington <joshwash@google.com>
Reviewed-by: Jasper Tran O'Leary <jtranoleary@google.com>
---
 drivers/net/gve/gve_tx.c | 34 ++++++++++++++++++++++------------
 1 file changed, 22 insertions(+), 12 deletions(-)

diff --git a/drivers/net/gve/gve_tx.c b/drivers/net/gve/gve_tx.c
index 59c82b04ed..2f5a7b0a2e 100644
--- a/drivers/net/gve/gve_tx.c
+++ b/drivers/net/gve/gve_tx.c
@@ -273,6 +273,9 @@ gve_tx_burst_qpl(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
 		gve_tx_clean_swr_qpl(txq);
 
 	for (nb_tx = 0; nb_tx < nb_pkts; nb_tx++) {
+		const void *mbuf_header_addr;
+		void *qpl_write_addr;
+
 		tx_pkt = *tx_pkts++;
 		ol_flags = tx_pkt->ol_flags;
 
@@ -317,26 +320,33 @@ gve_tx_burst_qpl(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
 					goto end_of_tx;
 			}
 		}
-		if (tx_pkt->nb_segs == 1 || ol_flags & RTE_MBUF_F_TX_TCP_SEG)
-			rte_memcpy((void *)(size_t)(fifo_addr + txq->fifo_base),
-				   (void *)(size_t)addr, hlen);
-		else
-			rte_pktmbuf_read(tx_pkt, 0, hlen,
-					 (void *)(size_t)(fifo_addr + txq->fifo_base));
+
+		qpl_write_addr = (void *)(size_t)(fifo_addr + txq->fifo_base);
+		mbuf_header_addr = rte_pktmbuf_read(tx_pkt, 0, hlen, qpl_write_addr);
+
+		/* Header data is linear in the mbuf head. Copy directly. */
+		if (mbuf_header_addr != qpl_write_addr)
+			rte_memcpy(qpl_write_addr, mbuf_header_addr, hlen);
+
 		gve_tx_fill_pkt_desc(txd, tx_pkt, nb_used, hlen, fifo_addr);
 
 		if (ol_flags & RTE_MBUF_F_TX_TCP_SEG) {
+			const void *mbuf_payload_addr;
+
 			tx_id = (tx_id + 1) & mask;
 			txd = &txr[tx_id];
 			addr = (uint64_t)(tx_pkt->buf_addr) + tx_pkt->data_off + hlen;
 			fifo_addr = gve_tx_alloc_from_fifo(txq, tx_id, tx_pkt->pkt_len - hlen);
-			if (tx_pkt->nb_segs == 1)
-				rte_memcpy((void *)(size_t)(fifo_addr + txq->fifo_base),
-					   (void *)(size_t)addr,
+			qpl_write_addr = (void *)(txq->fifo_base + fifo_addr);
+			mbuf_payload_addr = rte_pktmbuf_read(tx_pkt, hlen, tx_pkt->pkt_len - hlen,
+							     qpl_write_addr);
+
+			/* Payload data is contiguous. Take the offset from the
+			 * read request and copy from there.
+			 */
+			if (mbuf_payload_addr != qpl_write_addr)
+				rte_memcpy(qpl_write_addr, mbuf_payload_addr,
 					   tx_pkt->pkt_len - hlen);
-			else
-				rte_pktmbuf_read(tx_pkt, hlen, tx_pkt->pkt_len - hlen,
-						 (void *)(size_t)(fifo_addr + txq->fifo_base));
 
 			gve_tx_fill_seg_desc(txd, ol_flags, tx_offload,
 					     tx_pkt->pkt_len - hlen, fifo_addr);
-- 
2.55.0.rc0.799.gd6f94ed593-goog


^ permalink raw reply related

* [PATCH 2/9] net/gve: delay adding mbuf head to software ring
From: Joshua Washington @ 2026-07-01 18:35 UTC (permalink / raw)
  To: Jeroen de Borst, Joshua Washington, Xiaoyun Li, Junfeng Guo
  Cc: dev, stable, Jasper Tran O'Leary
In-Reply-To: <20260701183528.2443032-1-joshwash@google.com>

The GQ TX datapath was set up to write the mbuf head into the sw_ring
before writing the descriptors. This poses a problem because it's
possible for the packet to be dropped due to lacking the FIFO space to
do a proper TX. In such a case, the packet won't be sent, and will lead
to leaked mbufs in the subsequent segments.

There is also no real reason that the head mbuf must be set in the
sw_ring separately from the others; the mbuf chain is not actually
walked as part of GQ TX.

Fixes: a46583cf43c8 ("net/gve: support Rx/Tx")
Cc: stable@dpdk.org
Signed-off-by: Joshua Washington <joshwash@google.com>
Reviewed-by: Jasper Tran O'Leary <jtranoleary@google.com>
---
 drivers/net/gve/gve_tx.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/net/gve/gve_tx.c b/drivers/net/gve/gve_tx.c
index 5c73c21b8d..59c82b04ed 100644
--- a/drivers/net/gve/gve_tx.c
+++ b/drivers/net/gve/gve_tx.c
@@ -301,7 +301,6 @@ gve_tx_burst_qpl(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
 			(uint32_t)(tx_offload.l2_len + tx_offload.l3_len + tx_offload.l4_len) :
 			tx_pkt->pkt_len;
 
-		sw_ring[sw_id] = tx_pkt;
 		if (!is_fifo_avail(txq, hlen)) {
 			gve_tx_clean(txq);
 			if (!is_fifo_avail(txq, hlen))
@@ -344,13 +343,14 @@ gve_tx_burst_qpl(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
 		}
 
 		/* record mbuf in sw_ring for free */
-		for (i = 1; i < first->nb_segs; i++) {
+		for (i = 0; i < first->nb_segs; i++) {
+			if (!tx_pkt)
+				break;
+			sw_ring[sw_id] = tx_pkt;
 			sw_id = (sw_id + 1) & mask;
 			tx_pkt = tx_pkt->next;
-			sw_ring[sw_id] = tx_pkt;
 		}
 
-		sw_id = (sw_id + 1) & mask;
 		tx_id = (tx_id + 1) & mask;
 
 		txq->nb_free -= nb_used;
-- 
2.55.0.rc0.799.gd6f94ed593-goog


^ permalink raw reply related

* [PATCH 1/9] net/gve: clear out shared memory region for stats report
From: Joshua Washington @ 2026-07-01 18:35 UTC (permalink / raw)
  To: Jeroen de Borst, Joshua Washington, Rushil Gupta, Ferruh Yigit
  Cc: dev, stable, Mark Blasko
In-Reply-To: <20260701183528.2443032-1-joshwash@google.com>

The stats report memzone is allocated from hugepage memory which could
possibly have had sensitive data from a previous DPDK invocation.

Clear out the buffer before sharing the memory region with the virtual
device to protect guest memory.

Fixes: 458b53dec01e ("net/gve: enable imissed stats for GQ format")
Cc: stable@dpdk.org
Signed-off-by: Joshua Washington <joshwash@google.com>
Reviewed-by: Mark Blasko <blasko@google.com>
---
 drivers/net/gve/gve_ethdev.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/net/gve/gve_ethdev.c b/drivers/net/gve/gve_ethdev.c
index 0b02dcb3ad..f73784a109 100644
--- a/drivers/net/gve/gve_ethdev.c
+++ b/drivers/net/gve/gve_ethdev.c
@@ -306,6 +306,8 @@ gve_alloc_stats_report(struct gve_priv *priv,
 	if (!priv->stats_report_mem)
 		return -ENOMEM;
 
+	memset(priv->stats_report_mem->addr, 0, priv->stats_report_mem->len);
+
 	/* offset by skipping stats written by gve. */
 	priv->stats_start_idx = (GVE_TX_STATS_REPORT_NUM * nb_tx_queues) +
 		(GVE_RX_STATS_REPORT_NUM * nb_rx_queues);
-- 
2.55.0.rc0.799.gd6f94ed593-goog


^ permalink raw reply related

* [PATCH 0/9] Stability fixes for GVE
From: Joshua Washington @ 2026-07-01 18:35 UTC (permalink / raw)
  Cc: dev, Joshua Washington

This patch series consists of mostly unrelated fixes in the GVE driver.

Joshua Washington (9):
  net/gve: clear out shared memory region for stats report
  net/gve: delay adding mbuf head to software ring
  net/gve: copy data to QPL buffer when mbuf read does not
  net/gve: validate buf ID before processing Rx packet
  net/gve: set mbuf to null in software ring after use
  net/gve: free ctx mbuf if packet dropped after first segment
  net/gve: increase range of DMA memzone ids to 64 bits
  net/gve: don't reset ring size bounds to default on reset
  net/gve: restrict max ring size in GQ QPL to 2K

 drivers/net/gve/base/gve_adminq.c | 12 ++++++---
 drivers/net/gve/base/gve_osdep.h  |  4 +--
 drivers/net/gve/gve_ethdev.c      |  8 +++---
 drivers/net/gve/gve_ethdev.h      |  1 +
 drivers/net/gve/gve_rx.c          |  3 +++
 drivers/net/gve/gve_rx_dqo.c      |  6 +++++
 drivers/net/gve/gve_tx.c          | 42 +++++++++++++++++++------------
 7 files changed, 52 insertions(+), 24 deletions(-)

-- 
2.55.0.rc0.799.gd6f94ed593-goog


^ permalink raw reply

* Re: [PATCH 0/2] fix dmadev incomplete ABI validation
From: David Marchand @ 2026-07-01 17:01 UTC (permalink / raw)
  To: datshan, Chengwen Feng; +Cc: thomas, dev, Stephen Hemminger, Bruce Richardson
In-Reply-To: <CAJFAV8wOpRcDPip62f4EuAv_HDK_PuoGf75ZdqiKX3Er14OQXA@mail.gmail.com>

On Wed, 1 Jul 2026 at 10:11, David Marchand <david.marchand@redhat.com> wrote:
> On Tue, 30 Jun 2026 at 15:24, <datshan@qq.com> wrote:
> >
> > From: datshan <datshan@qq.com>
> >
> > Fix dmadev incomplete ABI validation.
> >
> > Chengwen Feng (2):
> >   dmadev: fix incomplete configuration validation
> >   test/dmadev: add config and vchan validation tests
> >
> >  app/test/test_dmadev_api.c | 186 ++++++++++++++++++++++++++++++++++---
> >  lib/dmadev/rte_dmadev.c    | 156 +++++++++++++++++++++----------
> >  2 files changed, 283 insertions(+), 59 deletions(-)
> >
>
> This series is not versionned, but I guess this is a new revision.
> Don't forget to flag your submissions with a version number in the future.
> I marked the previous patches as superseded in patchwork.
>
> Also provide a high level summary of the differences between versions.
>
> All of those steps are listed in the contributing guide, I suggest
> (re-)reading the whole guide, with a highlight on:
> https://doc.dpdk.org/guides/contributing/patches.html#steps-to-getting-your-patch-merged
>
> Thanks, I'll try to find some time to review this week.

Well, enforcing *now* that some fields are 0 is actually breaking ABI
for existing applications.
This change should be deferred to 26.11, and backporting to a LTS is
not possible.

Opinions?


-- 
David Marchand


^ permalink raw reply

* RE: [PATCH v3 7/8] ip_frag: remove use of rte_memcpy
From: Konstantin Ananyev @ 2026-07-01 16:53 UTC (permalink / raw)
  To: Stephen Hemminger, dev@dpdk.org
In-Reply-To: <20260701162127.207318-8-stephen@networkplumber.org>



> Replace rte_memcpy with assignment or plain memcpy to
> get more static checking.
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---
>  lib/ip_frag/rte_ipv6_fragmentation.c | 3 +--
>  lib/ip_frag/rte_ipv6_reassembly.c    | 5 ++---
>  2 files changed, 3 insertions(+), 5 deletions(-)
> 
> diff --git a/lib/ip_frag/rte_ipv6_fragmentation.c
> b/lib/ip_frag/rte_ipv6_fragmentation.c
> index c81f2402e3..86dd2e65ce 100644
> --- a/lib/ip_frag/rte_ipv6_fragmentation.c
> +++ b/lib/ip_frag/rte_ipv6_fragmentation.c
> @@ -6,7 +6,6 @@
>  #include <errno.h>
> 
>  #include <eal_export.h>
> -#include <rte_memcpy.h>
> 
>  #include "ip_frag_common.h"
> 
> @@ -24,7 +23,7 @@ __fill_ipv6hdr_frag(struct rte_ipv6_hdr *dst,
>  {
>  	struct rte_ipv6_fragment_ext *fh;
> 
> -	rte_memcpy(dst, src, sizeof(*dst));
> +	*dst = *src;
>  	dst->payload_len = rte_cpu_to_be_16(len);
>  	dst->proto = IPPROTO_FRAGMENT;
> 
> diff --git a/lib/ip_frag/rte_ipv6_reassembly.c b/lib/ip_frag/rte_ipv6_reassembly.c
> index 9aa2f2d08b..cae4266f97 100644
> --- a/lib/ip_frag/rte_ipv6_reassembly.c
> +++ b/lib/ip_frag/rte_ipv6_reassembly.c
> @@ -5,7 +5,6 @@
>  #include <stddef.h>
> 
>  #include <eal_export.h>
> -#include <rte_memcpy.h>
> 
>  #include "ip_frag_common.h"
> 
> @@ -145,8 +144,8 @@ rte_ipv6_frag_reassemble_packet(struct rte_ip_frag_tbl
> *tbl,
>  	int32_t ip_len;
>  	int32_t trim;
> 
> -	rte_memcpy(&key.src_dst[0], &ip_hdr->src_addr, 16);
> -	rte_memcpy(&key.src_dst[2], &ip_hdr->dst_addr, 16);
> +	memcpy(&key.src_dst[0], &ip_hdr->src_addr, 16);
> +	memcpy(&key.src_dst[2], &ip_hdr->dst_addr, 16);
> 
>  	key.id = frag_hdr->id;
>  	key.key_len = IPV6_KEYLEN;
> --

Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>

> 2.53.0


^ permalink raw reply

* RE: [PATCH v3 4/8] ip_frag: drop IPv6 fragments with per-fragment headers
From: Konstantin Ananyev @ 2026-07-01 16:52 UTC (permalink / raw)
  To: Stephen Hemminger, dev@dpdk.org; +Cc: stable@dpdk.org
In-Reply-To: <20260701162127.207318-5-stephen@networkplumber.org>


> IPv6 reassembly assumes the fragment header directly follows the IPv6
> header. ipv6_frag_reassemble() patches the next-header field and removes
> the fragment header at a fixed offset, so a fragment with per-fragment
> extension headers before the fragment header reassembles to a corrupt
> datagram.
> 
> Drop the fragment when its fragment header does not directly follow the
> IPv6 header. Headers after the fragment header are payload and are
> unaffected.
> 
> RFC 8200 permits discarding packets with extension headers out of the
> recommended order, and RFC 9099 recommends dropping non-conforming
> fragmented packets.
> 
> Fixes: 4f1a8f633862 ("ip_frag: add IPv6 reassembly")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---
>  lib/ip_frag/rte_ipv6_reassembly.c | 22 ++++++++++++++++++++++
>  1 file changed, 22 insertions(+)
> 
> diff --git a/lib/ip_frag/rte_ipv6_reassembly.c b/lib/ip_frag/rte_ipv6_reassembly.c
> index 0e809a01e5..b6f623d53b 100644
> --- a/lib/ip_frag/rte_ipv6_reassembly.c
> +++ b/lib/ip_frag/rte_ipv6_reassembly.c
> @@ -180,6 +180,28 @@ rte_ipv6_frag_reassemble_packet(struct rte_ip_frag_tbl
> *tbl,
>  		return NULL;
>  	}
> 
> +	/*
> +	 * Only a fragment header directly following the IPv6 header is
> supported.
> +	 * Per-fragment (unfragmentable) extension headers placed
> +	 * before the fragment header are not handled: ipv6_frag_reassemble()
> +	 * patches the IPv6 header's next-header field and removes the fragment
> +	 * header assuming it sits immediately after the IPv6 header, so such a
> +	 * fragment would be reassembled into a corrupt datagram. Drop it.
> +	 *
> +	 * Extension headers after the fragment header (destination options,
> +	 * AH, ESP, upper-layer) are part of the fragmentable payload and are
> +	 * reassembled as opaque bytes, so they are not affected. The test uses
> +	 * the fragment header's position rather than l3_len so that callers
> +	 * which include later headers in l3_len are not rejected.
> +	 */
> +	if ((uintptr_t)frag_hdr != (uintptr_t)(ip_hdr + 1)) {
> +		IP_FRAG_LOG(DEBUG,
> +			    "%s:%d: drop fragment with header before frag header,
> offset %zu\n",
> +			    __func__, __LINE__, (uintptr_t)frag_hdr -
> (uintptr_t)ip_hdr);
> +		IP_FRAG_MBUF2DR(dr, mb);
> +		return NULL;
> +	}
> +
>  	if (unlikely(trim > 0))
>  		rte_pktmbuf_trim(mb, trim);
> 
> --

Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>

> 2.53.0


^ permalink raw reply

* RE: [PATCH v3 8/8] doc: add release note about ip_frag changes
From: Konstantin Ananyev @ 2026-07-01 16:42 UTC (permalink / raw)
  To: Stephen Hemminger, dev@dpdk.org
In-Reply-To: <20260701162127.207318-9-stephen@networkplumber.org>



> These changes maybe surprising and need some documentation.
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---
>  doc/guides/rel_notes/release_26_07.rst | 10 ++++++++++
>  1 file changed, 10 insertions(+)
> 
> diff --git a/doc/guides/rel_notes/release_26_07.rst
> b/doc/guides/rel_notes/release_26_07.rst
> index 4ca0a9ac77..4918cb59ed 100644
> --- a/doc/guides/rel_notes/release_26_07.rst
> +++ b/doc/guides/rel_notes/release_26_07.rst
> @@ -241,6 +241,16 @@ API Changes
>    - ``rte_pmd_mlx5_enable_steering``
>    - ``rte_pmd_mlx5_disable_steering``
> 
> +* **ip_frag: changed handling of malformed fragments.**
> +
> +  - Duplicate fragments are tolerated instead of failing reassembly.
> +  - Overlapping fragments are rejected on arrival rather than during reassembly.
> +  - Oversized fragments (reassembled length over 65535) are rejected.
> +  - For IPv6, fragments with per-fragment extension headers are rejected.
> +
> +  Overlap and oversize are now detected on arrival, which adds a scan of the
> +  already received fragments per fragment and may affect throughput.
> +
> 
>  ABI Changes
>  -----------
> --

Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>

> 2.53.0


^ permalink raw reply

* [PATCH v3 8/8] doc: add release note about ip_frag changes
From: Stephen Hemminger @ 2026-07-01 16:20 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260701162127.207318-1-stephen@networkplumber.org>

These changes maybe surprising and need some documentation.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 doc/guides/rel_notes/release_26_07.rst | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst
index 4ca0a9ac77..4918cb59ed 100644
--- a/doc/guides/rel_notes/release_26_07.rst
+++ b/doc/guides/rel_notes/release_26_07.rst
@@ -241,6 +241,16 @@ API Changes
   - ``rte_pmd_mlx5_enable_steering``
   - ``rte_pmd_mlx5_disable_steering``
 
+* **ip_frag: changed handling of malformed fragments.**
+
+  - Duplicate fragments are tolerated instead of failing reassembly.
+  - Overlapping fragments are rejected on arrival rather than during reassembly.
+  - Oversized fragments (reassembled length over 65535) are rejected.
+  - For IPv6, fragments with per-fragment extension headers are rejected.
+
+  Overlap and oversize are now detected on arrival, which adds a scan of the
+  already received fragments per fragment and may affect throughput.
+
 
 ABI Changes
 -----------
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 7/8] ip_frag: remove use of rte_memcpy
From: Stephen Hemminger @ 2026-07-01 16:20 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260701162127.207318-1-stephen@networkplumber.org>

Replace rte_memcpy with assignment or plain memcpy to
get more static checking.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/ip_frag/rte_ipv6_fragmentation.c | 3 +--
 lib/ip_frag/rte_ipv6_reassembly.c    | 5 ++---
 2 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/lib/ip_frag/rte_ipv6_fragmentation.c b/lib/ip_frag/rte_ipv6_fragmentation.c
index c81f2402e3..86dd2e65ce 100644
--- a/lib/ip_frag/rte_ipv6_fragmentation.c
+++ b/lib/ip_frag/rte_ipv6_fragmentation.c
@@ -6,7 +6,6 @@
 #include <errno.h>
 
 #include <eal_export.h>
-#include <rte_memcpy.h>
 
 #include "ip_frag_common.h"
 
@@ -24,7 +23,7 @@ __fill_ipv6hdr_frag(struct rte_ipv6_hdr *dst,
 {
 	struct rte_ipv6_fragment_ext *fh;
 
-	rte_memcpy(dst, src, sizeof(*dst));
+	*dst = *src;
 	dst->payload_len = rte_cpu_to_be_16(len);
 	dst->proto = IPPROTO_FRAGMENT;
 
diff --git a/lib/ip_frag/rte_ipv6_reassembly.c b/lib/ip_frag/rte_ipv6_reassembly.c
index 9aa2f2d08b..cae4266f97 100644
--- a/lib/ip_frag/rte_ipv6_reassembly.c
+++ b/lib/ip_frag/rte_ipv6_reassembly.c
@@ -5,7 +5,6 @@
 #include <stddef.h>
 
 #include <eal_export.h>
-#include <rte_memcpy.h>
 
 #include "ip_frag_common.h"
 
@@ -145,8 +144,8 @@ rte_ipv6_frag_reassemble_packet(struct rte_ip_frag_tbl *tbl,
 	int32_t ip_len;
 	int32_t trim;
 
-	rte_memcpy(&key.src_dst[0], &ip_hdr->src_addr, 16);
-	rte_memcpy(&key.src_dst[2], &ip_hdr->dst_addr, 16);
+	memcpy(&key.src_dst[0], &ip_hdr->src_addr, 16);
+	memcpy(&key.src_dst[2], &ip_hdr->dst_addr, 16);
 
 	key.id = frag_hdr->id;
 	key.key_len = IPV6_KEYLEN;
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 6/8] app/test: add test for IP reassembly
From: Stephen Hemminger @ 2026-07-01 16:20 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260701162127.207318-1-stephen@networkplumber.org>

There was no functional test for IPv4/IPv6 reassembly,
only a performance test. Add a new test  modeled on the Linux kernel
selftest tools/testing/selftests/net/ip_defrag.c. This is new
code so no license conflict.

Test covers:
size and fragment-size sweep across in-order, reverse,
odd/even and block delivery orders with byte-exact payload validation;
minimum 8-byte fragments; the fragment-count limit;
timeout of incomplete datagrams;
and the duplicate, overlap, extension-header and
oversized-fragment cases fixed earlier in this series.

The reassembled packet is checked with rte_mbuf_check() to catch
malformed segment chains in addition to wrong content.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test/meson.build       |   1 +
 app/test/test_reassembly.c | 675 +++++++++++++++++++++++++++++++++++++
 2 files changed, 676 insertions(+)
 create mode 100644 app/test/test_reassembly.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 61024125a7..b8c2208d0b 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -160,6 +160,7 @@ source_file_deps = {
     'test_rawdev.c': ['rawdev', 'bus_vdev', 'raw_skeleton'],
     'test_rcu_qsbr.c': ['rcu', 'hash'],
     'test_rcu_qsbr_perf.c': ['rcu', 'hash'],
+    'test_reassembly.c': ['net', 'ip_frag'],
     'test_reassembly_perf.c': ['net', 'ip_frag'],
     'test_reciprocal_division.c': [],
     'test_reciprocal_division_perf.c': [],
diff --git a/app/test/test_reassembly.c b/app/test/test_reassembly.c
new file mode 100644
index 0000000000..480de8d1dd
--- /dev/null
+++ b/app/test/test_reassembly.c
@@ -0,0 +1,675 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026
+ *
+ * Functional unit tests for the IP reassembly path of librte_ip_frag.
+ *
+ * Coverage mirrors the Linux selftest tools/testing/selftests/net/ip_defrag.c
+ * adapted to the library API and to DPDK-specific constraints:
+ *
+ *   - size / fragment-size sweep, bounded by RTE_LIBRTE_IP_FRAG_MAX_FRAG
+ *   - in-order, reverse, odd-then-even, and block-reordered delivery
+ *   - byte-exact validation of the reassembled payload (not just length)
+ *   - minimum (8-byte) fragments
+ *   - fragment-count boundary: exactly MAX reassembles, MAX + 1 fails
+ *   - incomplete datagram reaped on timeout
+ *   - zero-length fragment rejected
+ *   - duplicate fragment tolerated in a reordered set
+ *   - overlapping fragments (leading/trailing/contained) discarded
+ *   - IPv6 fragment with extension headers in the unfragmentable part dropped
+ *   - fragment whose end exceeds the maximum datagram size dropped
+ *
+ * The last four groups depend on the corresponding reassembly fixes
+ * (duplicate tolerance, overlap discard, extension-header drop, oversize
+ * drop); they pass once those are applied and fail on unpatched code. The
+ * remaining cases pass regardless.
+ *
+ * Fragments use l2_len == 0; the library reads the L3 header at offset 0.
+ */
+
+#include "test.h"
+
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_cycles.h>
+#include <rte_ip.h>
+#include <rte_ip_frag.h>
+#include <rte_log.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+
+#define NB_MBUF		1024
+#define MBUF_CACHE	0		/* exact accounting for leak checks */
+#define MBUF_DATA	2048
+#define V4_L3_LEN	((uint16_t)sizeof(struct rte_ipv4_hdr))
+#define V6_L3_LEN	((uint16_t)(sizeof(struct rte_ipv6_hdr) + \
+				    RTE_IPV6_FRAG_HDR_SIZE))
+#define TEST_ID		0x4242
+
+#ifndef RTE_LIBRTE_IP_FRAG_MAX_FRAG
+#define RTE_LIBRTE_IP_FRAG_MAX_FRAG 8
+#endif
+#define MAX_FRAG	RTE_LIBRTE_IP_FRAG_MAX_FRAG
+
+#define MAX_PAYLOAD	(MAX_FRAG * 256)	/* keeps a fragment in one mbuf */
+
+enum family { V4, V6 };
+enum order  { IN_ORDER, REVERSE, ODD_EVEN, BLOCK };
+
+struct frag_desc {
+	uint16_t ofs;	/* byte offset into the payload */
+	uint16_t plen;	/* payload bytes after L3 */
+	uint8_t  mf;
+};
+
+static struct rte_mempool *pkt_pool;
+
+/* position-dependent payload pattern, non-periodic at 256 so a misordered
+ * reassembly is detected even when lengths line up.
+ */
+static inline uint8_t
+pat(uint32_t k)
+{
+	return (uint8_t)(k * 31u + 7u);
+}
+
+/* ------------------------------- harness -------------------------------- */
+
+static int
+testsuite_setup(void)
+{
+	/* the table create/destroy per case is chatty at INFO level */
+	rte_log_set_level_pattern("lib.ip_frag", RTE_LOG_NOTICE);
+
+	pkt_pool = rte_pktmbuf_pool_create("REASM_POOL", NB_MBUF, MBUF_CACHE,
+					   0, MBUF_DATA, SOCKET_ID_ANY);
+	return pkt_pool == NULL ? TEST_FAILED : TEST_SUCCESS;
+}
+
+static void
+testsuite_teardown(void)
+{
+	rte_mempool_free(pkt_pool);
+	pkt_pool = NULL;
+}
+
+/* Every case must start and end with a full pool, so a leak in one case is
+ * pinpointed here rather than silently masking the next one.
+ */
+static int
+ut_setup(void)
+{
+	if (rte_mempool_avail_count(pkt_pool) != NB_MBUF) {
+		printf("pool not full at case start: %u/%u\n",
+		       rte_mempool_avail_count(pkt_pool), NB_MBUF);
+		return TEST_FAILED;
+	}
+	return TEST_SUCCESS;
+}
+
+static struct rte_ip_frag_tbl *
+tbl_new(uint64_t max_cycles)
+{
+	return rte_ip_frag_table_create(16, MAX_FRAG, 16, max_cycles,
+					rte_socket_id());
+}
+
+/* Build one fragment with a position-dependent payload. */
+static struct rte_mbuf *
+build_frag(enum family fam, uint16_t ofs, uint16_t plen, uint8_t mf)
+{
+	struct rte_mbuf *m = rte_pktmbuf_alloc(pkt_pool);
+	uint16_t l3 = (fam == V4) ? V4_L3_LEN : V6_L3_LEN;
+	char *p;
+	uint16_t i;
+
+	if (m == NULL)
+		return NULL;
+	m->data_off = 0;
+
+	if (fam == V4) {
+		struct rte_ipv4_hdr *ip = rte_pktmbuf_mtod(m,
+						struct rte_ipv4_hdr *);
+		uint16_t fo = ofs / RTE_IPV4_HDR_OFFSET_UNITS;
+
+		memset(ip, 0, V4_L3_LEN);
+		if (mf)
+			fo |= RTE_IPV4_HDR_MF_FLAG;
+		ip->version_ihl = 0x45;
+		ip->total_length = rte_cpu_to_be_16(V4_L3_LEN + plen);
+		ip->packet_id = rte_cpu_to_be_16(TEST_ID);
+		ip->fragment_offset = rte_cpu_to_be_16(fo);
+		ip->time_to_live = 64;
+		ip->next_proto_id = IPPROTO_UDP;
+		ip->src_addr = rte_cpu_to_be_32(0x0a000001);
+		ip->dst_addr = rte_cpu_to_be_32(0x0a000002);
+	} else {
+		struct rte_ipv6_hdr *ip = rte_pktmbuf_mtod(m,
+						struct rte_ipv6_hdr *);
+		struct rte_ipv6_fragment_ext *fh =
+			rte_pktmbuf_mtod_offset(m,
+				struct rte_ipv6_fragment_ext *,
+				sizeof(struct rte_ipv6_hdr));
+
+		memset(ip, 0, V6_L3_LEN);
+		ip->vtc_flow = rte_cpu_to_be_32(6u << 28);
+		ip->payload_len = rte_cpu_to_be_16(RTE_IPV6_FRAG_HDR_SIZE + plen);
+		ip->proto = IPPROTO_FRAGMENT;
+		ip->hop_limits = 64;
+		ip->src_addr.a[15] = 1;
+		ip->dst_addr.a[15] = 2;
+		fh->next_header = IPPROTO_UDP;
+		fh->reserved = 0;
+		fh->frag_data = rte_cpu_to_be_16(
+				RTE_IPV6_SET_FRAG_DATA(ofs, mf ? 1 : 0));
+		fh->id = rte_cpu_to_be_32(TEST_ID);
+	}
+
+	p = rte_pktmbuf_mtod_offset(m, char *, l3);
+	for (i = 0; i < plen; i++)
+		p[i] = (char)pat(ofs + i);
+
+	m->data_len = m->pkt_len = l3 + plen;
+	m->l2_len = 0;
+	m->l3_len = l3;
+	return m;
+}
+
+static struct rte_mbuf *
+feed(enum family fam, struct rte_ip_frag_tbl *tbl,
+	struct rte_ip_frag_death_row *dr, const struct frag_desc *d, uint64_t tms)
+{
+	struct rte_mbuf *m = build_frag(fam, d->ofs, d->plen, d->mf);
+
+	if (m == NULL)
+		return NULL;
+	if (fam == V4) {
+		struct rte_ipv4_hdr *ip = rte_pktmbuf_mtod(m, struct rte_ipv4_hdr *);
+		return rte_ipv4_frag_reassemble_packet(tbl, dr, m, tms, ip);
+	} else {
+		struct rte_ipv6_hdr *ip = rte_pktmbuf_mtod(m, struct rte_ipv6_hdr *);
+		struct rte_ipv6_fragment_ext *fh =
+			rte_pktmbuf_mtod_offset(m, struct rte_ipv6_fragment_ext *,
+						sizeof(struct rte_ipv6_hdr));
+		return rte_ipv6_frag_reassemble_packet(tbl, dr, m, tms, ip, fh);
+	}
+}
+
+/* Split a datagram of total_plen into fragments of frag_size (multiple of 8).
+ * Returns the fragment count, or -1 if it would exceed MAX_FRAG.
+ */
+static int
+make_datagram(uint16_t total_plen, uint16_t frag_size, struct frag_desc *out)
+{
+	int n = 0;
+	uint16_t ofs = 0;
+
+	while (ofs < total_plen) {
+		uint16_t rem = total_plen - ofs;
+		uint16_t len = rem <= frag_size ? rem : frag_size;
+
+		if (n >= MAX_FRAG)
+			return -1;
+		out[n].ofs = ofs;
+		out[n].plen = len;
+		out[n].mf = (ofs + len < total_plen);
+		ofs += len;
+		n++;
+	}
+	return n;
+}
+
+/* Produce a delivery order (array of indices into descs). */
+static void
+make_order(enum order ord, int n, int *idx)
+{
+	int i, k = 0;
+
+	switch (ord) {
+	case IN_ORDER:
+		for (i = 0; i < n; i++)
+			idx[i] = i;
+		break;
+	case REVERSE:
+		for (i = 0; i < n; i++)
+			idx[i] = n - 1 - i;
+		break;
+	case ODD_EVEN:
+		for (i = 1; i < n; i += 2)
+			idx[k++] = i;
+		for (i = 0; i < n; i += 2)
+			idx[k++] = i;
+		break;
+	case BLOCK: {
+		int t = n / 3 ? n / 3 : 1;
+
+		for (i = 2 * t; i < n; i++)
+			idx[k++] = i;
+		for (i = t; i < 2 * t && i < n; i++)
+			idx[k++] = i;
+		for (i = 0; i < t && i < n; i++)
+			idx[k++] = i;
+		break;
+	}
+	}
+}
+
+/* Feed descs in the given order; return reassembled mbuf or NULL. */
+static struct rte_mbuf *
+run_ordered(enum family fam, const struct frag_desc *descs, int n,
+	    const int *idx)
+{
+	struct rte_ip_frag_death_row dr;
+	struct rte_ip_frag_tbl *tbl;
+	struct rte_mbuf *out = NULL;
+	uint64_t tms = rte_rdtsc();
+	int i;
+
+	memset(&dr, 0, sizeof(dr));
+	tbl = tbl_new(rte_get_tsc_hz());
+	if (tbl == NULL)
+		return NULL;
+	for (i = 0; i < n; i++) {
+		struct rte_mbuf *r = feed(fam, tbl, &dr, &descs[idx[i]], tms);
+
+		if (r != NULL)
+			out = r;
+	}
+	rte_ip_frag_free_death_row(&dr, 0);
+	rte_ip_frag_table_destroy(tbl);
+	return out;
+}
+
+/* Validate length and byte-exact payload, then free. Returns 0 on success.
+ * Note: reassembly strips the IPv6 fragment header, so the reassembled v6
+ * header is sizeof(rte_ipv6_hdr), not the V6_L3_LEN the fragments were built
+ * with. v4 has no fragment header to remove.
+ */
+static int
+validate(struct rte_mbuf *m, enum family fam, uint16_t total_plen)
+{
+	uint16_t l3 = (fam == V4) ? V4_L3_LEN :
+				    (uint16_t)sizeof(struct rte_ipv6_hdr);
+	uint8_t buf[MAX_PAYLOAD];
+	const uint8_t *p;
+	const char *reason;
+	uint16_t k;
+	int rc = 0;
+
+	if (m == NULL)
+		return -1;
+	if (rte_mbuf_check(m, 1, &reason) != 0) {
+		printf("  bad mbuf fam=%d total=%u: %s\n", fam, total_plen,
+		       reason);
+		rte_pktmbuf_free(m);
+		return -1;
+	}
+	if (m->pkt_len != (uint32_t)(l3 + total_plen)) {
+		rte_pktmbuf_free(m);
+		return -1;
+	}
+	p = rte_pktmbuf_read(m, l3, total_plen, buf);
+	if (p == NULL) {
+		rte_pktmbuf_free(m);
+		return -1;
+	}
+	for (k = 0; k < total_plen; k++) {
+		if (p[k] != pat(k)) {
+			rc = -1;
+			break;
+		}
+	}
+	rte_pktmbuf_free(m);
+	return rc;
+}
+
+/* --------------------------- baseline / sweep --------------------------- */
+
+static int
+sweep_one(enum family fam, uint16_t total_plen, uint16_t frag_size)
+{
+	struct frag_desc descs[MAX_FRAG];
+	int idx[MAX_FRAG];
+	const enum order orders[] = { IN_ORDER, REVERSE, ODD_EVEN, BLOCK };
+	int n = make_datagram(total_plen, frag_size, descs);
+	unsigned int o;
+
+	if (n < 2)		/* skip single-fragment / oversized for sweep */
+		return 0;
+
+	for (o = 0; o < RTE_DIM(orders); o++) {
+		make_order(orders[o], n, idx);
+		if (validate(run_ordered(fam, descs, n, idx), fam,
+			     total_plen) != 0) {
+			printf("  sweep fail: fam=%d total=%u fs=%u order=%u n=%d\n",
+			       fam, total_plen, frag_size, orders[o], n);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int
+sweep(enum family fam)
+{
+	const uint16_t fsizes[] = { 8, 16, 64, 256 };
+	unsigned int f;
+
+	for (f = 0; f < RTE_DIM(fsizes); f++) {
+		uint16_t fs = fsizes[f];
+		uint16_t total;
+
+		/* cover 2..MAX_FRAG fragments, last fragment partial */
+		for (total = fs + 8; total <= fs * MAX_FRAG; total += fs) {
+			if (sweep_one(fam, total, fs) != 0)
+				return TEST_FAILED;
+			if (total > fs + 4 &&
+			    sweep_one(fam, total - 4, fs) != 0)
+				return TEST_FAILED;
+		}
+	}
+	return TEST_SUCCESS;
+}
+
+static int test_sweep_v4(void) { return sweep(V4); }
+static int test_sweep_v6(void) { return sweep(V6); }
+
+/* Minimum 8-byte fragments. */
+static int
+test_min_fragment(void)
+{
+	struct frag_desc d[3] = {
+		{ 0, 8, 1 }, { 8, 8, 1 }, { 16, 8, 0 },
+	};
+	int idx[3];
+
+	make_order(REVERSE, 3, idx);
+	TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, 3, idx), V4, 24),
+				"min 8-byte fragments not reassembled");
+	make_order(ODD_EVEN, 3, idx);
+	TEST_ASSERT_SUCCESS(validate(run_ordered(V6, d, 3, idx), V6, 24),
+				"min 8-byte fragments not reassembled (v6)");
+	return TEST_SUCCESS;
+}
+
+/* Exactly MAX_FRAG fragments reassembles; MAX_FRAG + 1 fails. */
+static int
+test_cap_boundary(void)
+{
+	struct frag_desc d[MAX_FRAG + 1];
+	int idx[MAX_FRAG + 1];
+	uint16_t fs = 8, total = fs * MAX_FRAG;
+	int n, i;
+
+	n = make_datagram(total, fs, d);
+	TEST_ASSERT_EQUAL(n, MAX_FRAG, "expected MAX_FRAG fragments");
+	make_order(IN_ORDER, n, idx);
+	TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, n, idx), V4, total),
+				"MAX_FRAG fragments should reassemble");
+
+	/* one more fragment than the table can hold */
+	for (i = 0; i <= MAX_FRAG; i++) {
+		d[i].ofs = i * fs;
+		d[i].plen = fs;
+		d[i].mf = (i < MAX_FRAG);
+		idx[i] = i;
+	}
+	TEST_ASSERT_NULL(run_ordered(V4, d, MAX_FRAG + 1, idx),
+			     "MAX_FRAG + 1 fragments should not reassemble");
+	TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
+			      "overflowing set leaked mbufs");
+	return TEST_SUCCESS;
+}
+
+/* Incomplete datagram: no output, reaped on timeout. */
+static int
+test_incomplete_timeout(void)
+{
+	struct rte_ip_frag_death_row dr;
+	struct rte_ip_frag_tbl *tbl;
+	uint64_t mc = rte_get_tsc_hz(), tms = rte_rdtsc();
+	struct frag_desc d[2] = { { 0, 64, 1 }, { 128, 64, 0 } }; /* gap */
+	struct rte_mbuf *out = NULL;
+	int i;
+
+	memset(&dr, 0, sizeof(dr));
+	tbl = tbl_new(mc);
+	TEST_ASSERT_NOT_NULL(tbl, "table create failed");
+	for (i = 0; i < 2; i++) {
+		struct rte_mbuf *r = feed(V4, tbl, &dr, &d[i], tms);
+
+		if (r != NULL)
+			out = r;
+	}
+	TEST_ASSERT_NULL(out, "incomplete datagram reassembled");
+	rte_ip_frag_table_del_expired_entries(tbl, &dr, tms + mc + 1);
+	rte_ip_frag_free_death_row(&dr, 0);
+	rte_ip_frag_table_destroy(tbl);
+	TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
+			      "expired fragments not freed");
+	return TEST_SUCCESS;
+}
+
+static int
+test_zero_len(void)
+{
+	struct frag_desc d = { 0, 0, 1 };
+	int idx = 0;
+
+	TEST_ASSERT_NULL(run_ordered(V4, &d, 1, &idx),
+			     "zero-length fragment accepted");
+	TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
+			      "zero-length fragment leaked");
+	return TEST_SUCCESS;
+}
+
+/* --------------------- duplicate / overlap / reject --------------------- */
+
+/* A duplicate anywhere in a reordered set must not break reassembly. */
+static int
+test_dup_tolerated(void)
+{
+	/* offsets 0,64,128,192 with 64B frags; inject a dup of frag 1 */
+	struct frag_desc d[5] = {
+		{   0, 64, 1 }, {  64, 64, 1 }, {  64, 64, 1 }, /* dup */
+		{ 128, 64, 1 }, { 192, 64, 0 },
+	};
+	int idx[5] = { 1, 4, 2, 0, 3 };	/* reordered, dup interleaved */
+
+	TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, 5, idx), V4, 256),
+				"duplicate fragment broke reassembly");
+	return TEST_SUCCESS;
+}
+
+/* Overlap geometries; the datagram must be discarded and every collected
+ * fragment freed. The last fragment is withheld so that on unfixed code the
+ * entry is *retained* (total_size stays UINT32_MAX) rather than torn down by
+ * the frag_size > total_size path: that retention is what we detect. We
+ * capture the mbufs still held in the table after draining the death row,
+ * before destroying the table (destroy frees held mbufs, hiding the leak).
+ */
+static int
+overlap_case(enum family fam, const struct frag_desc *d, int n, const char *what)
+{
+	struct rte_ip_frag_death_row dr;
+	struct rte_ip_frag_tbl *tbl;
+	struct rte_mbuf *out = NULL;
+	uint64_t tms = rte_rdtsc();
+	unsigned int held;
+	int i;
+
+	memset(&dr, 0, sizeof(dr));
+	tbl = tbl_new(rte_get_tsc_hz());
+	if (tbl == NULL)
+		return -1;
+	for (i = 0; i < n; i++) {
+		struct rte_mbuf *r = feed(fam, tbl, &dr, &d[i], tms);
+
+		if (r != NULL)
+			out = r;
+	}
+	rte_ip_frag_free_death_row(&dr, 0);
+	held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
+	rte_ip_frag_table_destroy(tbl);
+
+	if (out != NULL) {
+		rte_pktmbuf_free(out);
+		printf("  overlap reassembled instead of discarded: %s\n", what);
+		return -1;
+	}
+	if (held != 0) {
+		printf("  overlap kept %u fragment(s) instead of discarding: %s\n",
+		       held, what);
+		return -1;
+	}
+	return 0;
+}
+
+static int
+test_overlap(void)
+{
+	/* last fragment withheld in every case (all MF=1) */
+
+	/* overlapping fragment arrives second */
+	const struct frag_desc tail[2] = { { 0, 600, 1 }, { 300, 600, 1 } };
+	/* overlapping fragment arrives first */
+	const struct frag_desc head[2] = { { 300, 600, 1 }, { 0, 600, 1 } };
+	/* a fragment fully contained in an existing one */
+	const struct frag_desc cont[2] = { { 0, 600, 1 }, { 200, 200, 1 } };
+
+	TEST_ASSERT_SUCCESS(overlap_case(V6, tail, 2, "v6 overlap second"), "");
+	TEST_ASSERT_SUCCESS(overlap_case(V6, head, 2, "v6 overlap first"), "");
+	TEST_ASSERT_SUCCESS(overlap_case(V6, cont, 2, "v6 contained"), "");
+	TEST_ASSERT_SUCCESS(overlap_case(V4, tail, 2, "v4 overlap second"), "");
+	return TEST_SUCCESS;
+}
+
+/*
+ * An IPv6 fragment whose fragment header does not directly follow the base
+ * header (a per-fragment extension header precedes it) is dropped, not stored.
+ * Build base hdr + an 8-byte routing header + fragment header, and pass the
+ * fragment header at its real offset (48), so the library sees frag_hdr !=
+ * ip_hdr + 1. Captures whether the fragment is still held in the table after
+ * the death row is drained but before the table is destroyed.
+ */
+static int
+test_v6_ext_header_drop(void)
+{
+	struct rte_ip_frag_death_row dr;
+	struct rte_ip_frag_tbl *tbl;
+	struct rte_mbuf *m, *r;
+	struct rte_ipv6_hdr *ip;
+	struct rte_ipv6_fragment_ext *fh;
+	uint8_t *rthdr;
+	unsigned int held;
+	const uint16_t plen = 64;
+	const uint16_t ext = 8;	/* one 8-byte routing header */
+
+	memset(&dr, 0, sizeof(dr));
+	tbl = tbl_new(rte_get_tsc_hz());
+	TEST_ASSERT_NOT_NULL(tbl, "table create failed");
+
+	m = rte_pktmbuf_alloc(pkt_pool);
+	TEST_ASSERT_NOT_NULL(m, "alloc failed");
+	m->data_off = 0;
+	ip = rte_pktmbuf_mtod(m, struct rte_ipv6_hdr *);
+	memset(ip, 0, sizeof(*ip));
+	ip->vtc_flow = rte_cpu_to_be_32(6u << 28);
+	ip->payload_len = rte_cpu_to_be_16(ext + RTE_IPV6_FRAG_HDR_SIZE + plen);
+	ip->proto = IPPROTO_ROUTING;	/* per-fragment header before frag hdr */
+	ip->hop_limits = 64;
+	ip->src_addr.a[15] = 1;
+	ip->dst_addr.a[15] = 2;
+
+	/* 8-byte routing header, next = fragment */
+	rthdr = rte_pktmbuf_mtod_offset(m, uint8_t *, sizeof(*ip));
+	memset(rthdr, 0, ext);
+	rthdr[0] = IPPROTO_FRAGMENT;	/* next header */
+	rthdr[1] = 0;			/* hdr ext len: (0 + 1) * 8 = 8 bytes */
+
+	/* fragment header at offset 48, not 40 */
+	fh = rte_pktmbuf_mtod_offset(m, struct rte_ipv6_fragment_ext *,
+				     sizeof(*ip) + ext);
+	fh->next_header = IPPROTO_UDP;
+	fh->reserved = 0;
+	fh->frag_data = rte_cpu_to_be_16(RTE_IPV6_SET_FRAG_DATA(0, 1));
+	fh->id = rte_cpu_to_be_32(TEST_ID);
+
+	m->data_len = m->pkt_len = sizeof(*ip) + ext + RTE_IPV6_FRAG_HDR_SIZE +
+				   plen;
+	m->l2_len = 0;
+	m->l3_len = sizeof(*ip) + ext + RTE_IPV6_FRAG_HDR_SIZE;
+
+	r = rte_ipv6_frag_reassemble_packet(tbl, &dr, m, rte_rdtsc(), ip, fh);
+	rte_ip_frag_free_death_row(&dr, 0);
+	held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
+	rte_ip_frag_table_destroy(tbl);
+
+	TEST_ASSERT_NULL(r, "fragment with per-fragment header accepted");
+	TEST_ASSERT_EQUAL(held, 0,
+			  "per-fragment-header fragment stored instead of dropped");
+	return TEST_SUCCESS;
+}
+
+/* A fragment whose end exceeds the max datagram size is dropped, not stored. */
+static int
+oversize_drop_one(enum family fam)
+{
+	struct rte_ip_frag_death_row dr;
+	struct rte_ip_frag_tbl *tbl;
+	struct frag_desc d = { 0xFFF8, 64, 0 };	/* offset 65528 + 64 > 65535 */
+	struct rte_mbuf *r;
+	unsigned int held;
+
+	memset(&dr, 0, sizeof(dr));
+	tbl = tbl_new(rte_get_tsc_hz());
+	if (tbl == NULL)
+		return -1;
+	r = feed(fam, tbl, &dr, &d, rte_rdtsc());
+	rte_ip_frag_free_death_row(&dr, 0);
+	held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
+	rte_ip_frag_table_destroy(tbl);
+
+	if (r != NULL) {
+		rte_pktmbuf_free(r);
+		return -1;
+	}
+	return held == 0 ? 0 : -1;
+}
+
+static int
+test_oversize_drop(void)
+{
+	TEST_ASSERT_SUCCESS(oversize_drop_one(V4),
+			    "oversized v4 fragment stored instead of dropped");
+	TEST_ASSERT_SUCCESS(oversize_drop_one(V6),
+			    "oversized v6 fragment stored instead of dropped");
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite reassembly_testsuite = {
+	.suite_name = "IP Reassembly Unit Test Suite",
+	.setup = testsuite_setup,
+	.teardown = testsuite_teardown,
+	.unit_test_cases = {
+		TEST_CASE_ST(ut_setup, NULL, test_sweep_v4),
+		TEST_CASE_ST(ut_setup, NULL, test_sweep_v6),
+		TEST_CASE_ST(ut_setup, NULL, test_min_fragment),
+		TEST_CASE_ST(ut_setup, NULL, test_cap_boundary),
+		TEST_CASE_ST(ut_setup, NULL, test_incomplete_timeout),
+		TEST_CASE_ST(ut_setup, NULL, test_zero_len),
+		TEST_CASE_ST(ut_setup, NULL, test_dup_tolerated),
+		TEST_CASE_ST(ut_setup, NULL, test_overlap),
+		TEST_CASE_ST(ut_setup, NULL, test_v6_ext_header_drop),
+		TEST_CASE_ST(ut_setup, NULL, test_oversize_drop),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_reassembly(void)
+{
+	return unit_test_suite_runner(&reassembly_testsuite);
+}
+
+REGISTER_FAST_TEST(reassembly_autotest, NOHUGE_OK, ASAN_OK, test_reassembly);
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 5/8] ip_frag: reject oversized reassembled datagrams
From: Stephen Hemminger @ 2026-07-01 16:20 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Konstantin Ananyev
In-Reply-To: <20260701162127.207318-1-stephen@networkplumber.org>

The reassembled total length of a packet must not exceed 65535.
A fragment with a high offset could drive the sum past that,
causing silent truncation since IP payload_len/total_length is 16 bits.

When reassembling a packet the total length should not be allowed
to exceed 65535. A fragment with high offset could drive the sum
past that, causing silent truncation.

A valid datagram never exceeds 65535 bytes, so reject any fragment
whose resulting length would exceed that.
Fold the test into the existing zero-length check.

Fixes: cc8f4d020c0b ("examples/ip_reassembly: initial import")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
 lib/ip_frag/rte_ipv4_reassembly.c | 9 +++++++--
 lib/ip_frag/rte_ipv6_reassembly.c | 9 +++++++--
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/lib/ip_frag/rte_ipv4_reassembly.c b/lib/ip_frag/rte_ipv4_reassembly.c
index 980f7a3b77..727fc58243 100644
--- a/lib/ip_frag/rte_ipv4_reassembly.c
+++ b/lib/ip_frag/rte_ipv4_reassembly.c
@@ -136,8 +136,13 @@ rte_ipv4_frag_reassemble_packet(struct rte_ip_frag_tbl *tbl,
 		tbl, tbl->max_cycles, tbl->entry_mask, tbl->max_entries,
 		tbl->use_entries);
 
-	/* check that fragment length is greater then zero. */
-	if (ip_len <= 0) {
+	/*
+	 * Drop fragments with no payload, and any fragment whose end would
+	 * make the reassembled datagram exceed the maximum IPv4 size. The
+	 * total_length field is 16 bits, so otherwise it is silently
+	 * truncated while the mbuf still holds the full length.
+	 */
+	if (ip_len <= 0 || ip_ofs + ip_len + mb->l3_len > UINT16_MAX) {
 		IP_FRAG_MBUF2DR(dr, mb);
 		return NULL;
 	}
diff --git a/lib/ip_frag/rte_ipv6_reassembly.c b/lib/ip_frag/rte_ipv6_reassembly.c
index b6f623d53b..9aa2f2d08b 100644
--- a/lib/ip_frag/rte_ipv6_reassembly.c
+++ b/lib/ip_frag/rte_ipv6_reassembly.c
@@ -174,8 +174,13 @@ rte_ipv6_frag_reassemble_packet(struct rte_ip_frag_tbl *tbl,
 		tbl, tbl->max_cycles, tbl->entry_mask, tbl->max_entries,
 		tbl->use_entries);
 
-	/* check that fragment length is greater then zero. */
-	if (ip_len <= 0) {
+	/*
+	 * Drop fragments with no payload, and any fragment whose end would
+	 * make the reassembled payload exceed 65535 bytes. The payload_len
+	 * field is 16 bits, so otherwise it is silently truncated while the
+	 * mbuf still holds the full length.
+	 */
+	if (ip_len <= 0 || ip_ofs + ip_len > UINT16_MAX) {
 		IP_FRAG_MBUF2DR(dr, mb);
 		return NULL;
 	}
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 4/8] ip_frag: drop IPv6 fragments with per-fragment headers
From: Stephen Hemminger @ 2026-07-01 16:20 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable
In-Reply-To: <20260701162127.207318-1-stephen@networkplumber.org>

IPv6 reassembly assumes the fragment header directly follows the IPv6
header. ipv6_frag_reassemble() patches the next-header field and removes
the fragment header at a fixed offset, so a fragment with per-fragment
extension headers before the fragment header reassembles to a corrupt
datagram.

Drop the fragment when its fragment header does not directly follow the
IPv6 header. Headers after the fragment header are payload and are
unaffected.

RFC 8200 permits discarding packets with extension headers out of the
recommended order, and RFC 9099 recommends dropping non-conforming
fragmented packets.

Fixes: 4f1a8f633862 ("ip_frag: add IPv6 reassembly")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/ip_frag/rte_ipv6_reassembly.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/lib/ip_frag/rte_ipv6_reassembly.c b/lib/ip_frag/rte_ipv6_reassembly.c
index 0e809a01e5..b6f623d53b 100644
--- a/lib/ip_frag/rte_ipv6_reassembly.c
+++ b/lib/ip_frag/rte_ipv6_reassembly.c
@@ -180,6 +180,28 @@ rte_ipv6_frag_reassemble_packet(struct rte_ip_frag_tbl *tbl,
 		return NULL;
 	}
 
+	/*
+	 * Only a fragment header directly following the IPv6 header is supported.
+	 * Per-fragment (unfragmentable) extension headers placed
+	 * before the fragment header are not handled: ipv6_frag_reassemble()
+	 * patches the IPv6 header's next-header field and removes the fragment
+	 * header assuming it sits immediately after the IPv6 header, so such a
+	 * fragment would be reassembled into a corrupt datagram. Drop it.
+	 *
+	 * Extension headers after the fragment header (destination options,
+	 * AH, ESP, upper-layer) are part of the fragmentable payload and are
+	 * reassembled as opaque bytes, so they are not affected. The test uses
+	 * the fragment header's position rather than l3_len so that callers
+	 * which include later headers in l3_len are not rejected.
+	 */
+	if ((uintptr_t)frag_hdr != (uintptr_t)(ip_hdr + 1)) {
+		IP_FRAG_LOG(DEBUG,
+			    "%s:%d: drop fragment with header before frag header, offset %zu\n",
+			    __func__, __LINE__, (uintptr_t)frag_hdr - (uintptr_t)ip_hdr);
+		IP_FRAG_MBUF2DR(dr, mb);
+		return NULL;
+	}
+
 	if (unlikely(trim > 0))
 		rte_pktmbuf_trim(mb, trim);
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 3/8] ip_frag: include protocol in IPv4 reassembly key
From: Stephen Hemminger @ 2026-07-01 16:20 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Konstantin Ananyev
In-Reply-To: <20260701162127.207318-1-stephen@networkplumber.org>

DPDK IPv4 reassembly code was not following RFC 791 section 3.2
which says:
    The internet identification field (ID) is used together with the
    source and destination address, and the protocol fields, to identify
    datagram fragments for reassembly.

Omitting the protocol means two datagrams between the
same pair of hosts that share an IP id but carry different protocols
(for example UDP and ICMP) are merged into a single reassembly context,
producing a corrupted datagram.

Fold the protocol into the unused upper bits of the 32-bit id field
of the key. The IPv4 identification is 16 bits and occupies the low
half, so the protocol can be carried in the upper bits without changing
the key layout, the key comparison or the hash.

Fixes: cc8f4d020c0b ("examples/ip_reassembly: initial import")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
 lib/ip_frag/rte_ipv4_reassembly.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/lib/ip_frag/rte_ipv4_reassembly.c b/lib/ip_frag/rte_ipv4_reassembly.c
index 3c8ae113ba..980f7a3b77 100644
--- a/lib/ip_frag/rte_ipv4_reassembly.c
+++ b/lib/ip_frag/rte_ipv4_reassembly.c
@@ -111,9 +111,15 @@ rte_ipv4_frag_reassemble_packet(struct rte_ip_frag_tbl *tbl,
 	ip_ofs = (uint16_t)(flag_offset & RTE_IPV4_HDR_OFFSET_MASK);
 	ip_flag = (uint16_t)(flag_offset & RTE_IPV4_HDR_MF_FLAG);
 
+	/*
+	 * RFC 791 requires using: source, destination, identifier field and protocol
+	 */
+
 	/* use first 8 bytes only */
 	memcpy(&key.src_dst[0], &ip_hdr->src_addr, 8);
-	key.id = ip_hdr->packet_id;
+
+	/* packet_id is 16 bits and proto id is 8 bits */
+	key.id = ((uint32_t) ip_hdr->next_proto_id << 16) | ip_hdr->packet_id;
 	key.key_len = IPV4_KEYLEN;
 
 	ip_ofs *= RTE_IPV4_HDR_OFFSET_UNITS;
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 2/8] ip_frag: discard datagrams with overlapping fragments
From: Stephen Hemminger @ 2026-07-01 16:20 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev
In-Reply-To: <20260701162127.207318-1-stephen@networkplumber.org>

Existing code does not handle overlapping fragments.

RFC 8200 (IPv6) requires that on overlap all reassembly is abandoned
and all received fragments are dropped. RFC 791 (IPv4) originally called
for trimming and rewriting, but Linux discards for IPv4 as well, since
overlap has no legitimate use and is a known attack vector.

Depends on the duplicate-tolerance change so that an exact duplicate is
dropped on its own rather than discarding the whole datagram.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
 lib/ip_frag/ip_frag_internal.c | 38 +++++++++++++++++++++++++++-------
 1 file changed, 30 insertions(+), 8 deletions(-)

diff --git a/lib/ip_frag/ip_frag_internal.c b/lib/ip_frag/ip_frag_internal.c
index 9a03ef995a..55fc4e9343 100644
--- a/lib/ip_frag/ip_frag_internal.c
+++ b/lib/ip_frag/ip_frag_internal.c
@@ -92,16 +92,38 @@ ip_frag_process(struct ip_frag_pkt *fp, struct rte_ip_frag_death_row *dr,
 	uint32_t i, idx;
 
 	/*
-	 * Discard an exact duplicate fragment. If a previously stored fragment
-	 * already covers the same offset and length, this fragment carries no
-	 * new data. Reassembly is tolerant of duplicates (RFC 791), so drop
-	 * only this mbuf and keep the reassembly entry intact rather than
-	 * treating it as an error. Fragments overlapping an existing one with
-	 * different bounds are not handled here.
+	 * Scan the fragments already collected for this datagram before
+	 * storing the new one. The stored set is kept free of duplicates and
+	 * overlaps, so a single pass is sufficient.
 	 */
 	for (i = 0; i != fp->last_idx; i++) {
-		if (fp->frags[i].mb != NULL && fp->frags[i].ofs == ofs &&
-				fp->frags[i].len == len) {
+		if (fp->frags[i].mb == NULL)
+			continue;
+
+		/*
+		 * Exact duplicate: carries no new data. Reassembly tolerates
+		 * duplicates (RFC 791), so drop only this mbuf and keep the
+		 * entry.
+		 */
+		if (fp->frags[i].ofs == ofs && fp->frags[i].len == len) {
+			IP_FRAG_MBUF2DR(dr, mb);
+			return NULL;
+		}
+
+		/*
+		 * Overlap with an existing fragment. Per RFC 8200 section 4.5
+		 * (and RFC 5722) the datagram must be discarded; the same is
+		 * applied to IPv4. Free all collected fragments, drop this one,
+		 * and invalidate the entry.
+		 */
+		if (ofs < fp->frags[i].ofs + fp->frags[i].len && fp->frags[i].ofs < ofs + len) {
+			IP_FRAG_LOG(DEBUG,
+				    "%s:%d overlap ofs: %u len: %u\n"
+				    "fragment: %p ofs: %u len %u\n\n",
+				    __func__, __LINE__, ofs, len,
+				    fp, fp->frags[i].ofs, fp->frags[i].len);
+			ip_frag_free(fp, dr);
+			ip_frag_key_invalidate(&fp->key);
 			IP_FRAG_MBUF2DR(dr, mb);
 			return NULL;
 		}
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 1/8] ip_frag: tolerate duplicate fragments
From: Stephen Hemminger @ 2026-07-01 16:20 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Samyak Jain, Konstantin Ananyev
In-Reply-To: <20260701162127.207318-1-stephen@networkplumber.org>

The reassembly code tracked only a running byte total and reserved slots
for the first and last fragments, with no check for a fragment
duplicating data already received. A single duplicate could destroy a
recoverable datagram:
 - a duplicate first or last fragment collided with the reserved slot and
   sent the whole entry down the error path, freeing every collected
   fragment;
 - a duplicate intermediate fragment was appended to a new slot, inflating
   frag_size past total_size so reassembly never completed.

RFC 791 reassembly tolerates duplicates: a fragment covering bytes
already present carries no new information. Check for an exact duplicate
(stored fragment with the same offset and length) and drop only that
mbuf, before frag_size is updated, leaving the entry's accounting
unchanged.

Overlapping fragments with differing bounds are a separate issue
addressed in the next patch.

Reported-by: Samyak Jain <samyak.jain@amantyatech.com>
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
 lib/ip_frag/ip_frag_internal.c | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/lib/ip_frag/ip_frag_internal.c b/lib/ip_frag/ip_frag_internal.c
index 382f42d0e1..9a03ef995a 100644
--- a/lib/ip_frag/ip_frag_internal.c
+++ b/lib/ip_frag/ip_frag_internal.c
@@ -89,7 +89,23 @@ struct rte_mbuf *
 ip_frag_process(struct ip_frag_pkt *fp, struct rte_ip_frag_death_row *dr,
 	struct rte_mbuf *mb, uint16_t ofs, uint16_t len, uint16_t more_frags)
 {
-	uint32_t idx;
+	uint32_t i, idx;
+
+	/*
+	 * Discard an exact duplicate fragment. If a previously stored fragment
+	 * already covers the same offset and length, this fragment carries no
+	 * new data. Reassembly is tolerant of duplicates (RFC 791), so drop
+	 * only this mbuf and keep the reassembly entry intact rather than
+	 * treating it as an error. Fragments overlapping an existing one with
+	 * different bounds are not handled here.
+	 */
+	for (i = 0; i != fp->last_idx; i++) {
+		if (fp->frags[i].mb != NULL && fp->frags[i].ofs == ofs &&
+				fp->frags[i].len == len) {
+			IP_FRAG_MBUF2DR(dr, mb);
+			return NULL;
+		}
+	}
 
 	fp->frag_size += len;
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 0/8] ip_frag: fix reassembly defects and add test
From: Stephen Hemminger @ 2026-07-01 16:20 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260616210656.464062-1-stephen@networkplumber.org>

The IP reassembly library tracks only a running byte total and reserved
slots for the first and last fragments, with no coverage map. As a result
it mishandles duplicate, overlapping, oversized, and misheadered
fragments, and the IPv4 key is missing a field RFC 791 requires. There
was also no functional test to catch any of it.

These came out of reviewing a duplicate-fragment report on the list.

Patches 1 and 2 are interdependent: the overlap discard relies on the
duplicate handling so an exact duplicate is dropped on its own rather
than discarding the whole datagram. The rest are independent.

Patch 6 adds a functional test modeled on the Linux selftest ip_defrag.c.
It passes on this series; with any single fix reverted the matching case
fails.

v3 - drop stable from one patch and reword release note

Stephen Hemminger (8):
  ip_frag: tolerate duplicate fragments
  ip_frag: discard datagrams with overlapping fragments
  ip_frag: include protocol in IPv4 reassembly key
  ip_frag: drop IPv6 fragments with per-fragment headers
  ip_frag: reject oversized reassembled datagrams
  app/test: add test for IP reassembly
  ip_frag: remove use of rte_memcpy
  doc: add release note about ip_frag changes

 app/test/meson.build                   |   1 +
 app/test/test_reassembly.c             | 675 +++++++++++++++++++++++++
 doc/guides/rel_notes/release_26_07.rst |  10 +
 lib/ip_frag/ip_frag_internal.c         |  40 +-
 lib/ip_frag/rte_ipv4_reassembly.c      |  17 +-
 lib/ip_frag/rte_ipv6_fragmentation.c   |   3 +-
 lib/ip_frag/rte_ipv6_reassembly.c      |  36 +-
 7 files changed, 771 insertions(+), 11 deletions(-)
 create mode 100644 app/test/test_reassembly.c

-- 
2.53.0


^ permalink raw reply

* [PATCH] app/flow-perf: add represented-port and port-representor flow actions
From: Hadi Sandid @ 2026-07-01 15:04 UTC (permalink / raw)
  To: wisamm; +Cc: dev, Hadi Sandid

Add 'represented-port' and 'port-representor' action support,
and update documentation.

Signed-off-by: Hadi Sandid <hadisandid@gmail.com>
---
 .mailmap                         |  1 +
 app/test-flow-perf/actions_gen.c | 36 ++++++++++++++++++++++++++++++++
 app/test-flow-perf/main.c        | 21 +++++++++++++++++--
 doc/guides/tools/flow-perf.rst   | 20 ++++++++++++++++++
 4 files changed, 76 insertions(+), 2 deletions(-)

diff --git a/.mailmap b/.mailmap
index c5bc728fae..c51b14fbda 100644
--- a/.mailmap
+++ b/.mailmap
@@ -557,6 +557,7 @@ Guruprasad Rao <guruprasadx.rao@intel.com>
 Guy Kaneti <guyk@marvell.com>
 Guy Tzalik <gtzalik@amazon.com>
 H. Peter Anvin <hpa@linux.intel.com>
+Hadi Sandid <hadisandid@gmail.com>
 Haggai Eran <haggaie@nvidia.com>
 Haifei Luo <haifeil@nvidia.com>
 Haifeng Gao <gaohaifeng.gao@huawei.com>
diff --git a/app/test-flow-perf/actions_gen.c b/app/test-flow-perf/actions_gen.c
index 9d102e3af4..4a17349f92 100644
--- a/app/test-flow-perf/actions_gen.c
+++ b/app/test-flow-perf/actions_gen.c
@@ -187,6 +187,34 @@ add_port_id(struct rte_flow_action *actions,
 	actions[actions_counter].conf = &port_id;
 }
 
+static void
+add_represented_port(struct rte_flow_action *actions,
+	uint8_t actions_counter,
+	struct additional_para para)
+{
+	static struct rte_flow_action_ethdev represented_port = {
+		.port_id = PORT_ID_DST,
+	};
+
+	represented_port.port_id = para.dst_port;
+	actions[actions_counter].type = RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT;
+	actions[actions_counter].conf = &represented_port;
+}
+
+static void
+add_port_representor(struct rte_flow_action *actions,
+	uint8_t actions_counter,
+	struct additional_para para)
+{
+	static struct rte_flow_action_ethdev port_representor = {
+		.port_id = PORT_ID_DST,
+	};
+
+	port_representor.port_id = para.dst_port;
+	actions[actions_counter].type = RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR;
+	actions[actions_counter].conf = &port_representor;
+}
+
 static void
 add_drop(struct rte_flow_action *actions,
 	uint8_t actions_counter,
@@ -1102,6 +1130,14 @@ fill_actions(struct rte_flow_action *actions, uint64_t *flow_actions,
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_PORT_ID),
 			.funct = add_port_id
 		},
+		{
+			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT),
+			.funct = add_represented_port,
+		},
+		{
+			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR),
+			.funct = add_port_representor,
+		},
 		{
 			.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_DROP),
 			.funct = add_drop,
diff --git a/app/test-flow-perf/main.c b/app/test-flow-perf/main.c
index 6636d1517f..8147d41d4b 100644
--- a/app/test-flow-perf/main.c
+++ b/app/test-flow-perf/main.c
@@ -267,6 +267,18 @@ static const struct option_dict {
 		.map = &flow_actions[0],
 		.map_idx = &actions_idx
 	},
+	{
+		.str = "represented-port",
+		.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT),
+		.map = &flow_actions[0],
+		.map_idx = &actions_idx
+	},
+	{
+		.str = "port-representor",
+		.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR),
+		.map = &flow_actions[0],
+		.map_idx = &actions_idx
+	},
 	{
 		.str = "rss",
 		.mask = FLOW_ACTION_MASK(RTE_FLOW_ACTION_TYPE_RSS),
@@ -542,6 +554,8 @@ usage(char *progname)
 
 	printf("To set flow actions:\n");
 	printf("  --port-id: add port-id action in flow actions\n");
+	printf("  --represented-port: add represented-port action in flow actions\n");
+	printf("  --port-representor: add port-representor action in flow actions\n");
 	printf("  --rss: add rss action in flow actions\n");
 	printf("  --queue: add queue action in flow actions\n");
 	printf("  --jump: add jump action in flow actions\n");
@@ -699,6 +713,8 @@ args_parse(int argc, char **argv)
 		{ "icmpv6",                     0, 0, 0 },
 		/* Actions */
 		{ "port-id",                    2, 0, 0 },
+		{ "represented-port",           2, 0, 0 },
+		{ "port-representor",           2, 0, 0 },
 		{ "rss",                        0, 0, 0 },
 		{ "queue",                      0, 0, 0 },
 		{ "jump",                       0, 0, 0 },
@@ -913,8 +929,9 @@ args_parse(int argc, char **argv)
 					rte_exit(EXIT_FAILURE, "Invalid hairpin config mask\n");
 				hairpin_conf_mask = hp_conf;
 			}
-			if (strcmp(lgopts[opt_idx].name,
-					"port-id") == 0) {
+			if (strcmp(lgopts[opt_idx].name, "port-id") == 0 ||
+			    strcmp(lgopts[opt_idx].name, "represented-port") == 0 ||
+			    strcmp(lgopts[opt_idx].name, "port-representor") == 0) {
 				uint16_t port_idx = 0;
 
 				token = strtok(optarg, ",");
diff --git a/doc/guides/tools/flow-perf.rst b/doc/guides/tools/flow-perf.rst
index 657f06fec7..7f97a9940d 100644
--- a/doc/guides/tools/flow-perf.rst
+++ b/doc/guides/tools/flow-perf.rst
@@ -248,6 +248,26 @@ Actions:
        specify the destination port, the number of values should be
        the same with number of set bits in portmask.
 
+*	``--represented-port``
+	Add represented port redirection action to all flows actions.
+	Port redirection destination is defined in config.h under
+	PORT_ID_DST, default value = 1.
+
+		It can also have an optional parameter like
+		--represented-port=N[,M] to specify the destination port. The
+		number of values should be the same as the number of set bits in
+		portmask.
+
+*	``--port-representor``
+	Add port representor redirection action to all flows actions.
+	Port redirection destination is defined in config.h under
+	PORT_ID_DST, default value = 1.
+
+		It can also have an optional parameter like
+		--port-representor=N[,M] to specify the destination port. The
+		number of values should be the same as the number of set bits in
+		portmask.
+
 *	``--rss``
 	Add RSS action to all flows actions,
 	The queues in RSS action will be all queues configured
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH 1/2] net/ice: poll AdminQ if interrupt delivery unavailable
From: Bruce Richardson @ 2026-07-01 11:26 UTC (permalink / raw)
  To: Ciara Loftus; +Cc: dev, stable
In-Reply-To: <20260630141627.1035420-2-ciara.loftus@intel.com>

On Tue, Jun 30, 2026 at 02:16:26PM +0000, Ciara Loftus wrote:
> If interrupt registration fails eg. on FreeBSD with nic_uio, the ice
> interrupt handler is never called and AdminQ messages, including
> unsolicited link state notifications, are never processed.
> 
> This gap has always existed but was partially masked by the blocking link
> status poll in the ice dev_start function, which would delay some time
> until the link was up before returning. In this case, the link status was
> typically correct after dev_start. However after that delay was removed in
> 2dd7e99855 ("net/ice: revert fix link up when starting device"), the link
> status was often wrong after dev_start, and there was no mechanism to
> self-correct.
> 
> Rather than restoring the blocking wait removed by that commit which
> would re-impose a startup delay on every platform, instead activate a
> periodic alarm that drains the AdminQ on a 50ms interval, replicating the
> role of the interrupt handler on platforms where interrupt delivery is not
> supported.
> 
> Fixes: 2dd7e99855 ("net/ice: revert fix link up when starting device")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
> ---

This solution seems reasonable. Some thoughts and comments inline below.

/Bruce

>  drivers/net/intel/ice/ice_ethdev.c | 45 +++++++++++++++++++++++++++++-
>  drivers/net/intel/ice/ice_ethdev.h |  1 +
>  2 files changed, 45 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/net/intel/ice/ice_ethdev.c b/drivers/net/intel/ice/ice_ethdev.c
> index 022d92a2f6..447742ff8d 100644
> --- a/drivers/net/intel/ice/ice_ethdev.c
> +++ b/drivers/net/intel/ice/ice_ethdev.c
> @@ -3,6 +3,7 @@
>   */
>  
>  #include <rte_string_fns.h>
> +#include <rte_alarm.h>
>  #include <ethdev_pci.h>
>  
>  #include <ctype.h>
> @@ -108,7 +109,11 @@ enum ice_link_state_on_close {
>  
>  #define ICE_RSS_LUT_GLOBAL_QUEUE_NB	64
>  
> +/* Polling interval used when the interrupt path is unavailable */
> +#define ICE_AQ_ALARM_INTERVAL 50000 /* us */
> +
>  static int ice_dev_configure(struct rte_eth_dev *dev);
> +static void ice_aq_alarm_handler(void *param);
>  static int ice_dev_start(struct rte_eth_dev *dev);
>  static int ice_dev_stop(struct rte_eth_dev *dev);
>  static int ice_dev_close(struct rte_eth_dev *dev);
> @@ -1473,6 +1478,28 @@ ice_handle_aq_msg(struct rte_eth_dev *dev)
>  	}
>  }
>  
> +/*
> + * Alarm-based AdminQ polling handler that drains the AdminQ to process
> + * async events, then re-arms itself.
> + *
> + * @param param
> + *  The address of parameter (struct rte_eth_dev *) registered before.
> + *
> + * @return
> + *  void
> + */
> +static void
> +ice_aq_alarm_handler(void *param)
> +{
> +	struct rte_eth_dev *dev = (struct rte_eth_dev *)param;
> +	struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
> +
> +	if (!pf->adapter_stopped) {
> +		ice_handle_aq_msg(dev);
> +		rte_eal_alarm_set(ICE_AQ_ALARM_INTERVAL, ice_aq_alarm_handler, dev);
> +	}
> +}
> +
>  /**
>   * Interrupt handler triggered by NIC for handling
>   * specific interrupt.
> @@ -2913,6 +2940,11 @@ ice_dev_stop(struct rte_eth_dev *dev)
>  	rte_intr_efd_disable(intr_handle);
>  	rte_intr_vec_list_free(intr_handle);
>  
> +	if (pf->use_aq_polling) {
> +		rte_eal_alarm_cancel(ice_aq_alarm_handler, dev);
> +		pf->use_aq_polling = false;
> +	}
> +

Do we actually need to cancel the timer here? Since the callback itself
only rearms on started devices, we can let the timer expire and become a
no-op, rather than needing to explicitly cancel it here.

If so, and based on other suggestions I have below, we may be able to
remove this block from stop function entirely.

>  	pf->adapter_stopped = true;
>  	dev->data->dev_started = 0;
>  
> @@ -4434,7 +4466,13 @@ ice_dev_start(struct rte_eth_dev *dev)
>  	if (ice_rxq_intr_setup(dev))
>  		return -EIO;
>  
> -	rte_intr_enable(pci_dev->intr_handle);
> +	/* Fall back to polling the AdminQ via an alarm on platforms where
> +	 * interrupt delivery is unavailable.
> +	 */
> +	if (rte_intr_enable(pci_dev->intr_handle) != 0) {
> +		pf->use_aq_polling = true;
> +		rte_eal_alarm_set(ICE_AQ_ALARM_INTERVAL, ice_aq_alarm_handler, dev);
> +	}

If alarm set fails, we should at least emit a warning, even if there is
nothing we can really do about it.

Also, I don't think we need to track use_aq_polling at all. Its only use is
to cancel the timer, but all places where we cancel the timer are places
where we mark the port as stopped (with one exception, which I believe is a
separate error in the code), so the timer will self-cancel at next timeout
expiry.

>  
>  	/* Enable receiving broadcast packets and transmitting packets */
>  	ice_set_bit(ICE_PROMISC_BCAST_RX, pmask);
> @@ -4497,6 +4535,11 @@ ice_dev_start(struct rte_eth_dev *dev)
>  	for (i = 0; i < nb_txq; i++)
>  		ice_tx_queue_stop(dev, i);
>  
> +	if (pf->use_aq_polling) {
> +		rte_eal_alarm_cancel(ice_aq_alarm_handler, dev);
> +		pf->use_aq_polling = false;
> +	}
> +

As explained above, I don't think we need this block either. If we have an
error on start, then the port should be marked as stopped and the timer
won't rearm itself after it expires.

NOTE: there is an unrelated bug in the code here - at line 4550 in start,
we set "adapter_stopped = false", but there is still error handling at line
4564 which causes us to jump to rx_err and stop all the rx and tx queues
again, but neglecting to reset adapter_stopped back to true. I think we
need to either reset it immediately after the rx_err label, or move the
assignment down till after all potential rollback points are passed.

>  	return -EIO;
>  }
>  
> diff --git a/drivers/net/intel/ice/ice_ethdev.h b/drivers/net/intel/ice/ice_ethdev.h
> index 20e8a13fe9..86b256fc3f 100644
> --- a/drivers/net/intel/ice/ice_ethdev.h
> +++ b/drivers/net/intel/ice/ice_ethdev.h
> @@ -599,6 +599,7 @@ struct ice_pf {
>  	struct ice_eth_stats internal_stats;
>  	bool offset_loaded;
>  	bool adapter_stopped;
> +	bool use_aq_polling; /* true when interrupt path unavailable */
>  	struct ice_flow_list flow_list;
>  	rte_spinlock_t flow_ops_lock;
>  	bool init_link_up;
> -- 
> 2.43.0
> 

^ permalink raw reply

* Re: [PATCH v2 04/17] crypto/scheduler: replace strncpy with strlcpy
From: Ji, Kai @ 2026-07-01 11:18 UTC (permalink / raw)
  To: Richardson, Bruce, dev@dpdk.org
  Cc: stable@dpdk.org, Fan Zhang, De Lara Guarch, Pablo
In-Reply-To: <20260624103658.792750-5-bruce.richardson@intel.com>

[-- Attachment #1: Type: text/plain, Size: 2045 bytes --]

Acked-by: Kai Ji <kai.ji@intel.com>
________________________________
From: Richardson, Bruce <bruce.richardson@intel.com>
Sent: 24 June 2026 18:36
To: dev@dpdk.org <dev@dpdk.org>
Cc: Richardson, Bruce <bruce.richardson@intel.com>; stable@dpdk.org <stable@dpdk.org>; Ji, Kai <kai.ji@intel.com>; Fan Zhang <fanzhang.oss@gmail.com>; De Lara Guarch, Pablo <pablo.de.lara.guarch@intel.com>
Subject: [PATCH v2 04/17] crypto/scheduler: replace strncpy with strlcpy

Replace strncpy() with safer strlcpy() which always null-terminates.

Fixes: 50e14527b9d1 ("crypto/scheduler: improve parameters parsing")
Cc: stable@dpdk.org

Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
 drivers/crypto/scheduler/scheduler_pmd.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/crypto/scheduler/scheduler_pmd.c b/drivers/crypto/scheduler/scheduler_pmd.c
index 95ce893f05..ceaefa329b 100644
--- a/drivers/crypto/scheduler/scheduler_pmd.c
+++ b/drivers/crypto/scheduler/scheduler_pmd.c
@@ -229,10 +229,10 @@ cryptodev_scheduler_create(const char *name,
                         return -ENOMEM;
                 }

-               strncpy(sched_ctx->init_worker_names[
+               strlcpy(sched_ctx->init_worker_names[
                                         sched_ctx->nb_init_workers],
                                 init_params->worker_names[i],
-                               RTE_CRYPTODEV_SCHEDULER_NAME_MAX_LEN - 1);
+                               RTE_CRYPTODEV_SCHEDULER_NAME_MAX_LEN);

                 sched_ctx->nb_init_workers++;
         }
@@ -443,8 +443,8 @@ parse_worker_arg(const char *key __rte_unused,
                 return -ENOMEM;
         }

-       strncpy(param->worker_names[param->nb_workers++], value,
-                       RTE_CRYPTODEV_SCHEDULER_NAME_MAX_LEN - 1);
+       strlcpy(param->worker_names[param->nb_workers++], value,
+                       RTE_CRYPTODEV_SCHEDULER_NAME_MAX_LEN);

         return 0;
 }
--
2.53.0


[-- Attachment #2: Type: text/html, Size: 4616 bytes --]

^ permalink raw reply related

* Re: [PATCH] net/ixgbe: fix EEPROM read failure on copper media
From: Bruce Richardson @ 2026-07-01 11:07 UTC (permalink / raw)
  To: Mingjin Ye; +Cc: dev, stable, Anatoly Burakov, Vladimir Medvedkin
In-Reply-To: <akTv-wISOVqvd8XD@bricha3-mobl1.ger.corp.intel.com>

On Wed, Jul 01, 2026 at 11:46:19AM +0100, Bruce Richardson wrote:
> On Wed, Jul 01, 2026 at 09:07:03AM +0000, Mingjin Ye wrote:
> > The ixgbe_get_module_info() and ixgbe_get_module_eeprom() functions
> > attempt to read SFF EEPROM data for all port types. However, copper
> > media ports do not have SFF EEPROMs, causing invalid I2C operations
> > and generating error logs.
> > 
> > This patch adds a media type check at the beginning of both functions.
> > If the media type is ixgbe_media_type_copper, return -ENOTSUP to
> > safely skip the EEPROM read.
> > 
> > Fixes: b74d0cd43e37 ("net/ixgbe: add module EEPROM callbacks for ixgbe")
> > Cc: stable@dpdk.org
> > 
> > Signed-off-by: Mingjin Ye <mingjinx.ye@intel.com>
> > ---
> >  drivers/net/intel/ixgbe/ixgbe_ethdev.c | 12 ++++++++++++
> >  1 file changed, 12 insertions(+)
> > 
> Acked-by: Bruce Richardson <bruce.richardson@intel.com>
> 
Patch applied to dpdk-next-net-intel.
Thanks,
/Bruce

^ permalink raw reply

* [PATCH] net/intel: add MMG device support
From: Dhananjay Shukla @ 2026-07-01 10:29 UTC (permalink / raw)
  To: dev, bruce.richardson, aman.deep.singh

Add support for MMG device initialization in the CPFL PMD by
replacing direct device_id comparisons against CPFL_DEV_ID_MEV and
IXD_DEV_ID_VCPF with the idpf_is_vf_device() helper. This
centralizes VF/PF detection logic in the control path open and makes
the code handle MMG and future device IDs without requiring changes
at every call site.

Additionally:
- Export idpf_is_vf_device() as an internal symbol so it can be
  called from the CPFL driver.
- Skip VIRTCHNL2_OP_GET_STATS in cpfl_dev_stats_reset() for VF
  devices which do not support this virtchannel operation.
- Skip idpf_vc_rss_hash_set() in idpf_vport_rss_config() for VF
  devices which do not support setting RSS hash via virtchannel.

Signed-off-by: Dhananjay Shukla <dhananjay.shukla@intel.com>
---
 drivers/net/intel/cpfl/cpfl_ethdev.c        | 34 ++++++++++++---------
 drivers/net/intel/cpfl/cpfl_ethdev.h        |  6 ++--
 drivers/net/intel/cpfl/cpfl_vchnl.c         | 10 +++---
 drivers/net/intel/idpf/idpf_common_device.c | 13 +++++---
 drivers/net/intel/idpf/idpf_common_device.h |  1 +
 5 files changed, 38 insertions(+), 26 deletions(-)

diff --git a/drivers/net/intel/cpfl/cpfl_ethdev.c b/drivers/net/intel/cpfl/cpfl_ethdev.c
index 7ac8797490..e8cb7b71e8 100644
--- a/drivers/net/intel/cpfl/cpfl_ethdev.c
+++ b/drivers/net/intel/cpfl/cpfl_ethdev.c
@@ -369,6 +369,12 @@ cpfl_dev_stats_reset(struct rte_eth_dev *dev)
 	struct virtchnl2_vport_stats *pstats = NULL;
 	int ret;
 
+	/* VF devices do not support VIRTCHNL2_OP_GET_STATS */
+	if (idpf_is_vf_device(&vport->adapter->hw)) {
+		cpfl_reset_mbuf_alloc_failed_stats(dev);
+		return 0;
+	}
+
 	ret = idpf_vc_stats_query(vport, &pstats);
 	if (ret != 0)
 		return ret;
@@ -1703,7 +1709,7 @@ cpfl_handle_vchnl_event_msg(struct cpfl_adapter_ext *adapter, uint8_t *msg, uint
 	}
 
 	/* ignore if it is ctrl vport */
-	if (adapter->base.hw.device_id == CPFL_DEV_ID_MEV &&
+	if (!idpf_is_vf_device(&adapter->base.hw) &&
 			adapter->ctrl_vport.base.vport_id == vc_event->vport_id)
 		return;
 
@@ -1946,7 +1952,7 @@ cpfl_stop_cfgqs(struct cpfl_adapter_ext *adapter)
 	int i, ret;
 
 	for (i = 0; i < adapter->num_tx_cfgq; i++) {
-		if (adapter->base.hw.device_id == IXD_DEV_ID_VCPF)
+		if (idpf_is_vf_device(&adapter->base.hw))
 			ret = idpf_vc_ena_dis_one_queue_vcpf(&adapter->base,
 					adapter->cfgq_info[0].id,
 					VIRTCHNL2_QUEUE_TYPE_CONFIG_TX, false);
@@ -1961,7 +1967,7 @@ cpfl_stop_cfgqs(struct cpfl_adapter_ext *adapter)
 	}
 
 	for (i = 0; i < adapter->num_rx_cfgq; i++) {
-		if (adapter->base.hw.device_id == IXD_DEV_ID_VCPF)
+		if (idpf_is_vf_device(&adapter->base.hw))
 			ret = idpf_vc_ena_dis_one_queue_vcpf(&adapter->base,
 					adapter->cfgq_info[1].id,
 					VIRTCHNL2_QUEUE_TYPE_CONFIG_RX, false);
@@ -1997,7 +2003,7 @@ cpfl_start_cfgqs(struct cpfl_adapter_ext *adapter)
 	}
 
 	for (i = 0; i < adapter->num_tx_cfgq; i++) {
-		if (adapter->base.hw.device_id == IXD_DEV_ID_VCPF)
+		if (idpf_is_vf_device(&adapter->base.hw))
 			ret = idpf_vc_ena_dis_one_queue_vcpf(&adapter->base,
 					adapter->cfgq_info[0].id,
 					VIRTCHNL2_QUEUE_TYPE_CONFIG_TX, true);
@@ -2011,7 +2017,7 @@ cpfl_start_cfgqs(struct cpfl_adapter_ext *adapter)
 	}
 
 	for (i = 0; i < adapter->num_rx_cfgq; i++) {
-		if (adapter->base.hw.device_id == IXD_DEV_ID_VCPF)
+		if (idpf_is_vf_device(&adapter->base.hw))
 			ret = idpf_vc_ena_dis_one_queue_vcpf(&adapter->base,
 					adapter->cfgq_info[1].id,
 					VIRTCHNL2_QUEUE_TYPE_CONFIG_RX, true);
@@ -2185,7 +2191,7 @@ cpfl_cfgq_setup(struct cpfl_adapter_ext *adapter)
 	for (i = 0; i < adapter->num_cfgq; i++) {
 		if (i % 2 == 0) {
 		/* Setup Tx config queue */
-			if (adapter->base.hw.device_id == IXD_DEV_ID_VCPF)
+			if (idpf_is_vf_device(&adapter->base.hw))
 				create_cfgq_info[i].id = cfgq_info->cfgq[i].qid;
 			else
 				create_cfgq_info[i].id = vport->base.chunks_info.tx_start_qid +
@@ -2195,7 +2201,7 @@ cpfl_cfgq_setup(struct cpfl_adapter_ext *adapter)
 			create_cfgq_info[i].len = CPFL_CFGQ_RING_SIZE;
 			create_cfgq_info[i].buf_size = CPFL_CFGQ_BUFFER_SIZE;
 			memset(&create_cfgq_info[i].reg, 0, sizeof(struct idpf_ctlq_reg));
-			if (adapter->base.hw.device_id == IXD_DEV_ID_VCPF)
+			if (idpf_is_vf_device(&adapter->base.hw))
 				create_cfgq_info[i].reg.tail = cfgq_info->cfgq[i].qtail_reg_start;
 			else
 				create_cfgq_info[i].reg.tail = tx_qtail_start +
@@ -2203,7 +2209,7 @@ cpfl_cfgq_setup(struct cpfl_adapter_ext *adapter)
 
 		} else {
 		/* Setup Rx config queue */
-			if (adapter->base.hw.device_id == IXD_DEV_ID_VCPF)
+			if (idpf_is_vf_device(&adapter->base.hw))
 				create_cfgq_info[i].id = cfgq_info->cfgq[i].qid;
 			else
 				create_cfgq_info[i].id = vport->base.chunks_info.rx_start_qid +
@@ -2213,7 +2219,7 @@ cpfl_cfgq_setup(struct cpfl_adapter_ext *adapter)
 			create_cfgq_info[i].len = CPFL_CFGQ_RING_SIZE;
 			create_cfgq_info[i].buf_size = CPFL_CFGQ_BUFFER_SIZE;
 			memset(&create_cfgq_info[i].reg, 0, sizeof(struct idpf_ctlq_reg));
-			if (adapter->base.hw.device_id == IXD_DEV_ID_VCPF)
+			if (idpf_is_vf_device(&adapter->base.hw))
 				create_cfgq_info[i].reg.tail = cfgq_info->cfgq[i].qtail_reg_start;
 			else
 				create_cfgq_info[i].reg.tail = rx_qtail_start +
@@ -2289,7 +2295,7 @@ cpfl_ctrl_path_close(struct cpfl_adapter_ext *adapter)
 {
 	cpfl_stop_cfgqs(adapter);
 	cpfl_remove_cfgqs(adapter);
-	if (adapter->base.hw.device_id == CPFL_DEV_ID_MEV)
+	if (!idpf_is_vf_device(&adapter->base.hw))
 		idpf_vc_vport_destroy(&adapter->ctrl_vport.base);
 	else
 		vcpf_del_queues(adapter);
@@ -2300,7 +2306,7 @@ cpfl_ctrl_path_open(struct cpfl_adapter_ext *adapter)
 {
 	int ret;
 
-	if (adapter->base.hw.device_id == CPFL_DEV_ID_MEV) {
+	if (!idpf_is_vf_device(&adapter->base.hw)) {
 		ret = cpfl_vc_create_ctrl_vport(adapter);
 		if (ret) {
 			PMD_INIT_LOG(ERR, "Failed to create control vport");
@@ -2329,7 +2335,7 @@ cpfl_ctrl_path_open(struct cpfl_adapter_ext *adapter)
 	ret = cpfl_cfgq_setup(adapter);
 	if (ret) {
 		PMD_INIT_LOG(ERR, "Failed to setup control queues");
-		if (adapter->base.hw.device_id == CPFL_DEV_ID_MEV)
+		if (!idpf_is_vf_device(&adapter->base.hw))
 			goto err_cfgq_setup;
 		else
 			goto err_del_cfg;
@@ -2355,7 +2361,7 @@ cpfl_ctrl_path_open(struct cpfl_adapter_ext *adapter)
 	cpfl_remove_cfgqs(adapter);
 err_cfgq_setup:
 err_init_ctrl_vport:
-	if (adapter->base.hw.device_id == CPFL_DEV_ID_MEV)
+	if (!idpf_is_vf_device(&adapter->base.hw))
 		idpf_vc_vport_destroy(&adapter->ctrl_vport.base);
 err_del_cfg:
 	vcpf_del_queues(adapter);
@@ -2835,7 +2841,7 @@ cpfl_dev_vport_init(struct rte_eth_dev *dev, void *init_params)
 		}
 	}
 	/* get the vport info */
-	if (adapter->base.hw.device_id == IXD_DEV_ID_VCPF) {
+	if (idpf_is_vf_device(&adapter->base.hw)) {
 		pci_dev = RTE_CLASS_TO_BUS_DEVICE(dev, *pci_dev);
 		vi.func_type = VCPF_CPCHNL2_FTYPE_LAN_VF;
 		vi.pf_id = CPFL_HOST0_CPF_ID;
diff --git a/drivers/net/intel/cpfl/cpfl_ethdev.h b/drivers/net/intel/cpfl/cpfl_ethdev.h
index d41aa93191..31c9d7f1a7 100644
--- a/drivers/net/intel/cpfl/cpfl_ethdev.h
+++ b/drivers/net/intel/cpfl/cpfl_ethdev.h
@@ -340,7 +340,7 @@ cpfl_get_vsi_id(struct cpfl_itf *itf)
 
 		return repr->vport_info->vport.info.vsi_id;
 	} else if (itf->type == CPFL_ITF_TYPE_VPORT) {
-		if (itf->adapter->base.hw.device_id == CPFL_DEV_ID_MEV) {
+		if (!idpf_is_vf_device(&itf->adapter->base.hw)) {
 			vport_id = ((struct cpfl_vport *)itf)->base.vport_id;
 
 			vport_identity.func_type = CPCHNL2_FTYPE_LAN_PF;
@@ -359,7 +359,7 @@ cpfl_get_vsi_id(struct cpfl_itf *itf)
 
 			vsi_id = info->vport.info.vsi_id;
 		} else {
-			if (itf->adapter->base.hw.device_id == IXD_DEV_ID_VCPF)
+			if (idpf_is_vf_device(&itf->adapter->base.hw))
 				vsi_id = (uint16_t)((struct cpfl_vport *)itf)->vport_info.vsi_id;
 		}
 	}
@@ -402,7 +402,7 @@ vcpf_get_abs_qid(uint16_t port_id,  uint32_t queue_type)
 		return CPFL_INVALID_HW_ID;
 	if (itf->type == CPFL_ITF_TYPE_VPORT) {
 		vport = (void *)itf;
-		if (itf->adapter->base.hw.device_id == IXD_DEV_ID_VCPF) {
+		if (idpf_is_vf_device(&itf->adapter->base.hw)) {
 			switch (queue_type) {
 			case VIRTCHNL2_QUEUE_TYPE_TX:
 				return vport->vport_info.abs_start_txq_id;
diff --git a/drivers/net/intel/cpfl/cpfl_vchnl.c b/drivers/net/intel/cpfl/cpfl_vchnl.c
index c9d122d2c3..593516f700 100644
--- a/drivers/net/intel/cpfl/cpfl_vchnl.c
+++ b/drivers/net/intel/cpfl/cpfl_vchnl.c
@@ -216,7 +216,7 @@ cpfl_config_ctlq_rx(struct cpfl_adapter_ext *adapter)
 	uint16_t num_qs;
 	int size, err, i;
 
-	if (adapter->base.hw.device_id != IXD_DEV_ID_VCPF) {
+	if (!idpf_is_vf_device(&adapter->base.hw)) {
 		if (vport->base.rxq_model != VIRTCHNL2_QUEUE_MODEL_SINGLE) {
 			PMD_DRV_LOG(ERR, "This rxq model isn't supported.");
 			err = -EINVAL;
@@ -235,7 +235,7 @@ cpfl_config_ctlq_rx(struct cpfl_adapter_ext *adapter)
 		return err;
 	}
 
-	if (adapter->base.hw.device_id == IXD_DEV_ID_VCPF)
+	if (idpf_is_vf_device(&adapter->base.hw))
 		vc_rxqs->vport_id = rte_cpu_to_le_32(VCPF_CFGQ_VPORT_ID);
 	else
 		vc_rxqs->vport_id = vport->base.vport_id;
@@ -249,7 +249,7 @@ cpfl_config_ctlq_rx(struct cpfl_adapter_ext *adapter)
 		rxq_info->queue_id = adapter->cfgq_info[2 * i + 1].id;
 		rxq_info->model = VIRTCHNL2_QUEUE_MODEL_SINGLE;
 		rxq_info->data_buffer_size = adapter->cfgq_info[2 * i + 1].buf_size;
-		if (adapter->base.hw.device_id != IXD_DEV_ID_VCPF)
+		if (!idpf_is_vf_device(&adapter->base.hw))
 			rxq_info->max_pkt_size = vport->base.max_pkt_len;
 		rxq_info->desc_ids = VIRTCHNL2_RXDID_2_FLEX_SQ_NIC_M;
 		rxq_info->qflags |= VIRTCHNL2_RX_DESC_SIZE_32BYTE;
@@ -281,7 +281,7 @@ cpfl_config_ctlq_tx(struct cpfl_adapter_ext *adapter)
 	uint16_t num_qs;
 	int size, err, i;
 
-	if (adapter->base.hw.device_id != IXD_DEV_ID_VCPF) {
+	if (!idpf_is_vf_device(&adapter->base.hw)) {
 		if (vport->base.txq_model != VIRTCHNL2_QUEUE_MODEL_SINGLE) {
 			PMD_DRV_LOG(ERR, "This txq model isn't supported.");
 			err = -EINVAL;
@@ -300,7 +300,7 @@ cpfl_config_ctlq_tx(struct cpfl_adapter_ext *adapter)
 		return err;
 	}
 
-	if (adapter->base.hw.device_id == IXD_DEV_ID_VCPF)
+	if (idpf_is_vf_device(&adapter->base.hw))
 		vc_txqs->vport_id = rte_cpu_to_le_32(VCPF_CFGQ_VPORT_ID);
 	else
 		vc_txqs->vport_id = vport->base.vport_id;
diff --git a/drivers/net/intel/idpf/idpf_common_device.c b/drivers/net/intel/idpf/idpf_common_device.c
index f27a911977..1586dae96c 100644
--- a/drivers/net/intel/idpf/idpf_common_device.c
+++ b/drivers/net/intel/idpf/idpf_common_device.c
@@ -451,6 +451,7 @@ idpf_adapter_init(struct idpf_adapter *adapter)
  *
  * Return: 1 for VF device, 0 for PF device.
  */
+RTE_EXPORT_INTERNAL_SYMBOL(idpf_is_vf_device)
 bool idpf_is_vf_device(struct idpf_hw *hw)
 {
 	if (hw->device_id == IDPF_DEV_ID_SRIOV  || hw->device_id == IXD_DEV_ID_VCPF)
@@ -608,6 +609,7 @@ RTE_EXPORT_INTERNAL_SYMBOL(idpf_vport_rss_config)
 int
 idpf_vport_rss_config(struct idpf_vport *vport)
 {
+	struct idpf_hw *hw = &vport->adapter->hw;
 	int ret;
 
 	ret = idpf_vc_rss_key_set(vport);
@@ -622,10 +624,13 @@ idpf_vport_rss_config(struct idpf_vport *vport)
 		return ret;
 	}
 
-	ret = idpf_vc_rss_hash_set(vport);
-	if (ret != 0) {
-		DRV_LOG(ERR, "Failed to configure RSS hash");
-		return ret;
+	/* VF devices do not support setting RSS hash */
+	if (!idpf_is_vf_device(hw)) {
+		ret = idpf_vc_rss_hash_set(vport);
+		if (ret != 0) {
+			DRV_LOG(ERR, "Failed to configure RSS hash");
+			return ret;
+		}
 	}
 
 	return ret;
diff --git a/drivers/net/intel/idpf/idpf_common_device.h b/drivers/net/intel/idpf/idpf_common_device.h
index aee3cd317c..f4c7f1b2d1 100644
--- a/drivers/net/intel/idpf/idpf_common_device.h
+++ b/drivers/net/intel/idpf/idpf_common_device.h
@@ -63,6 +63,7 @@
 (PCI_BASE_CLASS_NETWORK_ETHERNET << 16 | PCI_SUB_BASE_CLASS_NETWORK_ETHERNET << 8 |  \
 IDPF_NETWORK_ETHERNET_PROGIF)
 
+__rte_internal
 bool idpf_is_vf_device(struct idpf_hw *hw);
 
 enum idpf_rx_func_type {
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH] net/ixgbe: fix EEPROM read failure on copper media
From: Bruce Richardson @ 2026-07-01 10:46 UTC (permalink / raw)
  To: Mingjin Ye; +Cc: dev, stable, Anatoly Burakov, Vladimir Medvedkin
In-Reply-To: <20260701090703.107264-1-mingjinx.ye@intel.com>

On Wed, Jul 01, 2026 at 09:07:03AM +0000, Mingjin Ye wrote:
> The ixgbe_get_module_info() and ixgbe_get_module_eeprom() functions
> attempt to read SFF EEPROM data for all port types. However, copper
> media ports do not have SFF EEPROMs, causing invalid I2C operations
> and generating error logs.
> 
> This patch adds a media type check at the beginning of both functions.
> If the media type is ixgbe_media_type_copper, return -ENOTSUP to
> safely skip the EEPROM read.
> 
> Fixes: b74d0cd43e37 ("net/ixgbe: add module EEPROM callbacks for ixgbe")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Mingjin Ye <mingjinx.ye@intel.com>
> ---
>  drivers/net/intel/ixgbe/ixgbe_ethdev.c | 12 ++++++++++++
>  1 file changed, 12 insertions(+)
> 
> diff --git a/drivers/net/intel/ixgbe/ixgbe_ethdev.c b/drivers/net/intel/ixgbe/ixgbe_ethdev.c
> index f9de95e4fc..44c29f0642 100644
> --- a/drivers/net/intel/ixgbe/ixgbe_ethdev.c
> +++ b/drivers/net/intel/ixgbe/ixgbe_ethdev.c
> @@ -7424,6 +7424,12 @@ ixgbe_get_module_info(struct rte_eth_dev *dev,
>  	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
>  		return -E_RTE_SECONDARY;
>  
> +	if (hw->phy.media_type == ixgbe_media_type_copper) {
> +		PMD_DRV_LOG(DEBUG, "Port %u is Base-T (copper), no SFF module info.",
> +				dev->data->port_id);
> +		return -ENOTSUP;
> +	}
> +
>  	/* Check whether we support SFF-8472 or not */
>  	status = hw->phy.ops.read_i2c_eeprom(hw,
>  					     IXGBE_SFF_SFF_8472_COMP,
> @@ -7477,6 +7483,12 @@ ixgbe_get_module_eeprom(struct rte_eth_dev *dev,
>  	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
>  		return -E_RTE_SECONDARY;
>  
> +	if (hw->phy.media_type == ixgbe_media_type_copper) {
> +		PMD_DRV_LOG(DEBUG, "Port %u is Base-T (copper), cannot read module EEPROM.",
> +				dev->data->port_id);
> +		return -ENOTSUP;
> +	}
> +

Looks reasonable.

Acked-by: Bruce Richardson <bruce.richardson@intel.com>


^ permalink raw reply

* Re: [PATCH v3 0/6] Intel network drivers enhancements
From: Bruce Richardson @ 2026-07-01 10:42 UTC (permalink / raw)
  To: Dawid Wesierski; +Cc: dev, thomas, stephen, marek.kasiewicz
In-Reply-To: <20260630120657.1046588-1-dawid.wesierski@intel.com>

On Tue, Jun 30, 2026 at 08:06:50AM -0400, Dawid Wesierski wrote:
> This series collects Intel E810 iavf and ice driver enhancements developed
> for the Media Transport Library (MTL) to support high-performance SMPTE
> ST 2110 media streaming workflows.
> 
> The "new code" in this series (specifically the testpmd enhancement in
> patch 6) demonstrates how the standard DPDK buffer-split offload can be
> orchestrated with pinned external-buffer mempools
> (RTE_PKTMBUF_POOL_F_PINNED_EXT_BUF) to achieve this. By pinning mbufs to
> contiguous hugepages, the NIC DMAs RTP payloads directly into application-
> owned memory. This eliminates the need for the header-split.
> 
> Documentation and a concrete configuration example for this workflow are
> included in the testpmd user guide (patch 6/6). The new 'create pinned-rxpool'
> command serves as both a test vehicle and a reference implementation for
> integrators.
> 
> In this series:
> - iavf maximum ring descriptor count to raised 4096 (HW limit).
> - iavf queue rate limit enabled reconfiguration at runtime.
> - Added opt-in "rl_burst_size" ice devarg for tighter packet spacing (jitter reduction).
> - Enabled PTP timestamping for all packets on ice.
> - Added opt-in "no_runtime_queue_setup" iavf devarg to restore strict
>   initialization semantics when required.
> 
> - Dropped the ethdev and net/intel "header-split mbuf callback" API
> - Replaced the out-of-tree approach with a testpmd demonstration (patch 6)
>   of the standard, upstream-preferred pinned-external-buffer workflow.
> - Fixed iavf error propagation and committed-state logic (Stephen Hemminger).
> - Converted the ice scheduler burst reduction and iavf runtime-config
>   disabling into opt-in devargs to preserve default behavior.
> - Updated documentation, commit messages, and .mailmap.
> 
> Dawid Wesierski (1):
>   app/testpmd: add pinned external-buffer Rx pool command
> 
> Marek Kasiewicz (5):
>   net/iavf: increase max ring descriptors to hardware limit
>   net/iavf: allow runtime queue rate limit configuration
>   net/ice: add scheduler rate-limiter burst size devarg
>   net/ice: timestamp all received packets when PTP is enabled
>   net/iavf: disable runtime queue setup capability
> 
Since RC2 is fast approaching, I've taken the first 3 patches of this
series into next-net-intel.

For the remaining 3 patches, the two driver patches need some more
discussion and probably rework. The final patch, for test-pmd, goes in a
different tree (not next-net-intel), so please send it as a separate
standalone patch.

Thanks,
/Bruce

^ permalink raw reply


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