DPDK patches and discussions
 help / color / mirror / Atom feed
* [PATCH 00/12] net/pcap: cleanups and test
@ 2026-01-06 18:26 Stephen Hemminger
  2026-01-06 18:26 ` [PATCH 01/12] net/pcap: avoid using rte_malloc and rte_memcpy Stephen Hemminger
                   ` (12 more replies)
  0 siblings, 13 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

This is a set of enhancements and tests to the PCAP PMD.
It started out when looking at the handling of timestamps
then realized lots of other cleanups were needed here.

Stephen Hemminger (12):
  net/pcap: avoid using rte_malloc and rte_memcpy
  net/pcap: support MTU set
  net/pcap: use bool for flags
  net/pcap: support Tx offloads
  net/pcap: support nanosecond timestamp precision
  net/pcap: remove global variables
  net/pcap: avoid use of volatile
  net/pcap: optimize calculation of receive timestamp
  net/pcap: report receive clock
  net/pcap: cleanup MAC address handling
  net/pcap: support MAC address set
  test: add test for pcap PMD

 app/test/meson.build                  |    2 +
 app/test/test_pmd_pcap.c              | 1471 +++++++++++++++++++++++++
 drivers/net/pcap/pcap_ethdev.c        |  373 ++++---
 drivers/net/pcap/pcap_osdep.h         |    2 +
 drivers/net/pcap/pcap_osdep_freebsd.c |   60 +-
 drivers/net/pcap/pcap_osdep_linux.c   |   51 +-
 drivers/net/pcap/pcap_osdep_windows.c |    5 +
 7 files changed, 1830 insertions(+), 134 deletions(-)
 create mode 100644 app/test/test_pmd_pcap.c

-- 
2.51.0


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

* [PATCH 01/12] net/pcap: avoid using rte_malloc and rte_memcpy
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-06 18:26 ` [PATCH 02/12] net/pcap: support MTU set Stephen Hemminger
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

No need to use rte_malloc or rte_memcpy in the short
code to get MAC address.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_osdep_freebsd.c | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/drivers/net/pcap/pcap_osdep_freebsd.c b/drivers/net/pcap/pcap_osdep_freebsd.c
index 20556b3e92..32e4a2bee7 100644
--- a/drivers/net/pcap/pcap_osdep_freebsd.c
+++ b/drivers/net/pcap/pcap_osdep_freebsd.c
@@ -8,9 +8,6 @@
 #include <net/if_dl.h>
 #include <sys/sysctl.h>
 
-#include <rte_malloc.h>
-#include <rte_memcpy.h>
-
 #include "pcap_osdep.h"
 
 int
@@ -41,19 +38,19 @@ osdep_iface_mac_get(const char *if_name, struct rte_ether_addr *mac)
 	if (len == 0)
 		return -1;
 
-	buf = rte_malloc(NULL, len, 0);
+	buf = malloc(len);
 	if (!buf)
 		return -1;
 
 	if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
-		rte_free(buf);
+		free(buf);
 		return -1;
 	}
 	ifm = (struct if_msghdr *)buf;
 	sdl = (struct sockaddr_dl *)(ifm + 1);
 
-	rte_memcpy(mac->addr_bytes, LLADDR(sdl), RTE_ETHER_ADDR_LEN);
+	memcpy(mac->addr_bytes, LLADDR(sdl), RTE_ETHER_ADDR_LEN);
 
-	rte_free(buf);
+	free(buf);
 	return 0;
 }
-- 
2.51.0


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

* [PATCH 02/12] net/pcap: support MTU set
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
  2026-01-06 18:26 ` [PATCH 01/12] net/pcap: avoid using rte_malloc and rte_memcpy Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-06 18:26 ` [PATCH 03/12] net/pcap: use bool for flags Stephen Hemminger
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

When used as single interface useful to support pass through
of MTU setting to enable larger frames.

Cleanup the transmit logic so that if packet sent exceeds MTU
is an error.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c        | 94 +++++++++++++++------------
 drivers/net/pcap/pcap_osdep.h         |  1 +
 drivers/net/pcap/pcap_osdep_freebsd.c | 26 ++++++++
 drivers/net/pcap/pcap_osdep_linux.c   | 21 ++++++
 drivers/net/pcap/pcap_osdep_windows.c |  5 ++
 5 files changed, 104 insertions(+), 43 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index f323c0b0df..f4cb444395 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -22,7 +22,7 @@
 #include "pcap_osdep.h"
 
 #define RTE_ETH_PCAP_SNAPSHOT_LEN 65535
-#define RTE_ETH_PCAP_SNAPLEN RTE_ETHER_MAX_JUMBO_FRAME_LEN
+#define RTE_ETH_PCAP_SNAPLEN (RTE_ETHER_MAX_JUMBO_FRAME_LEN - RTE_ETHER_CRC_LEN)
 #define RTE_ETH_PCAP_PROMISC 1
 #define RTE_ETH_PCAP_TIMEOUT -1
 
@@ -377,46 +377,46 @@ static uint16_t
 eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 {
 	unsigned int i;
-	struct rte_mbuf *mbuf;
 	struct pmd_process_private *pp;
 	struct pcap_tx_queue *dumper_q = queue;
 	uint16_t num_tx = 0;
 	uint32_t tx_bytes = 0;
-	struct pcap_pkthdr header;
 	pcap_dumper_t *dumper;
-	unsigned char temp_data[RTE_ETH_PCAP_SNAPLEN];
-	size_t len, caplen;
+	uint16_t mtu;
 
 	pp = rte_eth_devices[dumper_q->port_id].process_private;
 	dumper = pp->tx_dumper[dumper_q->queue_id];
+	mtu = rte_eth_devices[dumper_q->port_id].data->mtu;
 
-	if (dumper == NULL || nb_pkts == 0)
+	if (unlikely(dumper == NULL || nb_pkts == 0))
 		return 0;
 
 	/* writes the nb_pkts packets to the previously opened pcap file
 	 * dumper */
 	for (i = 0; i < nb_pkts; i++) {
-		mbuf = bufs[i];
-		len = caplen = rte_pktmbuf_pkt_len(mbuf);
-		if (unlikely(!rte_pktmbuf_is_contiguous(mbuf) &&
-				len > sizeof(temp_data))) {
-			caplen = sizeof(temp_data);
-		}
+		struct rte_mbuf *mbuf = bufs[i];
+		size_t len = rte_pktmbuf_pkt_len(mbuf);
+		uint8_t temp_data[RTE_ETH_PCAP_SNAPLEN];
+		struct pcap_pkthdr header;
+
+		if (unlikely(len > mtu))
+			continue;
 
 		calculate_timestamp(&header.ts);
 		header.len = len;
-		header.caplen = caplen;
+		header.caplen = len;
+
 		/* rte_pktmbuf_read() returns a pointer to the data directly
 		 * in the mbuf (when the mbuf is contiguous) or, otherwise,
 		 * a pointer to temp_data after copying into it.
 		 */
-		pcap_dump((u_char *)dumper, &header,
-			rte_pktmbuf_read(mbuf, 0, caplen, temp_data));
+		const uint8_t *data = rte_pktmbuf_read(mbuf, 0, len, temp_data);
+		pcap_dump((u_char *)dumper, &header, data);
 
 		num_tx++;
-		tx_bytes += caplen;
-		rte_pktmbuf_free(mbuf);
+		tx_bytes += len;
 	}
+	rte_pktmbuf_free_bulk(bufs, nb_pkts);
 
 	/*
 	 * Since there's no place to hook a callback when the forwarding
@@ -444,15 +444,15 @@ eth_tx_drop(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	if (unlikely(nb_pkts == 0))
 		return 0;
 
-	for (i = 0; i < nb_pkts; i++) {
+	for (i = 0; i < nb_pkts; i++)
 		tx_bytes += bufs[i]->pkt_len;
-		rte_pktmbuf_free(bufs[i]);
-	}
+
+	rte_pktmbuf_free_bulk(bufs, nb_pkts);
 
 	tx_queue->tx_stat.pkts += nb_pkts;
 	tx_queue->tx_stat.bytes += tx_bytes;
 
-	return i;
+	return nb_pkts;
 }
 
 /*
@@ -462,52 +462,45 @@ static uint16_t
 eth_pcap_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 {
 	unsigned int i;
-	int ret;
-	struct rte_mbuf *mbuf;
 	struct pmd_process_private *pp;
 	struct pcap_tx_queue *tx_queue = queue;
 	uint16_t num_tx = 0;
 	uint32_t tx_bytes = 0;
 	pcap_t *pcap;
-	unsigned char temp_data[RTE_ETH_PCAP_SNAPLEN];
-	size_t len;
+	uint16_t mtu;
 
 	pp = rte_eth_devices[tx_queue->port_id].process_private;
 	pcap = pp->tx_pcap[tx_queue->queue_id];
+	mtu = rte_eth_devices[tx_queue->port_id].data->mtu;
 
 	if (unlikely(nb_pkts == 0 || pcap == NULL))
 		return 0;
 
 	for (i = 0; i < nb_pkts; i++) {
-		mbuf = bufs[i];
-		len = rte_pktmbuf_pkt_len(mbuf);
-		if (unlikely(!rte_pktmbuf_is_contiguous(mbuf) &&
-				len > sizeof(temp_data))) {
-			PMD_LOG(ERR,
-				"Dropping multi segment PCAP packet. Size (%zd) > max size (%zd).",
-				len, sizeof(temp_data));
-			rte_pktmbuf_free(mbuf);
+		struct rte_mbuf *mbuf = bufs[i];
+		size_t len = rte_pktmbuf_pkt_len(mbuf);
+		uint8_t temp_data[RTE_ETH_PCAP_SNAPLEN];
+
+		if (unlikely(len > mtu))
 			continue;
-		}
 
 		/* rte_pktmbuf_read() returns a pointer to the data directly
 		 * in the mbuf (when the mbuf is contiguous) or, otherwise,
 		 * a pointer to temp_data after copying into it.
 		 */
-		ret = pcap_sendpacket(pcap,
-			rte_pktmbuf_read(mbuf, 0, len, temp_data), len);
-		if (unlikely(ret != 0))
-			break;
-		num_tx++;
-		tx_bytes += len;
-		rte_pktmbuf_free(mbuf);
+		const uint8_t *data = rte_pktmbuf_read(mbuf, 0, len, temp_data);
+
+		if (likely(pcap_sendpacket(pcap, data, len) == 0)) {
+			num_tx++;
+			tx_bytes += len;
+		}
 	}
 
 	tx_queue->tx_stat.pkts += num_tx;
 	tx_queue->tx_stat.bytes += tx_bytes;
-	tx_queue->tx_stat.err_pkts += i - num_tx;
+	tx_queue->tx_stat.err_pkts += nb_pkts - num_tx;
 
-	return i;
+	return nb_pkts;
 }
 
 /*
@@ -745,6 +738,8 @@ eth_dev_info(struct rte_eth_dev *dev,
 	dev_info->max_rx_queues = dev->data->nb_rx_queues;
 	dev_info->max_tx_queues = dev->data->nb_tx_queues;
 	dev_info->min_rx_bufsize = 0;
+	dev_info->min_mtu = RTE_ETHER_MIN_LEN - RTE_ETHER_HDR_LEN - RTE_ETHER_CRC_LEN;
+	dev_info->max_mtu = RTE_ETH_PCAP_SNAPLEN;
 
 	return 0;
 }
@@ -1002,6 +997,18 @@ eth_tx_queue_stop(struct rte_eth_dev *dev, uint16_t tx_queue_id)
 	return 0;
 }
 
+static int
+eth_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
+{
+	struct pmd_internals *internals = dev->data->dev_private;
+
+	if (internals->single_iface)
+		return osdep_iface_mtu_set(internals->if_index, mtu);
+
+	return 0;
+}
+
+
 static const struct eth_dev_ops ops = {
 	.dev_start = eth_dev_start,
 	.dev_stop = eth_dev_stop,
@@ -1015,6 +1022,7 @@ static const struct eth_dev_ops ops = {
 	.rx_queue_stop = eth_rx_queue_stop,
 	.tx_queue_stop = eth_tx_queue_stop,
 	.link_update = eth_link_update,
+	.mtu_set = eth_mtu_set,
 	.stats_get = eth_stats_get,
 	.stats_reset = eth_stats_reset,
 };
diff --git a/drivers/net/pcap/pcap_osdep.h b/drivers/net/pcap/pcap_osdep.h
index 2aa13f3629..3c8b7ff27b 100644
--- a/drivers/net/pcap/pcap_osdep.h
+++ b/drivers/net/pcap/pcap_osdep.h
@@ -14,5 +14,6 @@ extern int eth_pcap_logtype;
 
 int osdep_iface_index_get(const char *name);
 int osdep_iface_mac_get(const char *name, struct rte_ether_addr *mac);
+int osdep_iface_mtu_set(int index, uint16_t mtu);
 
 #endif
diff --git a/drivers/net/pcap/pcap_osdep_freebsd.c b/drivers/net/pcap/pcap_osdep_freebsd.c
index 32e4a2bee7..0279dbf00b 100644
--- a/drivers/net/pcap/pcap_osdep_freebsd.c
+++ b/drivers/net/pcap/pcap_osdep_freebsd.c
@@ -4,9 +4,12 @@
  * All rights reserved.
  */
 
+#include <unistd.h>
 #include <net/if.h>
 #include <net/if_dl.h>
 #include <sys/sysctl.h>
+#include <sys/ioctl.h>
+#include <sys/sockio.h>
 
 #include "pcap_osdep.h"
 
@@ -54,3 +57,26 @@ osdep_iface_mac_get(const char *if_name, struct rte_ether_addr *mac)
 	free(buf);
 	return 0;
 }
+
+int osdep_iface_mtu_set(int ifindex, uint16_t mtu)
+{
+	char ifname[IFNAMSIZ];
+
+	if (if_indextoname(ifindex, ifname) == NULL)
+		return -errno;
+
+	int s = socket(AF_INET, SOCK_DGRAM, 0);
+	if (s < 0)
+		return -errno;
+
+	struct ifreq ifr = { 0 };
+	if (s < 0)
+		return -EINVAL;
+
+	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
+	ifr.ifr_mtu = mtu;
+
+	int ret = ioctl(s, SIOCSIFMTU, &ifr);
+	close(s);
+	return (ret < 0) ? -errno : 0;
+}
diff --git a/drivers/net/pcap/pcap_osdep_linux.c b/drivers/net/pcap/pcap_osdep_linux.c
index 97033f57c5..d180e9b4b4 100644
--- a/drivers/net/pcap/pcap_osdep_linux.c
+++ b/drivers/net/pcap/pcap_osdep_linux.c
@@ -40,3 +40,24 @@ osdep_iface_mac_get(const char *if_name, struct rte_ether_addr *mac)
 	close(if_fd);
 	return 0;
 }
+
+int
+osdep_iface_mtu_set(int ifindex, uint16_t mtu)
+{
+	char ifname[IFNAMSIZ];
+
+	if (if_indextoname(ifindex, ifname) == NULL)
+		return -errno;
+
+	int s = socket(PF_INET, SOCK_DGRAM, 0);
+	if (s < 0)
+		return -errno;
+
+	struct ifreq ifr = { .ifr_mtu = mtu };
+	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
+
+	int ret = ioctl(s, SIOCSIFMTU, &ifr);
+	close(s);
+
+	return (ret < 0) ? -errno : 0;
+}
diff --git a/drivers/net/pcap/pcap_osdep_windows.c b/drivers/net/pcap/pcap_osdep_windows.c
index 1d398dc7ed..fde0db9961 100644
--- a/drivers/net/pcap/pcap_osdep_windows.c
+++ b/drivers/net/pcap/pcap_osdep_windows.c
@@ -116,3 +116,8 @@ osdep_iface_mac_get(const char *device_name, struct rte_ether_addr *mac)
 	free(info);
 	return ret;
 }
+
+int osdep_iface_mtu_set(int index __rte_unused, uint16_t mtu __rte_unused)
+{
+	return -ENOTSUP;
+}
-- 
2.51.0


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

* [PATCH 03/12] net/pcap: use bool for flags
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
  2026-01-06 18:26 ` [PATCH 01/12] net/pcap: avoid using rte_malloc and rte_memcpy Stephen Hemminger
  2026-01-06 18:26 ` [PATCH 02/12] net/pcap: support MTU set Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-07 10:28   ` Marat Khalili
  2026-01-06 18:26 ` [PATCH 04/12] net/pcap: support Tx offloads Stephen Hemminger
                   ` (9 subsequent siblings)
  12 siblings, 1 reply; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Save some space by using bool for flag values.
Use common code to parse value of a boolean flag.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 73 +++++++++++++++++-----------------
 1 file changed, 36 insertions(+), 37 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index f4cb444395..6a3e89aeab 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -5,6 +5,7 @@
  */
 
 #include <stdlib.h>
+#include <stdbool.h>
 #include <time.h>
 
 #include <pcap.h>
@@ -91,9 +92,9 @@ struct pmd_internals {
 	char devargs[ETH_PCAP_ARG_MAXLEN];
 	struct rte_ether_addr eth_addr;
 	int if_index;
-	int single_iface;
-	int phy_mac;
-	unsigned int infinite_rx;
+	bool single_iface;
+	bool phy_mac;
+	bool infinite_rx;
 };
 
 struct pmd_process_private {
@@ -103,25 +104,25 @@ struct pmd_process_private {
 };
 
 struct pmd_devargs {
-	unsigned int num_of_queue;
+	uint16_t num_of_queue;
+	bool phy_mac;
 	struct devargs_queue {
 		pcap_dumper_t *dumper;
 		pcap_t *pcap;
 		const char *name;
 		const char *type;
 	} queue[RTE_PMD_PCAP_MAX_QUEUES];
-	int phy_mac;
 };
 
 struct pmd_devargs_all {
 	struct pmd_devargs rx_queues;
 	struct pmd_devargs tx_queues;
-	int single_iface;
-	unsigned int is_tx_pcap;
-	unsigned int is_tx_iface;
-	unsigned int is_rx_pcap;
-	unsigned int is_rx_iface;
-	unsigned int infinite_rx;
+	bool single_iface;
+	bool is_tx_pcap;
+	bool is_tx_iface;
+	bool is_rx_pcap;
+	bool is_rx_iface;
+	bool infinite_rx;
 };
 
 static const char *valid_arguments[] = {
@@ -858,7 +859,7 @@ eth_dev_close(struct rte_eth_dev *dev)
 		}
 	}
 
-	if (internals->phy_mac == 0)
+	if (!internals->phy_mac)
 		/* not dynamically allocated, must not be freed */
 		dev->data->mac_addrs = NULL;
 
@@ -1180,31 +1181,29 @@ open_tx_iface(const char *key, const char *value, void *extra_args)
 }
 
 static int
-select_phy_mac(const char *key __rte_unused, const char *value,
-		void *extra_args)
+process_bool_flag(const char *key, const char *value, void *extra_args)
 {
-	if (extra_args) {
-		const int phy_mac = atoi(value);
-		int *enable_phy_mac = extra_args;
-
-		if (phy_mac)
-			*enable_phy_mac = 1;
-	}
-	return 0;
-}
+	bool *flag = extra_args;
+	/* table of possible representation of boolean */
+	static const char * const values[] = {
+		"false", "true",
+		"0", "1",
+		"disable", "enable",
+		"on", "off",
+	};
 
-static int
-get_infinite_rx_arg(const char *key __rte_unused,
-		const char *value, void *extra_args)
-{
-	if (extra_args) {
-		const int infinite_rx = atoi(value);
-		int *enable_infinite_rx = extra_args;
+	for (unsigned int i = 0; i < RTE_DIM(values); i++) {
+		if (strcmp(value, values[i]) == 0) {
+			*flag = !!(i & 1);
 
-		if (infinite_rx > 0)
-			*enable_infinite_rx = 1;
+			PMD_LOG(INFO, "%s set to %s",
+				key, *flag ? "true" : "false");
+			return 0;
+		}
 	}
-	return 0;
+
+	PMD_LOG(ERR, "Invalid '%s' value '%s'", key, value);
+	return -1;
 }
 
 static int
@@ -1479,7 +1478,7 @@ pmd_pcap_probe(struct rte_vdev_device *dev)
 		dumpers.queue[0] = pcaps.queue[0];
 
 		ret = rte_kvargs_process(kvlist, ETH_PCAP_PHY_MAC_ARG,
-				&select_phy_mac, &pcaps.phy_mac);
+					 &process_bool_flag, &pcaps.phy_mac);
 		if (ret < 0)
 			goto free_kvlist;
 
@@ -1518,9 +1517,9 @@ pmd_pcap_probe(struct rte_vdev_device *dev)
 
 		if (infinite_rx_arg_cnt == 1) {
 			ret = rte_kvargs_process(kvlist,
-					ETH_PCAP_INFINITE_RX_ARG,
-					&get_infinite_rx_arg,
-					&devargs_all.infinite_rx);
+						 ETH_PCAP_INFINITE_RX_ARG,
+						 &process_bool_flag,
+						 &devargs_all.infinite_rx);
 			if (ret < 0)
 				goto free_kvlist;
 			PMD_LOG(INFO, "infinite_rx has been %s for %s",
-- 
2.51.0


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

* [PATCH 04/12] net/pcap: support Tx offloads
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
                   ` (2 preceding siblings ...)
  2026-01-06 18:26 ` [PATCH 03/12] net/pcap: use bool for flags Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-06 18:26 ` [PATCH 05/12] net/pcap: support nanosecond timestamp precision Stephen Hemminger
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The driver already handles multi-segment mbufs but did not report
that in offload flags. Driver can easily insert vlan tag making
testing easier.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index 6a3e89aeab..41c5bc638f 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -403,6 +403,9 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		if (unlikely(len > mtu))
 			continue;
 
+		if ((mbuf->ol_flags & RTE_MBUF_F_TX_VLAN) && rte_vlan_insert(&mbuf))
+			continue;
+
 		calculate_timestamp(&header.ts);
 		header.len = len;
 		header.caplen = len;
@@ -485,6 +488,9 @@ eth_pcap_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		if (unlikely(len > mtu))
 			continue;
 
+		if ((mbuf->ol_flags & RTE_MBUF_F_TX_VLAN) && rte_vlan_insert(&mbuf))
+			continue;
+
 		/* rte_pktmbuf_read() returns a pointer to the data directly
 		 * in the mbuf (when the mbuf is contiguous) or, otherwise,
 		 * a pointer to temp_data after copying into it.
@@ -741,6 +747,8 @@ eth_dev_info(struct rte_eth_dev *dev,
 	dev_info->min_rx_bufsize = 0;
 	dev_info->min_mtu = RTE_ETHER_MIN_LEN - RTE_ETHER_HDR_LEN - RTE_ETHER_CRC_LEN;
 	dev_info->max_mtu = RTE_ETH_PCAP_SNAPLEN;
+	dev_info->tx_offload_capa = RTE_ETH_TX_OFFLOAD_MULTI_SEGS |
+		RTE_ETH_TX_OFFLOAD_VLAN_INSERT;
 
 	return 0;
 }
-- 
2.51.0


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

* [PATCH 05/12] net/pcap: support nanosecond timestamp precision
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
                   ` (3 preceding siblings ...)
  2026-01-06 18:26 ` [PATCH 04/12] net/pcap: support Tx offloads Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-06 18:26 ` [PATCH 06/12] net/pcap: remove global variables Stephen Hemminger
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Consistently support nanosecond timestamps across all the
variations of pcap PMD receive.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 103 +++++++++++++++++++++++++--------
 1 file changed, 79 insertions(+), 24 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index 41c5bc638f..905ca62bd4 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -19,13 +19,12 @@
 #include <rte_mbuf_dyn.h>
 #include <bus_vdev_driver.h>
 #include <rte_os_shim.h>
+#include <rte_time.h>
 
 #include "pcap_osdep.h"
 
 #define RTE_ETH_PCAP_SNAPSHOT_LEN 65535
 #define RTE_ETH_PCAP_SNAPLEN (RTE_ETHER_MAX_JUMBO_FRAME_LEN - RTE_ETHER_CRC_LEN)
-#define RTE_ETH_PCAP_PROMISC 1
-#define RTE_ETH_PCAP_TIMEOUT -1
 
 #define ETH_PCAP_RX_PCAP_ARG  "rx_pcap"
 #define ETH_PCAP_TX_PCAP_ARG  "tx_pcap"
@@ -68,6 +67,7 @@ struct queue_missed_stat {
 struct pcap_rx_queue {
 	uint16_t port_id;
 	uint16_t queue_id;
+	bool timestamp_offloading;
 	struct rte_mempool *mb_pool;
 	struct queue_stat rx_stat;
 	struct queue_missed_stat missed_stat;
@@ -95,6 +95,7 @@ struct pmd_internals {
 	bool single_iface;
 	bool phy_mac;
 	bool infinite_rx;
+	bool timestamp_offloading;
 };
 
 struct pmd_process_private {
@@ -299,7 +300,6 @@ eth_pcap_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		if (ret != 1) {
 			if (ret == PCAP_ERROR)
 				pcap_q->rx_stat.err_pkts++;
-
 			break;
 		}
 
@@ -325,10 +325,20 @@ eth_pcap_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		}
 
 		mbuf->pkt_len = len;
-		uint64_t us = (uint64_t)header->ts.tv_sec * US_PER_S + header->ts.tv_usec;
 
-		*RTE_MBUF_DYNFIELD(mbuf, timestamp_dynfield_offset, rte_mbuf_timestamp_t *) = us;
-		mbuf->ol_flags |= timestamp_rx_dynflag;
+		if (pcap_q->timestamp_offloading) {
+			/*
+			 * Although time stamp in struct pcap_pkthdr is defined as struct timeval,
+			 * it really is a timespec with nanosecond resolution.
+			 */
+			const struct timespec *ts = (struct timespec *)&header->ts;
+
+			*RTE_MBUF_DYNFIELD(mbuf, timestamp_dynfield_offset,
+					   rte_mbuf_timestamp_t *) = rte_timespec_to_ns(ts);
+
+			mbuf->ol_flags |= timestamp_rx_dynflag;
+		}
+
 		mbuf->port = pcap_q->port_id;
 		bufs[num_rx] = mbuf;
 		num_rx++;
@@ -514,22 +524,57 @@ eth_pcap_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
  * pcap_open_live wrapper function
  */
 static inline int
-open_iface_live(const char *iface, pcap_t **pcap) {
-	*pcap = pcap_open_live(iface, RTE_ETH_PCAP_SNAPLEN,
-			RTE_ETH_PCAP_PROMISC, RTE_ETH_PCAP_TIMEOUT, errbuf);
+open_iface_live(const char *iface, pcap_t **pcap)
+{
+	pcap_t *pc = pcap_create(iface, errbuf);
+	if (pc == NULL) {
+		PMD_LOG(ERR, "Couldn't create %s: %s", iface, errbuf);
+		goto error;
+	}
 
-	if (*pcap == NULL) {
-		PMD_LOG(ERR, "Couldn't open %s: %s", iface, errbuf);
-		return -1;
+	int status = pcap_set_tstamp_precision(pc, PCAP_TSTAMP_PRECISION_NANO);
+	if (status != 0)
+		PMD_LOG(WARNING, "%s: Could not set to ns precision: %s",
+			iface, pcap_statustostr(status));
+
+	status = pcap_set_immediate_mode(pc, 1);
+	if (status != 0)
+		PMD_LOG(WARNING, "%s: Could not set to immediate mode: %s",
+			iface, pcap_statustostr(status));
+
+	status = pcap_set_promisc(pc, 1);
+	if (status != 0)
+		PMD_LOG(WARNING, "%s: Could not set to promiscious: %s",
+			iface, pcap_statustostr(status));
+
+	status = pcap_set_snaplen(pc, RTE_ETH_PCAP_SNAPSHOT_LEN);
+	if (status != 0)
+		PMD_LOG(WARNING, "%s: Could not set snapshot length: %s",
+			iface, pcap_statustostr(status));
+
+	status = pcap_activate(pc);
+	if (status < 0) {
+		char *cp = pcap_geterr(pc);
+
+		if (status == PCAP_ERROR)
+			PMD_LOG(ERR, "%s: could not activate: %s", iface, cp);
+		else
+			PMD_LOG(ERR, "%s: %s (%s)", iface, pcap_statustostr(status), cp);
+		goto error;
 	}
 
-	if (pcap_setnonblock(*pcap, 1, errbuf)) {
+	if (pcap_setnonblock(pc, 1, errbuf)) {
 		PMD_LOG(ERR, "Couldn't set non-blocking on %s: %s", iface, errbuf);
-		pcap_close(*pcap);
-		return -1;
+		goto error;
 	}
 
+	*pcap = pc;
 	return 0;
+
+error:
+	if (pc)
+		pcap_close(pc);
+	return -1;
 }
 
 static int
@@ -576,7 +621,8 @@ open_single_tx_pcap(const char *pcap_filename, pcap_dumper_t **dumper)
 static int
 open_single_rx_pcap(const char *pcap_filename, pcap_t **pcap)
 {
-	*pcap = pcap_open_offline(pcap_filename, errbuf);
+	*pcap = pcap_open_offline_with_tstamp_precision(pcap_filename,
+							PCAP_TSTAMP_PRECISION_NANO, errbuf);
 	if (*pcap == NULL) {
 		PMD_LOG(ERR, "Couldn't open %s: %s", pcap_filename,
 			errbuf);
@@ -613,6 +659,15 @@ eth_dev_start(struct rte_eth_dev *dev)
 	struct pcap_tx_queue *tx;
 	struct pcap_rx_queue *rx;
 
+	if (internals->timestamp_offloading) {
+		int ret = rte_mbuf_dyn_rx_timestamp_register(&timestamp_dynfield_offset,
+							     &timestamp_rx_dynflag);
+		if (ret != 0) {
+			PMD_LOG(ERR, "Failed to register Rx timestamp field/flag");
+			return ret;
+		}
+	}
+
 	/* Special iface case. Single pcap is open and shared between tx/rx. */
 	if (internals->single_iface) {
 		tx = &internals->tx_queue[0];
@@ -728,8 +783,13 @@ eth_dev_stop(struct rte_eth_dev *dev)
 }
 
 static int
-eth_dev_configure(struct rte_eth_dev *dev __rte_unused)
+eth_dev_configure(struct rte_eth_dev *dev)
 {
+	struct pmd_internals *internals = dev->data->dev_private;
+	struct rte_eth_conf *dev_conf = &dev->data->dev_conf;
+	const struct rte_eth_rxmode *rxmode = &dev_conf->rxmode;
+
+	internals->timestamp_offloading = !!(rxmode->offloads & RTE_ETH_RX_OFFLOAD_TIMESTAMP);
 	return 0;
 }
 
@@ -749,6 +809,7 @@ eth_dev_info(struct rte_eth_dev *dev,
 	dev_info->max_mtu = RTE_ETH_PCAP_SNAPLEN;
 	dev_info->tx_offload_capa = RTE_ETH_TX_OFFLOAD_MULTI_SEGS |
 		RTE_ETH_TX_OFFLOAD_VLAN_INSERT;
+	dev_info->rx_offload_capa = RTE_ETH_RX_OFFLOAD_TIMESTAMP;
 
 	return 0;
 }
@@ -896,6 +957,7 @@ eth_rx_queue_setup(struct rte_eth_dev *dev,
 	pcap_q->port_id = dev->data->port_id;
 	pcap_q->queue_id = rx_queue_id;
 	dev->data->rx_queues[rx_queue_id] = pcap_q;
+	pcap_q->timestamp_offloading = internals->timestamp_offloading;
 
 	if (internals->infinite_rx) {
 		struct pmd_process_private *pp;
@@ -1446,13 +1508,6 @@ pmd_pcap_probe(struct rte_vdev_device *dev)
 	start_cycles = rte_get_timer_cycles();
 	hz = rte_get_timer_hz();
 
-	ret = rte_mbuf_dyn_rx_timestamp_register(&timestamp_dynfield_offset,
-			&timestamp_rx_dynflag);
-	if (ret != 0) {
-		PMD_LOG(ERR, "Failed to register Rx timestamp field/flag");
-		return -1;
-	}
-
 	if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
 		eth_dev = rte_eth_dev_attach_secondary(name);
 		if (!eth_dev) {
-- 
2.51.0


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

* [PATCH 06/12] net/pcap: remove global variables
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
                   ` (4 preceding siblings ...)
  2026-01-06 18:26 ` [PATCH 05/12] net/pcap: support nanosecond timestamp precision Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-07  9:48   ` Marat Khalili
  2026-01-06 18:26 ` [PATCH 07/12] net/pcap: avoid use of volatile Stephen Hemminger
                   ` (6 subsequent siblings)
  12 siblings, 1 reply; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Localize variables where possible.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index 905ca62bd4..1658685a28 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -39,11 +39,9 @@
 
 #define RTE_PMD_PCAP_MAX_QUEUES 16
 
-static char errbuf[PCAP_ERRBUF_SIZE];
 static struct timespec start_time;
 static uint64_t start_cycles;
 static uint64_t hz;
-static uint8_t iface_idx;
 
 static uint64_t timestamp_rx_dynflag;
 static int timestamp_dynfield_offset = -1;
@@ -526,6 +524,8 @@ eth_pcap_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 static inline int
 open_iface_live(const char *iface, pcap_t **pcap)
 {
+	char errbuf[PCAP_ERRBUF_SIZE];
+
 	pcap_t *pc = pcap_create(iface, errbuf);
 	if (pc == NULL) {
 		PMD_LOG(ERR, "Couldn't create %s: %s", iface, errbuf);
@@ -621,6 +621,8 @@ open_single_tx_pcap(const char *pcap_filename, pcap_dumper_t **dumper)
 static int
 open_single_rx_pcap(const char *pcap_filename, pcap_t **pcap)
 {
+	char errbuf[PCAP_ERRBUF_SIZE];
+
 	*pcap = pcap_open_offline_with_tstamp_precision(pcap_filename,
 							PCAP_TSTAMP_PRECISION_NANO, errbuf);
 	if (*pcap == NULL) {
@@ -1314,11 +1316,13 @@ pmd_init_internals(struct rte_vdev_device *vdev,
 	 * - and point eth_dev structure to new eth_dev_data structure
 	 */
 	*internals = (*eth_dev)->data->dev_private;
+
 	/*
 	 * Interface MAC = 02:70:63:61:70:<iface_idx>
 	 * derived from: 'locally administered':'p':'c':'a':'p':'iface_idx'
 	 * where the middle 4 characters are converted to hex.
 	 */
+	static uint8_t iface_idx;
 	(*internals)->eth_addr = (struct rte_ether_addr) {
 		.addr_bytes = { 0x02, 0x70, 0x63, 0x61, 0x70, iface_idx++ }
 	};
-- 
2.51.0


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

* [PATCH 07/12] net/pcap: avoid use of volatile
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
                   ` (5 preceding siblings ...)
  2026-01-06 18:26 ` [PATCH 06/12] net/pcap: remove global variables Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-07 10:31   ` Marat Khalili
  2026-01-06 18:26 ` [PATCH 08/12] net/pcap: optimize calculation of receive timestamp Stephen Hemminger
                   ` (5 subsequent siblings)
  12 siblings, 1 reply; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Using volatile for statistics generates expensive atomic rmw
operations when not necessary.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 38 ++++++++++++++++++++++++++--------
 1 file changed, 29 insertions(+), 9 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index 1658685a28..175d6998f9 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -47,10 +47,10 @@ static uint64_t timestamp_rx_dynflag;
 static int timestamp_dynfield_offset = -1;
 
 struct queue_stat {
-	volatile unsigned long pkts;
-	volatile unsigned long bytes;
-	volatile unsigned long err_pkts;
-	volatile unsigned long rx_nombuf;
+	uint64_t pkts;
+	uint64_t bytes;
+	uint64_t err_pkts;
+	uint64_t rx_nombuf;
 };
 
 struct queue_missed_stat {
@@ -267,6 +267,9 @@ eth_pcap_rx_infinite(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	pcap_q->rx_stat.pkts += i;
 	pcap_q->rx_stat.bytes += rx_bytes;
 
+	/* ensure store operations of statistics are visible */
+	rte_atomic_thread_fence(rte_memory_order_release);
+
 	return i;
 }
 
@@ -345,6 +348,9 @@ eth_pcap_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	pcap_q->rx_stat.pkts += num_rx;
 	pcap_q->rx_stat.bytes += rx_bytes;
 
+	/* ensure store operations of statistics are visible */
+	rte_atomic_thread_fence(rte_memory_order_release);
+
 	return num_rx;
 }
 
@@ -440,6 +446,9 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	dumper_q->tx_stat.bytes += tx_bytes;
 	dumper_q->tx_stat.err_pkts += nb_pkts - num_tx;
 
+	/* ensure store operations of statistics are visible */
+	rte_atomic_thread_fence(rte_memory_order_release);
+
 	return nb_pkts;
 }
 
@@ -464,6 +473,9 @@ eth_tx_drop(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	tx_queue->tx_stat.pkts += nb_pkts;
 	tx_queue->tx_stat.bytes += tx_bytes;
 
+	/* ensure store operations of statistics are visible */
+	rte_atomic_thread_fence(rte_memory_order_release);
+
 	return nb_pkts;
 }
 
@@ -515,6 +527,9 @@ eth_pcap_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	tx_queue->tx_stat.bytes += tx_bytes;
 	tx_queue->tx_stat.err_pkts += nb_pkts - num_tx;
 
+	/* ensure store operations of statistics are visible */
+	rte_atomic_thread_fence(rte_memory_order_release);
+
 	return nb_pkts;
 }
 
@@ -821,13 +836,16 @@ eth_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
 	      struct eth_queue_stats *qstats)
 {
 	unsigned int i;
-	unsigned long rx_packets_total = 0, rx_bytes_total = 0;
-	unsigned long rx_missed_total = 0;
-	unsigned long rx_nombuf_total = 0, rx_err_total = 0;
-	unsigned long tx_packets_total = 0, tx_bytes_total = 0;
-	unsigned long tx_packets_err_total = 0;
+	uint64_t rx_packets_total = 0, rx_bytes_total = 0;
+	uint64_t rx_missed_total = 0;
+	uint64_t rx_nombuf_total = 0, rx_err_total = 0;
+	uint64_t tx_packets_total = 0, tx_bytes_total = 0;
+	uint64_t tx_packets_err_total = 0;
 	const struct pmd_internals *internal = dev->data->dev_private;
 
+	/* ensure that current statistics are visible */
+	rte_atomic_thread_fence(rte_memory_order_acquire);
+
 	for (i = 0; i < RTE_ETHDEV_QUEUE_STAT_CNTRS &&
 			i < dev->data->nb_rx_queues; i++) {
 		if (qstats != NULL) {
@@ -884,6 +902,8 @@ eth_stats_reset(struct rte_eth_dev *dev)
 		internal->tx_queue[i].tx_stat.err_pkts = 0;
 	}
 
+	/* ensure store operations of statistics are visible */
+	rte_atomic_thread_fence(rte_memory_order_release);
 	return 0;
 }
 
-- 
2.51.0


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

* [PATCH 08/12] net/pcap: optimize calculation of receive timestamp
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
                   ` (6 preceding siblings ...)
  2026-01-06 18:26 ` [PATCH 07/12] net/pcap: avoid use of volatile Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-07 10:58   ` Marat Khalili
  2026-01-06 18:26 ` [PATCH 09/12] net/pcap: report receive clock Stephen Hemminger
                   ` (4 subsequent siblings)
  12 siblings, 1 reply; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Avoid doing slow instructions in receive path when calculating
timestamp. Give all packets in the same rx burst the same timestamp.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index 175d6998f9..e283fb3787 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -20,6 +20,7 @@
 #include <bus_vdev_driver.h>
 #include <rte_os_shim.h>
 #include <rte_time.h>
+#include <rte_reciprocal.h>
 
 #include "pcap_osdep.h"
 
@@ -41,7 +42,7 @@
 
 static struct timespec start_time;
 static uint64_t start_cycles;
-static uint64_t hz;
+static struct rte_reciprocal_u64 hz_inv;
 
 static uint64_t timestamp_rx_dynflag;
 static int timestamp_dynfield_offset = -1;
@@ -362,8 +363,6 @@ eth_null_rx(void *queue __rte_unused,
 	return 0;
 }
 
-#define NSEC_PER_SEC	1000000000L
-
 /*
  * This function stores nanoseconds in `tv_usec` field of `struct timeval`,
  * because `ts` goes directly to nanosecond-precision dump.
@@ -374,8 +373,10 @@ calculate_timestamp(struct timeval *ts) {
 	struct timespec cur_time;
 
 	cycles = rte_get_timer_cycles() - start_cycles;
-	cur_time.tv_sec = cycles / hz;
-	cur_time.tv_nsec = (cycles % hz) * NSEC_PER_SEC / hz;
+	cur_time.tv_sec = rte_reciprocal_divide_u64(cycles, &hz_inv);
+	/* compute remainder */
+	cycles -= cur_time.tv_sec * rte_get_timer_hz();
+	cur_time.tv_nsec = rte_reciprocal_divide_u64(cycles * NS_PER_S, &hz_inv);
 
 	ts->tv_sec = start_time.tv_sec + cur_time.tv_sec;
 	ts->tv_usec = start_time.tv_nsec + cur_time.tv_nsec;
@@ -394,6 +395,7 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	unsigned int i;
 	struct pmd_process_private *pp;
 	struct pcap_tx_queue *dumper_q = queue;
+	struct pcap_pkthdr header;
 	uint16_t num_tx = 0;
 	uint32_t tx_bytes = 0;
 	pcap_dumper_t *dumper;
@@ -406,13 +408,14 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	if (unlikely(dumper == NULL || nb_pkts == 0))
 		return 0;
 
-	/* writes the nb_pkts packets to the previously opened pcap file
-	 * dumper */
+	/* all packets in burst have same timestamp */
+	calculate_timestamp(&header.ts);
+
+	/* writes the nb_pkts packets to the previously opened pcap file dumper */
 	for (i = 0; i < nb_pkts; i++) {
 		struct rte_mbuf *mbuf = bufs[i];
 		size_t len = rte_pktmbuf_pkt_len(mbuf);
 		uint8_t temp_data[RTE_ETH_PCAP_SNAPLEN];
-		struct pcap_pkthdr header;
 
 		if (unlikely(len > mtu))
 			continue;
@@ -420,7 +423,6 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		if ((mbuf->ol_flags & RTE_MBUF_F_TX_VLAN) && rte_vlan_insert(&mbuf))
 			continue;
 
-		calculate_timestamp(&header.ts);
 		header.len = len;
 		header.caplen = len;
 
@@ -1530,7 +1532,8 @@ pmd_pcap_probe(struct rte_vdev_device *dev)
 
 	timespec_get(&start_time, TIME_UTC);
 	start_cycles = rte_get_timer_cycles();
-	hz = rte_get_timer_hz();
+
+	hz_inv = rte_reciprocal_value_u64(rte_get_timer_hz());
 
 	if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
 		eth_dev = rte_eth_dev_attach_secondary(name);
-- 
2.51.0


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

* [PATCH 09/12] net/pcap: report receive clock
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
                   ` (7 preceding siblings ...)
  2026-01-06 18:26 ` [PATCH 08/12] net/pcap: optimize calculation of receive timestamp Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-06 18:26 ` [PATCH 10/12] net/pcap: cleanup MAC address handling Stephen Hemminger
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Since this drive supports receive timestamping, provide a callback
so that the application can determine the clock rate for timestamp.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index e283fb3787..3eb313ebb8 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -1103,6 +1103,16 @@ eth_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
 	return 0;
 }
 
+/* Timestamp values in receive packets from libpcap are in UTC */
+static int
+eth_rx_clock(struct rte_eth_dev *dev __rte_unused, uint64_t *timestamp)
+{
+	struct timespec cur_time;
+
+	timespec_get(&cur_time, TIME_UTC);
+	*timestamp = rte_timespec_to_ns(&cur_time);
+	return 0;
+}
 
 static const struct eth_dev_ops ops = {
 	.dev_start = eth_dev_start,
@@ -1120,6 +1130,7 @@ static const struct eth_dev_ops ops = {
 	.mtu_set = eth_mtu_set,
 	.stats_get = eth_stats_get,
 	.stats_reset = eth_stats_reset,
+	.read_clock = eth_rx_clock,
 };
 
 static int
-- 
2.51.0


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

* [PATCH 10/12] net/pcap: cleanup MAC address handling
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
                   ` (8 preceding siblings ...)
  2026-01-06 18:26 ` [PATCH 09/12] net/pcap: report receive clock Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-06 18:26 ` [PATCH 11/12] net/pcap: support MAC address set Stephen Hemminger
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Use rte_ether_addr structure to avoid memcpy and void *.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index 3eb313ebb8..bf863e8708 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -1383,9 +1383,9 @@ pmd_init_internals(struct rte_vdev_device *vdev,
 
 static int
 eth_pcap_update_mac(const char *if_name, struct rte_eth_dev *eth_dev,
-		const unsigned int numa_node)
+		    const unsigned int numa_node)
 {
-	void *mac_addrs;
+	struct rte_ether_addr *mac_addrs;
 	struct rte_ether_addr mac;
 
 	if (osdep_iface_mac_get(if_name, &mac) < 0)
@@ -1396,7 +1396,7 @@ eth_pcap_update_mac(const char *if_name, struct rte_eth_dev *eth_dev,
 		return -1;
 
 	PMD_LOG(INFO, "Setting phy MAC for %s", if_name);
-	rte_memcpy(mac_addrs, mac.addr_bytes, RTE_ETHER_ADDR_LEN);
+	rte_ether_addr_copy(&mac, mac_addrs);
 	eth_dev->data->mac_addrs = mac_addrs;
 	return 0;
 }
-- 
2.51.0


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

* [PATCH 11/12] net/pcap: support MAC address set
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
                   ` (9 preceding siblings ...)
  2026-01-06 18:26 ` [PATCH 10/12] net/pcap: cleanup MAC address handling Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-06 18:26 ` [PATCH 12/12] test: add test for pcap PMD Stephen Hemminger
  2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
  12 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

When using pcap on a single interface, it is possible for
driver to proxy the mac address set operation.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c        | 13 ++++++++++++
 drivers/net/pcap/pcap_osdep.h         |  1 +
 drivers/net/pcap/pcap_osdep_freebsd.c | 23 ++++++++++++++++++++
 drivers/net/pcap/pcap_osdep_linux.c   | 30 ++++++++++++++++++++++++++-
 4 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index bf863e8708..79f2e28fbc 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -1103,6 +1103,18 @@ eth_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
 	return 0;
 }
 
+static int
+eth_dev_macaddr_set(struct rte_eth_dev *dev, struct rte_ether_addr *addr)
+{
+	struct pmd_internals *internals = dev->data->dev_private;
+
+	if (internals->single_iface)
+		return osdep_iface_mac_set(internals->if_index, addr);
+	else
+		return -ENOTSUP;
+}
+
+
 /* Timestamp values in receive packets from libpcap are in UTC */
 static int
 eth_rx_clock(struct rte_eth_dev *dev __rte_unused, uint64_t *timestamp)
@@ -1127,6 +1139,7 @@ static const struct eth_dev_ops ops = {
 	.rx_queue_stop = eth_rx_queue_stop,
 	.tx_queue_stop = eth_tx_queue_stop,
 	.link_update = eth_link_update,
+	.mac_addr_set = eth_dev_macaddr_set,
 	.mtu_set = eth_mtu_set,
 	.stats_get = eth_stats_get,
 	.stats_reset = eth_stats_reset,
diff --git a/drivers/net/pcap/pcap_osdep.h b/drivers/net/pcap/pcap_osdep.h
index 3c8b7ff27b..00944e0843 100644
--- a/drivers/net/pcap/pcap_osdep.h
+++ b/drivers/net/pcap/pcap_osdep.h
@@ -14,6 +14,7 @@ extern int eth_pcap_logtype;
 
 int osdep_iface_index_get(const char *name);
 int osdep_iface_mac_get(const char *name, struct rte_ether_addr *mac);
+int osdep_iface_mac_set(int ifindex, const struct rte_ether_addr *mac);
 int osdep_iface_mtu_set(int index, uint16_t mtu);
 
 #endif
diff --git a/drivers/net/pcap/pcap_osdep_freebsd.c b/drivers/net/pcap/pcap_osdep_freebsd.c
index 0279dbf00b..39227da63a 100644
--- a/drivers/net/pcap/pcap_osdep_freebsd.c
+++ b/drivers/net/pcap/pcap_osdep_freebsd.c
@@ -58,6 +58,29 @@ osdep_iface_mac_get(const char *if_name, struct rte_ether_addr *mac)
 	return 0;
 }
 
+int
+osdep_iface_mac_set(int ifindex, const struct rte_ether_addr *mac)
+{
+	char ifname[IFNAMSIZ];
+
+	if (if_indextoname(ifindex, ifname) == NULL)
+		return -errno;
+
+	int s = socket(AF_INET, SOCK_DGRAM, 0);
+	if (s < 0)
+		return -errno;
+
+	struct ifreq ifr = { 0 };
+	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
+	ifr.ifr_addr.sa_family = AF_LINK;
+	memcpy(ifr.ifr_addr.sa_data, mac, sizeof(*mac));
+
+	int ret = ioctl(s, SIOCSIFLLADDR, &ifr);
+	close(s);
+
+	return (ret < 0) ? -errno : 0;
+}
+
 int osdep_iface_mtu_set(int ifindex, uint16_t mtu)
 {
 	char ifname[IFNAMSIZ];
diff --git a/drivers/net/pcap/pcap_osdep_linux.c b/drivers/net/pcap/pcap_osdep_linux.c
index d180e9b4b4..d5a3855a0a 100644
--- a/drivers/net/pcap/pcap_osdep_linux.c
+++ b/drivers/net/pcap/pcap_osdep_linux.c
@@ -4,13 +4,18 @@
  * All rights reserved.
  */
 
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
 #include <net/if.h>
+#include <net/if_arp.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
-#include <unistd.h>
 
 #include <rte_memcpy.h>
 #include <rte_string_fns.h>
+#include <rte_ether.h>
 
 #include "pcap_osdep.h"
 
@@ -41,6 +46,29 @@ osdep_iface_mac_get(const char *if_name, struct rte_ether_addr *mac)
 	return 0;
 }
 
+int
+osdep_iface_mac_set(int ifindex, const struct rte_ether_addr *mac)
+{
+	char ifname[IFNAMSIZ];
+
+	if (if_indextoname(ifindex, ifname) == NULL)
+		return -errno;
+
+	int s = socket(AF_INET, SOCK_DGRAM, 0);
+	if (s < 0)
+		return -errno;
+
+	struct ifreq ifr = { 0 };
+	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
+	ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
+	memcpy(ifr.ifr_hwaddr.sa_data, mac, sizeof(*mac));
+
+	int ret = ioctl(s, SIOCSIFHWADDR, &ifr);
+	close(s);
+
+	return (ret < 0) ? -errno : 0;
+}
+
 int
 osdep_iface_mtu_set(int ifindex, uint16_t mtu)
 {
-- 
2.51.0


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

* [PATCH 12/12] test: add test for pcap PMD
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
                   ` (10 preceding siblings ...)
  2026-01-06 18:26 ` [PATCH 11/12] net/pcap: support MAC address set Stephen Hemminger
@ 2026-01-06 18:26 ` Stephen Hemminger
  2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
  12 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-06 18:26 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

This test was generated by Claude AI with some prompting and
pointing at existing ring PMD test. It tests timestamps and
jumbo frame support.

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

diff --git a/app/test/meson.build b/app/test/meson.build
index efec42a6bf..6bc37a2f94 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -141,6 +141,7 @@ source_file_deps = {
     'test_per_lcore.c': [],
     'test_pflock.c': [],
     'test_pie.c': ['sched'],
+    'test_pmd_pcap.c': ['ethdev', 'net', 'bus_vdev']  + packet_burst_generator_deps,
     'test_pmd_perf.c': ['ethdev', 'net'] + packet_burst_generator_deps,
     'test_pmd_ring.c': ['net_ring', 'ethdev', 'bus_vdev'],
     'test_pmd_ring_perf.c': ['ethdev', 'net_ring', 'bus_vdev'],
@@ -216,6 +217,7 @@ source_file_deps = {
 source_file_ext_deps = {
     'test_compressdev.c': ['zlib'],
     'test_pcapng.c': ['pcap'],
+    'test_pmd_pcap.c': ['pcap'],
 }
 
 def_lib = get_option('default_library')
diff --git a/app/test/test_pmd_pcap.c b/app/test/test_pmd_pcap.c
new file mode 100644
index 0000000000..1134404eb8
--- /dev/null
+++ b/app/test/test_pmd_pcap.c
@@ -0,0 +1,1471 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+#include "test.h"
+#include "packet_burst_generator.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <pcap/pcap.h>
+
+#include <rte_ethdev.h>
+#include <rte_bus_vdev.h>
+#include <rte_mbuf.h>
+#include <rte_mbuf_dyn.h>
+#include <rte_mempool.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_udp.h>
+
+#define SOCKET0 0
+#define RING_SIZE 256
+#define NB_MBUF 1024
+#define NUM_PACKETS 64
+#define MAX_PKT_BURST 32
+#define PCAP_SNAPLEN 65535
+
+/* Packet sizes to test */
+#define PKT_SIZE_MIN 60
+#define PKT_SIZE_SMALL 128
+#define PKT_SIZE_MEDIUM 512
+#define PKT_SIZE_LARGE 1024
+#define PKT_SIZE_MTU 1500
+#define PKT_SIZE_JUMBO 9000
+
+static struct rte_mempool *mp;
+
+/* Timestamp dynamic field access */
+static int timestamp_dynfield_offset = -1;
+static uint64_t timestamp_rx_dynflag;
+
+/* Temporary file paths */
+static char tx_pcap_path[PATH_MAX];
+static char rx_pcap_path[PATH_MAX];
+static char infinite_pcap_path[PATH_MAX];
+static char timestamp_pcap_path[PATH_MAX];
+static char varied_pcap_path[PATH_MAX];
+static char jumbo_pcap_path[PATH_MAX];
+
+/* MAC addresses for packet generation */
+static struct rte_ether_addr src_mac;
+static struct rte_ether_addr dst_mac = {
+	.addr_bytes = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }
+};
+
+/* Sample Ethernet/IPv4/UDP packet for testing */
+static const uint8_t test_packet[] = {
+	/* Ethernet header */
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  /* dst MAC (broadcast) */
+	0x00, 0x11, 0x22, 0x33, 0x44, 0x55,  /* src MAC */
+	0x08, 0x00,                          /* EtherType: IPv4 */
+	/* IPv4 header */
+	0x45, 0x00, 0x00, 0x2e,              /* ver, ihl, tos, len */
+	0x00, 0x01, 0x00, 0x00,              /* id, flags, frag */
+	0x40, 0x11, 0x00, 0x00,              /* ttl, proto(UDP), csum */
+	0x0a, 0x00, 0x00, 0x01,              /* src: 10.0.0.1 */
+	0x0a, 0x00, 0x00, 0x02,              /* dst: 10.0.0.2 */
+	/* UDP header */
+	0x04, 0xd2, 0x04, 0xd2,              /* sport, dport (1234) */
+	0x00, 0x1a, 0x00, 0x00,              /* len, csum */
+	/* Payload: "Test packet!" */
+	0x54, 0x65, 0x73, 0x74, 0x20, 0x70,
+	0x61, 0x63, 0x6b, 0x65, 0x74, 0x21
+};
+
+/*
+ * Helper: Get timestamp from mbuf using dynamic field
+ */
+static inline rte_mbuf_timestamp_t
+mbuf_timestamp_get(const struct rte_mbuf *mbuf)
+{
+	return *RTE_MBUF_DYNFIELD(mbuf, timestamp_dynfield_offset,
+				  rte_mbuf_timestamp_t *);
+}
+
+/*
+ * Helper: Check if mbuf has valid timestamp
+ */
+static inline int
+mbuf_has_timestamp(const struct rte_mbuf *mbuf)
+{
+	if (timestamp_dynfield_offset < 0)
+		return 0;
+	return (mbuf->ol_flags & timestamp_rx_dynflag) != 0;
+}
+
+/*
+ * Helper: Initialize timestamp dynamic field access
+ */
+static int
+timestamp_init(void)
+{
+	int offset;
+
+	offset = rte_mbuf_dynfield_lookup(RTE_MBUF_DYNFIELD_TIMESTAMP_NAME,
+					  NULL);
+	if (offset < 0) {
+		printf("Timestamp dynfield not registered\n");
+		return -1;
+	}
+	timestamp_dynfield_offset = offset;
+
+	offset = rte_mbuf_dynflag_lookup(RTE_MBUF_DYNFLAG_RX_TIMESTAMP_NAME,
+					 NULL);
+	if (offset < 0) {
+		printf("Timestamp dynflag not registered\n");
+		return -1;
+	}
+	timestamp_rx_dynflag = RTE_BIT64(offset);
+
+	return 0;
+}
+
+/*
+ * Helper: Create a unique temporary file path
+ */
+static int
+create_temp_path(char *buf, size_t buflen, const char *prefix)
+{
+	int fd;
+
+	snprintf(buf, buflen, "/tmp/%s_XXXXXX.pcap", prefix);
+	fd = mkstemps(buf, 5);  /* 5 = strlen(".pcap") */
+	if (fd < 0)
+		return -1;
+	close(fd);
+	return 0;
+}
+
+/*
+ * Helper: Create a pcap file with test packets using libpcap
+ */
+static int
+create_test_pcap(const char *path, unsigned int num_pkts)
+{
+	pcap_t *pd;
+	pcap_dumper_t *dumper;
+	struct pcap_pkthdr hdr;
+	unsigned int i;
+
+	pd = pcap_open_dead(DLT_EN10MB, PCAP_SNAPLEN);
+	if (pd == NULL) {
+		printf("pcap_open_dead failed\n");
+		return -1;
+	}
+
+	dumper = pcap_dump_open(pd, path);
+	if (dumper == NULL) {
+		printf("pcap_dump_open failed: %s\n", pcap_geterr(pd));
+		pcap_close(pd);
+		return -1;
+	}
+
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.caplen = sizeof(test_packet);
+	hdr.len = sizeof(test_packet);
+
+	for (i = 0; i < num_pkts; i++) {
+		hdr.ts.tv_sec = i;
+		hdr.ts.tv_usec = 0;
+		pcap_dump((u_char *)dumper, &hdr, test_packet);
+	}
+
+	pcap_dump_close(dumper);
+	pcap_close(pd);
+	return 0;
+}
+
+/*
+ * Helper: Create pcap file with packets of specified size
+ */
+static int
+create_sized_pcap(const char *path, unsigned int num_pkts, uint16_t pkt_size)
+{
+	pcap_t *pd;
+	pcap_dumper_t *dumper;
+	struct pcap_pkthdr hdr;
+	uint8_t *pkt_data;
+	unsigned int i;
+
+	/* Minimum valid ethernet frame */
+	if (pkt_size < 60)
+		pkt_size = 60;
+
+	pkt_data = calloc(1, pkt_size);
+	if (pkt_data == NULL)
+		return -1;
+
+	/* Build ethernet header */
+	struct rte_ether_hdr *eth_hdr = (struct rte_ether_hdr *)pkt_data;
+	rte_ether_addr_copy(&src_mac, &eth_hdr->src_addr);
+	rte_ether_addr_copy(&dst_mac, &eth_hdr->dst_addr);
+	eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+	/* Build IP header */
+	struct rte_ipv4_hdr *ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
+	uint16_t ip_len = pkt_size - sizeof(struct rte_ether_hdr);
+	ip_hdr->version_ihl = RTE_IPV4_VHL_DEF;
+	ip_hdr->total_length = rte_cpu_to_be_16(ip_len);
+	ip_hdr->time_to_live = 64;
+	ip_hdr->next_proto_id = IPPROTO_UDP;
+	ip_hdr->src_addr = rte_cpu_to_be_32(IPV4_ADDR(10, 0, 0, 1));
+	ip_hdr->dst_addr = rte_cpu_to_be_32(IPV4_ADDR(10, 0, 0, 2));
+	ip_hdr->hdr_checksum = 0;
+	ip_hdr->hdr_checksum = rte_ipv4_cksum(ip_hdr);
+
+	/* Build UDP header */
+	struct rte_udp_hdr *udp_hdr = (struct rte_udp_hdr *)(ip_hdr + 1);
+	uint16_t udp_len = ip_len - sizeof(struct rte_ipv4_hdr);
+	udp_hdr->src_port = rte_cpu_to_be_16(1234);
+	udp_hdr->dst_port = rte_cpu_to_be_16(1234);
+	udp_hdr->dgram_len = rte_cpu_to_be_16(udp_len);
+	udp_hdr->dgram_cksum = 0;
+
+	/* Fill payload with pattern */
+	uint8_t *payload = (uint8_t *)(udp_hdr + 1);
+	uint16_t payload_len = udp_len - sizeof(struct rte_udp_hdr);
+	for (uint16_t j = 0; j < payload_len; j++)
+		payload[j] = (uint8_t)(j & 0xFF);
+
+	pd = pcap_open_dead(DLT_EN10MB, PCAP_SNAPLEN);
+	if (pd == NULL) {
+		free(pkt_data);
+		return -1;
+	}
+
+	dumper = pcap_dump_open(pd, path);
+	if (dumper == NULL) {
+		pcap_close(pd);
+		free(pkt_data);
+		return -1;
+	}
+
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.caplen = pkt_size;
+	hdr.len = pkt_size;
+
+	for (i = 0; i < num_pkts; i++) {
+		hdr.ts.tv_sec = i;
+		hdr.ts.tv_usec = 0;
+		/* Vary sequence byte in payload */
+		payload[0] = (uint8_t)(i & 0xFF);
+		pcap_dump((u_char *)dumper, &hdr, pkt_data);
+	}
+
+	pcap_dump_close(dumper);
+	pcap_close(pd);
+	free(pkt_data);
+	return 0;
+}
+
+/*
+ * Helper: Create pcap file with varied packet sizes
+ */
+static int
+create_varied_pcap(const char *path, unsigned int num_pkts)
+{
+	static const uint16_t sizes[] = {
+		PKT_SIZE_MIN, PKT_SIZE_SMALL, PKT_SIZE_MEDIUM,
+		PKT_SIZE_LARGE, PKT_SIZE_MTU
+	};
+	pcap_t *pd;
+	pcap_dumper_t *dumper;
+	struct pcap_pkthdr hdr;
+	uint8_t *pkt_data;
+	unsigned int i;
+
+	pkt_data = calloc(1, PKT_SIZE_MTU);
+	if (pkt_data == NULL)
+		return -1;
+
+	pd = pcap_open_dead(DLT_EN10MB, PCAP_SNAPLEN);
+	if (pd == NULL) {
+		free(pkt_data);
+		return -1;
+	}
+
+	dumper = pcap_dump_open(pd, path);
+	if (dumper == NULL) {
+		pcap_close(pd);
+		free(pkt_data);
+		return -1;
+	}
+
+	for (i = 0; i < num_pkts; i++) {
+		uint16_t pkt_size = sizes[i % RTE_DIM(sizes)];
+
+		memset(pkt_data, 0, pkt_size);
+
+		/* Build ethernet header */
+		struct rte_ether_hdr *eth_hdr = (struct rte_ether_hdr *)pkt_data;
+		rte_ether_addr_copy(&src_mac, &eth_hdr->src_addr);
+		rte_ether_addr_copy(&dst_mac, &eth_hdr->dst_addr);
+		eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+		/* Build IP header */
+		struct rte_ipv4_hdr *ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
+		uint16_t ip_len = pkt_size - sizeof(struct rte_ether_hdr);
+		ip_hdr->version_ihl = RTE_IPV4_VHL_DEF;
+		ip_hdr->total_length = rte_cpu_to_be_16(ip_len);
+		ip_hdr->time_to_live = 64;
+		ip_hdr->next_proto_id = IPPROTO_UDP;
+		ip_hdr->src_addr = rte_cpu_to_be_32(IPV4_ADDR(10, 0, 0, 1));
+		ip_hdr->dst_addr = rte_cpu_to_be_32(IPV4_ADDR(10, 0, 0, 2));
+		ip_hdr->hdr_checksum = 0;
+		ip_hdr->hdr_checksum = rte_ipv4_cksum(ip_hdr);
+
+		/* Build UDP header */
+		struct rte_udp_hdr *udp_hdr = (struct rte_udp_hdr *)(ip_hdr + 1);
+		uint16_t udp_len = ip_len - sizeof(struct rte_ipv4_hdr);
+		udp_hdr->src_port = rte_cpu_to_be_16(1234);
+		udp_hdr->dst_port = rte_cpu_to_be_16(1234);
+		udp_hdr->dgram_len = rte_cpu_to_be_16(udp_len);
+
+		memset(&hdr, 0, sizeof(hdr));
+		hdr.ts.tv_sec = i;
+		hdr.caplen = pkt_size;
+		hdr.len = pkt_size;
+
+		pcap_dump((u_char *)dumper, &hdr, pkt_data);
+	}
+
+	pcap_dump_close(dumper);
+	pcap_close(pd);
+	free(pkt_data);
+	return 0;
+}
+
+/*
+ * Helper: Create pcap file with specific timestamps for testing
+ */
+static int
+create_timestamped_pcap(const char *path, unsigned int num_pkts,
+			uint32_t base_sec, uint32_t usec_increment)
+{
+	pcap_t *pd;
+	pcap_dumper_t *dumper;
+	struct pcap_pkthdr hdr;
+	unsigned int i;
+
+	pd = pcap_open_dead_with_tstamp_precision(DLT_EN10MB, PCAP_SNAPLEN,
+						  PCAP_TSTAMP_PRECISION_MICRO);
+	if (pd == NULL)
+		return -1;
+
+	dumper = pcap_dump_open(pd, path);
+	if (dumper == NULL) {
+		pcap_close(pd);
+		return -1;
+	}
+
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.caplen = sizeof(test_packet);
+	hdr.len = sizeof(test_packet);
+
+	for (i = 0; i < num_pkts; i++) {
+		uint64_t total_usec = (uint64_t)i * usec_increment;
+		hdr.ts.tv_sec = base_sec + total_usec / 1000000;
+		hdr.ts.tv_usec = total_usec % 1000000;
+		pcap_dump((u_char *)dumper, &hdr, test_packet);
+	}
+
+	pcap_dump_close(dumper);
+	pcap_close(pd);
+	return 0;
+}
+
+/*
+ * Helper: Count packets in a pcap file using libpcap
+ */
+static int
+count_pcap_packets(const char *path)
+{
+	pcap_t *pd;
+	char errbuf[PCAP_ERRBUF_SIZE];
+	struct pcap_pkthdr *hdr;
+	const u_char *data;
+	int count = 0;
+
+	pd = pcap_open_offline(path, errbuf);
+	if (pd == NULL)
+		return -1;
+
+	while (pcap_next_ex(pd, &hdr, &data) == 1)
+		count++;
+
+	pcap_close(pd);
+	return count;
+}
+
+/*
+ * Helper: Get packet sizes from pcap file
+ */
+static int
+get_pcap_packet_sizes(const char *path, uint16_t *sizes, unsigned int max_pkts)
+{
+	pcap_t *pd;
+	char errbuf[PCAP_ERRBUF_SIZE];
+	struct pcap_pkthdr *hdr;
+	const u_char *data;
+	unsigned int count = 0;
+
+	pd = pcap_open_offline(path, errbuf);
+	if (pd == NULL)
+		return -1;
+
+	while (pcap_next_ex(pd, &hdr, &data) == 1 && count < max_pkts) {
+		sizes[count] = hdr->caplen;
+		count++;
+	}
+
+	pcap_close(pd);
+	return count;
+}
+
+/*
+ * Helper: Configure and start a pcap ethdev port
+ */
+static int
+setup_pcap_port(uint16_t port)
+{
+	struct rte_eth_conf port_conf = {
+		.rxmode.offloads = RTE_ETH_RX_OFFLOAD_TIMESTAMP,
+	};
+	int ret;
+
+	ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+	TEST_ASSERT(ret == 0, "Failed to configure port %u: %s",
+		    port, rte_strerror(-ret));
+
+	ret = rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, mp);
+	TEST_ASSERT(ret == 0, "Failed to setup RX queue on port %u: %s",
+		    port, rte_strerror(-ret));
+
+	ret = rte_eth_tx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL);
+	TEST_ASSERT(ret == 0, "Failed to setup TX queue on port %u: %s",
+		    port, rte_strerror(-ret));
+
+	ret = rte_eth_dev_start(port);
+	TEST_ASSERT(ret == 0, "Failed to start port %u: %s",
+		    port, rte_strerror(-ret));
+
+	return 0;
+}
+
+/*
+ * Helper: Create a pcap vdev and return its port ID
+ */
+static int
+create_pcap_vdev(const char *name, const char *devargs, uint16_t *port_id)
+{
+	int ret;
+
+	ret = rte_vdev_init(name, devargs);
+	TEST_ASSERT(ret == 0, "Failed to create vdev %s: %s",
+		    name, rte_strerror(-ret));
+
+	ret = rte_eth_dev_get_port_by_name(name, port_id);
+	TEST_ASSERT(ret == 0, "Failed to get port ID for %s", name);
+
+	return 0;
+}
+
+/*
+ * Helper: Cleanup a pcap vdev
+ */
+static void
+cleanup_pcap_vdev(const char *name, uint16_t port_id)
+{
+	rte_eth_dev_stop(port_id);
+	rte_vdev_uninit(name);
+}
+
+/*
+ * Helper: Generate test packets using packet_burst_generator
+ */
+static int
+generate_test_packets(struct rte_mempool *pool, struct rte_mbuf **mbufs,
+		      unsigned int count, uint8_t pkt_len)
+{
+	struct rte_ether_hdr eth_hdr;
+	struct rte_ipv4_hdr ip_hdr;
+	struct rte_udp_hdr udp_hdr;
+	uint16_t ip_pkt_data_len;
+	int nb_pkt;
+
+	/* Initialize ethernet header */
+	initialize_eth_header(&eth_hdr, &src_mac, &dst_mac,
+			      RTE_ETHER_TYPE_IPV4, 0, 0);
+
+	/* Calculate IP payload length (total - eth - ip headers) */
+	ip_pkt_data_len = pkt_len - sizeof(struct rte_ether_hdr) -
+			  sizeof(struct rte_ipv4_hdr);
+
+	/* Initialize UDP header */
+	initialize_udp_header(&udp_hdr, 1234, 1234,
+			      ip_pkt_data_len - sizeof(struct rte_udp_hdr));
+
+	/* Initialize IPv4 header */
+	initialize_ipv4_header(&ip_hdr, IPV4_ADDR(10, 0, 0, 1),
+			       IPV4_ADDR(10, 0, 0, 2), ip_pkt_data_len);
+
+	/* Generate packet burst */
+	nb_pkt = generate_packet_burst(pool, mbufs, &eth_hdr, 0,
+				       &ip_hdr, 1, &udp_hdr,
+				       count, pkt_len, 1);
+
+	return nb_pkt;
+}
+
+/*
+ * Helper: Allocate mbufs and fill with test packet data (legacy method)
+ */
+static int
+alloc_test_mbufs(struct rte_mbuf **mbufs, unsigned int count)
+{
+	unsigned int i;
+	int ret;
+
+	ret = rte_pktmbuf_alloc_bulk(mp, mbufs, count);
+	if (ret != 0)
+		return -1;
+
+	for (i = 0; i < count; i++) {
+		rte_memcpy(rte_pktmbuf_mtod(mbufs[i], void *),
+			   test_packet, sizeof(test_packet));
+		mbufs[i]->data_len = sizeof(test_packet);
+		mbufs[i]->pkt_len = sizeof(test_packet);
+	}
+	return 0;
+}
+
+/*
+ * Helper: Allocate a multi-segment mbuf for jumbo frames
+ * Returns the head mbuf with chained segments, or NULL on failure
+ */
+static struct rte_mbuf *
+alloc_jumbo_mbuf(uint32_t pkt_len, uint8_t fill_byte)
+{
+	struct rte_mbuf *head = NULL;
+	struct rte_mbuf **prev = &head;
+	uint32_t remaining = pkt_len;
+	uint16_t nb_segs = 0;
+
+	while (remaining > 0) {
+		struct rte_mbuf *seg = rte_pktmbuf_alloc(mp);
+		uint16_t seg_size;
+
+		if (seg == NULL) {
+			rte_pktmbuf_free(head);
+			return NULL;
+		}
+
+		seg_size = RTE_MIN(remaining, rte_pktmbuf_tailroom(seg));
+		seg->data_len = seg_size;
+
+		/* Fill segment with pattern */
+		memset(rte_pktmbuf_mtod(seg, void *), fill_byte, seg_size);
+
+		*prev = seg;
+		prev = &seg->next;
+		remaining -= seg_size;
+		nb_segs++;
+	}
+
+	if (head != NULL) {
+		head->pkt_len = pkt_len;
+		head->nb_segs = nb_segs;
+	}
+
+	return head;
+}
+
+/*
+ * Helper: Receive packets from port (no retry needed for file-based RX)
+ */
+static int
+receive_packets(uint16_t port, struct rte_mbuf **mbufs,
+		unsigned int max_pkts, unsigned int *received)
+{
+	unsigned int total = 0;
+
+	while (total < max_pkts) {
+		uint16_t nb_rx = rte_eth_rx_burst(port, 0, &mbufs[total], max_pkts - total);
+		if (nb_rx == 0)
+			break;
+		total += nb_rx;
+	}
+	*received = total;
+	return 0;
+}
+
+/*
+ * Helper: Verify mbuf contains expected test packet
+ */
+static int
+verify_packet(struct rte_mbuf *mbuf)
+{
+	TEST_ASSERT_EQUAL(rte_pktmbuf_data_len(mbuf), sizeof(test_packet),
+			  "Packet length mismatch");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(rte_pktmbuf_mtod(mbuf, void *),
+				      test_packet, sizeof(test_packet),
+				      "Packet data mismatch");
+	return 0;
+}
+
+/*
+ * Helper: Check if network interface exists
+ */
+static int
+iface_exists(const char *name)
+{
+	struct ifreq ifr;
+	int sock, ret;
+
+	sock = socket(AF_INET, SOCK_DGRAM, 0);
+	if (sock < 0)
+		return 0;
+
+	memset(&ifr, 0, sizeof(ifr));
+	strlcpy(ifr.ifr_name, name, IFNAMSIZ);
+	ret = ioctl(sock, SIOCGIFINDEX, &ifr);
+	close(sock);
+	return ret == 0;
+}
+
+/*
+ * Helper: Find a usable test interface
+ */
+static const char *
+find_test_iface(void)
+{
+	if (iface_exists("dummy0"))
+		return "dummy0";
+	if (iface_exists("lo"))
+		return "lo";
+	return NULL;
+}
+
+/*
+ * Test: Transmit packets to pcap file
+ */
+static int
+test_tx_to_file(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	char devargs[256];
+	uint16_t port_id;
+	int nb_tx, pkt_count;
+
+	printf("Testing TX to pcap file\n");
+
+	TEST_ASSERT(create_temp_path(tx_pcap_path, sizeof(tx_pcap_path),
+				     "pcap_tx") == 0,
+		    "Failed to create temp file path");
+
+	snprintf(devargs, sizeof(devargs), "tx_pcap=%s", tx_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_tx", devargs, &port_id) == 0,
+		    "Failed to create TX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup TX port");
+	TEST_ASSERT(alloc_test_mbufs(mbufs, NUM_PACKETS) == 0,
+		    "Failed to allocate mbufs");
+
+	nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, NUM_PACKETS);
+	TEST_ASSERT_EQUAL(nb_tx, NUM_PACKETS,
+			  "TX burst failed: sent %d/%d", nb_tx, NUM_PACKETS);
+
+	cleanup_pcap_vdev("net_pcap_tx", port_id);
+
+	pkt_count = count_pcap_packets(tx_pcap_path);
+	TEST_ASSERT_EQUAL(pkt_count, NUM_PACKETS,
+			  "Pcap file has %d packets, expected %d",
+			  pkt_count, NUM_PACKETS);
+
+	printf("TX to file PASSED: %d packets written\n", NUM_PACKETS);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Receive packets from pcap file
+ * Uses output from TX test as input
+ */
+static int
+test_rx_from_file(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int received, i;
+
+	printf("Testing RX from pcap file\n");
+
+	/* Create input file if TX test didn't run */
+	if (access(tx_pcap_path, F_OK) != 0) {
+		TEST_ASSERT(create_temp_path(tx_pcap_path, sizeof(tx_pcap_path),
+					     "pcap_rx_input") == 0,
+			    "Failed to create temp path");
+		TEST_ASSERT(create_test_pcap(tx_pcap_path, NUM_PACKETS) == 0,
+			    "Failed to create input pcap");
+	}
+
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s", tx_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_rx", devargs, &port_id) == 0,
+		    "Failed to create RX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup RX port");
+
+	receive_packets(port_id, mbufs, NUM_PACKETS, &received);
+	TEST_ASSERT_EQUAL(received, NUM_PACKETS,
+			  "Received %u packets, expected %d", received, NUM_PACKETS);
+
+	for (i = 0; i < received; i++) {
+		TEST_ASSERT(verify_packet(mbufs[i]) == 0,
+			    "Packet %u verification failed", i);
+	}
+	rte_pktmbuf_free_bulk(mbufs, received);
+
+	cleanup_pcap_vdev("net_pcap_rx", port_id);
+
+	printf("RX from file PASSED: %u packets verified\n", received);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: TX with varied packet sizes using packet_burst_generator
+ */
+static int
+test_tx_varied_sizes(void)
+{
+	static const uint8_t test_sizes[] = {
+		PKT_SIZE_MIN, PKT_SIZE_SMALL, PACKET_BURST_GEN_PKT_LEN_128
+	};
+	char tx_path[PATH_MAX];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int i;
+
+	printf("Testing TX with varied packet sizes\n");
+
+	TEST_ASSERT(create_temp_path(tx_path, sizeof(tx_path),
+				     "pcap_tx_varied") == 0,
+		    "Failed to create temp file path");
+
+	snprintf(devargs, sizeof(devargs), "tx_pcap=%s", tx_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_tx_var", devargs, &port_id) == 0,
+		    "Failed to create TX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup TX port");
+
+	for (i = 0; i < RTE_DIM(test_sizes); i++) {
+		struct rte_mbuf *mbufs[MAX_PKT_BURST];
+		int nb_pkt, nb_tx;
+
+		nb_pkt = generate_test_packets(mp, mbufs, MAX_PKT_BURST,
+					       test_sizes[i]);
+		TEST_ASSERT(nb_pkt > 0,
+			    "Failed to generate packets of size %u",
+			    test_sizes[i]);
+
+		nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, nb_pkt);
+		if (nb_tx < nb_pkt)
+			rte_pktmbuf_free_bulk(&mbufs[nb_tx], nb_pkt - nb_tx);
+
+		printf("  Size %u: generated %d, transmitted %d\n",
+		       test_sizes[i], nb_pkt, nb_tx);
+		TEST_ASSERT(nb_tx > 0, "Failed to TX packets of size %u",
+			    test_sizes[i]);
+	}
+
+	cleanup_pcap_vdev("net_pcap_tx_var", port_id);
+	unlink(tx_path);
+
+	printf("TX varied sizes PASSED\n");
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: RX with varied packet sizes
+ */
+static int
+test_rx_varied_sizes(void)
+{
+	static const uint16_t expected_sizes[] = {
+		PKT_SIZE_MIN, PKT_SIZE_SMALL, PKT_SIZE_MEDIUM,
+		PKT_SIZE_LARGE, PKT_SIZE_MTU
+	};
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	uint16_t rx_sizes[NUM_PACKETS];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int received, i;
+
+	printf("Testing RX with varied packet sizes\n");
+
+	TEST_ASSERT(create_temp_path(varied_pcap_path, sizeof(varied_pcap_path),
+				     "pcap_varied") == 0,
+		    "Failed to create temp path");
+	TEST_ASSERT(create_varied_pcap(varied_pcap_path, NUM_PACKETS) == 0,
+		    "Failed to create varied pcap");
+
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s", varied_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_var", devargs, &port_id) == 0,
+		    "Failed to create varied RX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup varied RX port");
+
+	receive_packets(port_id, mbufs, NUM_PACKETS, &received);
+	TEST_ASSERT_EQUAL(received, NUM_PACKETS,
+			  "Received %u packets, expected %d", received, NUM_PACKETS);
+
+	/* Verify packet sizes match expected pattern */
+	for (i = 0; i < received; i++) {
+		uint16_t expected = expected_sizes[i % RTE_DIM(expected_sizes)];
+		rx_sizes[i] = rte_pktmbuf_pkt_len(mbufs[i]);
+		TEST_ASSERT_EQUAL(rx_sizes[i], expected,
+				  "Packet %u: size %u, expected %u",
+				  i, rx_sizes[i], expected);
+	}
+
+	rte_pktmbuf_free_bulk(mbufs, received);
+	cleanup_pcap_vdev("net_pcap_var", port_id);
+
+	printf("RX varied sizes PASSED: %u packets with correct sizes\n", received);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Infinite RX mode - loops through pcap file continuously
+ */
+static int
+test_infinite_rx(void)
+{
+	struct rte_mbuf *mbufs[MAX_PKT_BURST];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int total_rx = 0;
+	int iter, attempts;
+
+	printf("Testing infinite RX mode\n");
+
+	TEST_ASSERT(create_temp_path(infinite_pcap_path, sizeof(infinite_pcap_path),
+				     "pcap_inf") == 0,
+		    "Failed to create temp path");
+	TEST_ASSERT(create_test_pcap(infinite_pcap_path, NUM_PACKETS) == 0,
+		    "Failed to create input pcap");
+
+	snprintf(devargs, sizeof(devargs),
+		 "rx_pcap=%s,infinite_rx=1", infinite_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_inf", devargs, &port_id) == 0,
+		    "Failed to create infinite RX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup infinite RX port");
+
+	/* Read more packets than file contains to verify looping */
+	for (iter = 0; iter < 3 && total_rx < NUM_PACKETS * 2; iter++) {
+		for (attempts = 0; attempts < 100 && total_rx < NUM_PACKETS * 2;
+		     attempts++) {
+			uint16_t nb_rx = rte_eth_rx_burst(port_id, 0, mbufs,
+							  MAX_PKT_BURST);
+			if (nb_rx > 0)
+				rte_pktmbuf_free_bulk(mbufs, nb_rx);
+			total_rx += nb_rx;
+			if (nb_rx == 0)
+				usleep(100);
+		}
+	}
+
+	cleanup_pcap_vdev("net_pcap_inf", port_id);
+
+	TEST_ASSERT(total_rx >= NUM_PACKETS * 2,
+		    "Infinite RX: got %u packets, need >= %d",
+		    total_rx, NUM_PACKETS * 2);
+
+	printf("Infinite RX PASSED: %u packets (file has %d)\n",
+	       total_rx, NUM_PACKETS);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: TX drop mode - packets dropped when no tx_pcap specified
+ */
+static int
+test_tx_drop(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	struct rte_eth_stats stats;
+	char devargs[256];
+	uint16_t port_id;
+	int nb_tx;
+
+	printf("Testing TX drop mode\n");
+
+	TEST_ASSERT(create_temp_path(rx_pcap_path, sizeof(rx_pcap_path),
+				     "pcap_drop") == 0,
+		    "Failed to create temp path");
+	TEST_ASSERT(create_test_pcap(rx_pcap_path, NUM_PACKETS) == 0,
+		    "Failed to create input pcap");
+
+	/* Only rx_pcap - TX should silently drop */
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s", rx_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_drop", devargs, &port_id) == 0,
+		    "Failed to create drop vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup drop port");
+	TEST_ASSERT(alloc_test_mbufs(mbufs, NUM_PACKETS) == 0,
+		    "Failed to allocate mbufs");
+
+	TEST_ASSERT(rte_eth_stats_reset(port_id) == 0,
+		    "Failed to reset stats");
+	nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, NUM_PACKETS);
+
+	/* Packets should be accepted even in drop mode */
+	TEST_ASSERT_EQUAL(nb_tx, NUM_PACKETS,
+			  "Drop mode TX: %d/%d accepted", nb_tx, NUM_PACKETS);
+
+	TEST_ASSERT(rte_eth_stats_get(port_id, &stats) == 0,
+		    "Failed to get stats");
+	cleanup_pcap_vdev("net_pcap_drop", port_id);
+
+	printf("TX drop PASSED: %d packets dropped, opackets=%" PRIu64"\n",
+	       nb_tx, stats.opackets);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Statistics accuracy and reset
+ */
+static int
+test_stats(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	struct rte_eth_stats stats;
+	char devargs[256];
+	char stats_tx_path[PATH_MAX];
+	uint16_t port_id;
+	unsigned int received;
+	int nb_tx;
+
+	printf("Testing statistics accuracy\n");
+
+	TEST_ASSERT(create_temp_path(rx_pcap_path, sizeof(rx_pcap_path),
+				     "pcap_stats_rx") == 0,
+		    "Failed to create RX temp path");
+	TEST_ASSERT(create_temp_path(stats_tx_path, sizeof(stats_tx_path),
+				     "pcap_stats_tx") == 0,
+		    "Failed to create TX temp path");
+	TEST_ASSERT(create_test_pcap(rx_pcap_path, NUM_PACKETS) == 0,
+		    "Failed to create input pcap");
+
+	snprintf(devargs, sizeof(devargs),
+		 "rx_pcap=%s,tx_pcap=%s", rx_pcap_path, stats_tx_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_stats", devargs, &port_id) == 0,
+		    "Failed to create stats vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup stats port");
+
+	/* Verify stats start at zero */
+	TEST_ASSERT(rte_eth_stats_reset(port_id) == 0,
+		    "Failed to reset stats");
+	TEST_ASSERT(rte_eth_stats_get(port_id, &stats) == 0,
+		    "Failed to get stats");
+	TEST_ASSERT(stats.ipackets == 0 && stats.opackets == 0 &&
+		    stats.ibytes == 0 && stats.obytes == 0,
+		    "Initial stats not zero");
+
+	/* RX and verify stats */
+	receive_packets(port_id, mbufs, NUM_PACKETS, &received);
+	TEST_ASSERT(rte_eth_stats_get(port_id, &stats) == 0,
+		    "Failed to get stats after RX");
+	TEST_ASSERT_EQUAL(stats.ipackets, received,
+			  "RX stats: ipackets=%"PRIu64", received=%u",
+			  stats.ipackets, received);
+
+	/* TX and verify stats */
+	nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, received);
+	TEST_ASSERT(rte_eth_stats_get(port_id, &stats) == 0,
+		    "Failed to get stats after TX");
+	TEST_ASSERT_EQUAL(stats.opackets, (uint64_t)nb_tx,
+			  "TX stats: opackets=%"PRIu64", sent=%u",
+			  stats.opackets, nb_tx);
+
+	/* Verify stats reset */
+	TEST_ASSERT(rte_eth_stats_reset(port_id) == 0,
+		    "Failed to reset stats");
+	TEST_ASSERT(rte_eth_stats_get(port_id, &stats) == 0,
+		    "Failed to get stats after reset");
+	TEST_ASSERT(stats.ipackets == 0 && stats.opackets == 0,
+		    "Stats not reset to zero");
+
+	cleanup_pcap_vdev("net_pcap_stats", port_id);
+	unlink(stats_tx_path);
+
+	printf("Statistics PASSED: RX=%u, TX=%d\n", received, nb_tx);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: MTU configuration
+ */
+static int
+test_set_mtu(void)
+{
+	char devargs[256];
+	char mtu_tx_path[PATH_MAX];
+	uint16_t port_id;
+	static const uint16_t mtu_values[] = {1500, 9000, 1280};
+	int ret = 0;
+	size_t i;
+
+	printf("Testing MTU configuration\n");
+
+	TEST_ASSERT(create_temp_path(rx_pcap_path, sizeof(rx_pcap_path),
+				     "pcap_mtu_rx") == 0,
+		    "Failed to create RX temp path");
+	TEST_ASSERT(create_temp_path(mtu_tx_path, sizeof(mtu_tx_path),
+				     "pcap_mtu_tx") == 0,
+		    "Failed to create TX temp path");
+	TEST_ASSERT(create_test_pcap(rx_pcap_path, 1) == 0,
+		    "Failed to create input pcap");
+
+	snprintf(devargs, sizeof(devargs),
+		 "rx_pcap=%s,tx_pcap=%s", rx_pcap_path, mtu_tx_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_mtu", devargs, &port_id) == 0,
+		    "Failed to create MTU vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup MTU port");
+
+	for (i = 0; i < RTE_DIM(mtu_values); i++) {
+		uint16_t mtu;
+
+		ret = rte_eth_dev_set_mtu(port_id, mtu_values[i]);
+		if (ret != 0)
+			break;
+
+		TEST_ASSERT(rte_eth_dev_get_mtu(port_id, &mtu) == 0, "Failed to get MTU");
+		TEST_ASSERT_EQUAL(mtu, mtu_values[i], "MTU set mismatch for %u", mtu_values[i]);
+	}
+
+	cleanup_pcap_vdev("net_pcap_mtu", port_id);
+	unlink(mtu_tx_path);
+
+	if (ret == 0) {
+		printf("MTU test completed\n");
+		return TEST_SUCCESS;
+	}
+
+	if (ret == -ENOTSUP) {
+		printf("MTU set not supported\n");
+		return TEST_SKIPPED;
+	}
+
+	printf("Failed to set MTU: %s", strerror(-ret));
+	return TEST_FAILED;
+}
+
+/*
+ * Test: Jumbo frame RX (multi-segment mbufs)
+ */
+static int
+test_jumbo_rx(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int received, i;
+	const unsigned int num_jumbo = 16;
+
+	printf("Testing jumbo frame RX (%u byte packets, multi-segment)\n",
+	       PKT_SIZE_JUMBO);
+
+	TEST_ASSERT(create_temp_path(jumbo_pcap_path, sizeof(jumbo_pcap_path),
+				     "pcap_jumbo") == 0,
+		    "Failed to create temp path");
+	TEST_ASSERT(create_sized_pcap(jumbo_pcap_path, num_jumbo,
+				      PKT_SIZE_JUMBO) == 0,
+		    "Failed to create jumbo pcap");
+
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s", jumbo_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_jumbo", devargs, &port_id) == 0,
+		    "Failed to create jumbo RX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup jumbo RX port");
+
+	receive_packets(port_id, mbufs, num_jumbo, &received);
+	TEST_ASSERT_EQUAL(received, num_jumbo,
+			  "Received %u packets, expected %u", received, num_jumbo);
+
+	/* Verify all packets are jumbo size (may be multi-segment) */
+	for (i = 0; i < received; i++) {
+		uint32_t pkt_len = rte_pktmbuf_pkt_len(mbufs[i]);
+		uint16_t nb_segs = mbufs[i]->nb_segs;
+
+		TEST_ASSERT_EQUAL(pkt_len, PKT_SIZE_JUMBO,
+				  "Packet %u: size %u, expected %u",
+				  i, pkt_len, PKT_SIZE_JUMBO);
+
+		/* Jumbo frames should use multiple segments */
+		if (nb_segs > 1)
+			printf("  Packet %u: %u segments\n", i, nb_segs);
+	}
+
+	rte_pktmbuf_free_bulk(mbufs, received);
+	cleanup_pcap_vdev("net_pcap_jumbo", port_id);
+
+	printf("Jumbo RX PASSED: %u jumbo packets received\n", received);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Jumbo frame TX (multi-segment mbufs)
+ */
+static int
+test_jumbo_tx(void)
+{
+	struct rte_mbuf *mbufs[MAX_PKT_BURST];
+	char tx_path[PATH_MAX];
+	char devargs[256];
+	uint16_t port_id;
+	uint16_t sizes[MAX_PKT_BURST];
+	int nb_tx, pkt_count, ret;
+	unsigned int i;
+	const unsigned int num_jumbo = 8;
+
+	printf("Testing jumbo frame TX (multi-segment mbufs)\n");
+
+	TEST_ASSERT(create_temp_path(tx_path, sizeof(tx_path),
+				     "pcap_jumbo_tx") == 0,
+		    "Failed to create temp file path");
+
+	snprintf(devargs, sizeof(devargs), "tx_pcap=%s", tx_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_jumbo_tx", devargs, &port_id) == 0,
+		    "Failed to create TX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup TX port");
+
+	/* Set MTU to allow jumbo frames - PMD drops packets exceeding MTU */
+	ret = rte_eth_dev_set_mtu(port_id, PKT_SIZE_JUMBO);
+	if (ret != 0) {
+		printf("Failed to set MTU to %u: %s\n",
+		       PKT_SIZE_JUMBO, rte_strerror(-ret));
+		cleanup_pcap_vdev("net_pcap_jumbo_tx", port_id);
+		unlink(tx_path);
+		return TEST_SKIPPED;
+	}
+
+	/* Allocate multi-segment mbufs for jumbo frames */
+	for (i = 0; i < num_jumbo; i++) {
+		mbufs[i] = alloc_jumbo_mbuf(PKT_SIZE_JUMBO, (uint8_t)(i & 0xFF));
+		if (mbufs[i] == NULL) {
+			/* Free already allocated mbufs */
+			while (i > 0)
+				rte_pktmbuf_free(mbufs[--i]);
+			cleanup_pcap_vdev("net_pcap_jumbo_tx", port_id);
+			unlink(tx_path);
+			return TEST_FAILED;
+		}
+		printf("  Packet %u: %u segments for %u bytes\n",
+		       i, mbufs[i]->nb_segs, PKT_SIZE_JUMBO);
+	}
+
+	nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, num_jumbo);
+	/* Free any unsent mbufs */
+	for (i = nb_tx; i < num_jumbo; i++)
+		rte_pktmbuf_free(mbufs[i]);
+
+	TEST_ASSERT_EQUAL(nb_tx, (int)num_jumbo,
+			  "TX burst failed: sent %d/%u", nb_tx, num_jumbo);
+
+	cleanup_pcap_vdev("net_pcap_jumbo_tx", port_id);
+
+	/* Verify pcap file has correct packet count and sizes */
+	pkt_count = get_pcap_packet_sizes(tx_path, sizes, MAX_PKT_BURST);
+	TEST_ASSERT_EQUAL(pkt_count, (int)num_jumbo,
+			  "Pcap file has %d packets, expected %u",
+			  pkt_count, num_jumbo);
+
+	for (i = 0; i < (unsigned int)pkt_count; i++) {
+		TEST_ASSERT_EQUAL(sizes[i], PKT_SIZE_JUMBO,
+				  "Packet %u: size %u, expected %u",
+				  i, sizes[i], PKT_SIZE_JUMBO);
+	}
+
+	unlink(tx_path);
+
+	printf("Jumbo TX PASSED: %d jumbo packets written\n", nb_tx);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Layering on Linux network interface
+ */
+static int
+test_iface(void)
+{
+	struct rte_mbuf *mbufs[MAX_PKT_BURST];
+	struct rte_eth_dev_info dev_info;
+	char devargs[256];
+	uint16_t port_id;
+	const char *iface;
+	int ret, nb_tx, nb_pkt;
+
+	printf("Testing pcap on network interface\n");
+
+	iface = find_test_iface();
+	if (iface == NULL) {
+		printf("No suitable interface, skipping\n");
+		return TEST_SKIPPED;
+	}
+	printf("Using interface: %s\n", iface);
+
+	snprintf(devargs, sizeof(devargs), "iface=%s", iface);
+	if (rte_vdev_init("net_pcap_iface", devargs) < 0) {
+		printf("Cannot create iface vdev (needs root?), skipping\n");
+		return TEST_SKIPPED;
+	}
+
+	TEST_ASSERT(rte_eth_dev_get_port_by_name("net_pcap_iface",
+						 &port_id) == 0,
+		    "Failed to get iface port ID");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup iface port");
+
+	ret = rte_eth_dev_info_get(port_id, &dev_info);
+	TEST_ASSERT(ret == 0, "Failed to get dev info: %s", rte_strerror(-ret));
+
+	printf("Driver: %s, max_rx_queues=%u, max_tx_queues=%u\n",
+	       dev_info.driver_name, dev_info.max_rx_queues,
+	       dev_info.max_tx_queues);
+
+	/* Use packet_burst_generator for interface test */
+	nb_pkt = generate_test_packets(mp, mbufs, MAX_PKT_BURST,
+				       PACKET_BURST_GEN_PKT_LEN);
+	TEST_ASSERT(nb_pkt > 0, "Failed to generate packets");
+
+	nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, nb_pkt);
+	if (nb_tx < nb_pkt)
+		rte_pktmbuf_free_bulk(&mbufs[nb_tx], nb_pkt - nb_tx);
+
+	cleanup_pcap_vdev("net_pcap_iface", port_id);
+
+	printf("Interface test PASSED: sent %d packets\n", nb_tx);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: MAC address with phy_mac option
+ */
+static int
+test_mac_address(void)
+{
+	struct rte_ether_addr orig_mac, new_mac, read_mac;
+	char devargs[256];
+	uint16_t port_id;
+	const char *iface;
+	int ret;
+
+	printf("Testing MAC address configuration\n");
+
+	iface = find_test_iface();
+	if (iface == NULL) {
+		printf("No suitable interface, skipping\n");
+		return TEST_SKIPPED;
+	}
+
+	if (strcmp(iface, "lo") == 0) {
+		printf("Need dummy interface to test setting mac address\n");
+		return TEST_SKIPPED;
+	}
+
+	snprintf(devargs, sizeof(devargs), "iface=%s,phy_mac=1", iface);
+	if (rte_vdev_init("net_pcap_mac", devargs) < 0) {
+		printf("Cannot create mac vdev (needs root?), skipping\n");
+		return TEST_SKIPPED;
+	}
+
+	TEST_ASSERT(rte_eth_dev_get_port_by_name("net_pcap_mac", &port_id) == 0,
+		    "Failed to get mac port ID");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup mac port");
+
+	ret = rte_eth_macaddr_get(port_id, &orig_mac);
+	TEST_ASSERT(ret == 0, "Failed to get original MAC");
+	printf("Original MAC: " RTE_ETHER_ADDR_PRT_FMT "\n",
+	       RTE_ETHER_ADDR_BYTES(&orig_mac));
+
+	/* Try to set a new MAC */
+	rte_eth_random_addr(new_mac.addr_bytes);
+
+	ret = rte_eth_dev_default_mac_addr_set(port_id, &new_mac);
+	if (ret == 0) {
+		rte_eth_macaddr_get(port_id, &read_mac);
+		printf("New MAC: " RTE_ETHER_ADDR_PRT_FMT "\n",
+		       RTE_ETHER_ADDR_BYTES(&read_mac));
+		/* Restore original */
+		rte_eth_dev_default_mac_addr_set(port_id, &orig_mac);
+		ret = TEST_SUCCESS;
+	} else if (ret == -ENOTSUP) {
+		printf("MAC change not supported\n");
+		ret = TEST_SKIPPED;
+	} else {
+		printf("MAC change failed: %s\n", rte_strerror(-ret));
+		ret = TEST_FAILED;
+	}
+
+	cleanup_pcap_vdev("net_pcap_mac", port_id);
+
+	printf("MAC address test completed\n");
+	return ret;
+}
+
+/*
+ * Test: Verify receive timestamps from pcap file
+ */
+static int
+test_rx_timestamp(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int received, i;
+	const uint32_t base_sec = 1000;
+	const uint32_t usec_increment = 10000; /* 10ms between packets */
+	rte_mbuf_timestamp_t prev_ts = 0;
+
+	printf("Testing RX timestamp accuracy\n");
+
+	TEST_ASSERT(create_temp_path(timestamp_pcap_path, sizeof(timestamp_pcap_path),
+				     "pcap_ts") == 0,
+		    "Failed to create temp path");
+	TEST_ASSERT(create_timestamped_pcap(timestamp_pcap_path, NUM_PACKETS,
+					    base_sec, usec_increment) == 0,
+		    "Failed to create timestamped pcap");
+
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s", timestamp_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_ts", devargs, &port_id) == 0,
+		    "Failed to create timestamp vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup timestamp port");
+
+	/* Try to initialize timestamp dynamic field access */
+	TEST_ASSERT(timestamp_init() == 0, "Timestamp dynfield not available");
+
+	receive_packets(port_id, mbufs, NUM_PACKETS, &received);
+	TEST_ASSERT_EQUAL(received, NUM_PACKETS,
+			  "Received %u packets, expected %d", received, NUM_PACKETS);
+
+	/* Check if first packet has timestamp flag set */
+	if (!mbuf_has_timestamp(mbufs[0])) {
+		printf("Timestamps not enabled in mbufs, skipping validation\n");
+		rte_pktmbuf_free_bulk(mbufs, received);
+		cleanup_pcap_vdev("net_pcap_ts", port_id);
+		return TEST_SUCCESS;
+	}
+
+	for (i = 0; i < received; i++) {
+		struct rte_mbuf *m = mbufs[i];
+
+		TEST_ASSERT(mbuf_has_timestamp(m),
+			    "Packet %u missing timestamp flag", i);
+
+		/* PCAP PMD stores timestamp in nanoseconds */
+		rte_mbuf_timestamp_t ts = mbuf_timestamp_get(mbufs[i]);
+		uint64_t expected = (uint64_t)base_sec * NS_PER_S
+			+ (uint64_t)i * usec_increment * 1000;
+
+		if (ts != expected)
+			printf("Packet %u: timestamp mismatch, expected=%"PRIu64" actual=%"PRIu64"\n",
+			       i, expected, ts);
+
+		/* Verify monotonically increasing timestamps */
+		if (i > 0) {
+			TEST_ASSERT(ts >= prev_ts,
+				    "Packet %u: timestamp not monotonic (prev=%"PRIu64", curr=%"PRIu64")",
+				    i, prev_ts, ts);
+		}
+		prev_ts = ts;
+	}
+
+	rte_pktmbuf_free_bulk(mbufs, received);
+	cleanup_pcap_vdev("net_pcap_ts", port_id);
+
+	printf("RX timestamp PASSED: %u packets with valid timestamps\n", received);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test suite setup
+ */
+static int
+test_setup(void)
+{
+	/* Generate random source MAC address */
+	rte_eth_random_addr(src_mac.addr_bytes);
+
+	mp = rte_pktmbuf_pool_create("pcap_test_pool", NB_MBUF, 32, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE,
+				     rte_socket_id());
+	TEST_ASSERT_NOT_NULL(mp, "Failed to create mempool");
+
+	return 0;
+}
+
+/*
+ * Test suite teardown
+ */
+static void
+test_teardown(void)
+{
+	/* Cleanup temp files */
+	if (tx_pcap_path[0] != '\0')
+		unlink(tx_pcap_path);
+	if (rx_pcap_path[0] != '\0')
+		unlink(rx_pcap_path);
+	if (infinite_pcap_path[0] != '\0')
+		unlink(infinite_pcap_path);
+	if (timestamp_pcap_path[0] != '\0')
+		unlink(timestamp_pcap_path);
+	if (varied_pcap_path[0] != '\0')
+		unlink(varied_pcap_path);
+	if (jumbo_pcap_path[0] != '\0')
+		unlink(jumbo_pcap_path);
+
+	rte_mempool_free(mp);
+	mp = NULL;
+}
+
+static struct unit_test_suite test_pmd_pcap_suite = {
+	.setup = test_setup,
+	.teardown = test_teardown,
+	.suite_name = "PCAP PMD Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_tx_to_file),
+		TEST_CASE(test_rx_from_file),
+		TEST_CASE(test_tx_varied_sizes),
+		TEST_CASE(test_rx_varied_sizes),
+		TEST_CASE(test_jumbo_rx),
+		TEST_CASE(test_jumbo_tx),
+		TEST_CASE(test_infinite_rx),
+		TEST_CASE(test_tx_drop),
+		TEST_CASE(test_stats),
+		TEST_CASE(test_set_mtu),
+		TEST_CASE(test_iface),
+		TEST_CASE(test_mac_address),
+		TEST_CASE(test_rx_timestamp),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_pmd_pcap(void)
+{
+	return unit_test_suite_runner(&test_pmd_pcap_suite);
+}
+
+REGISTER_FAST_TEST(pcap_pmd_autotest, true, true, test_pmd_pcap);
-- 
2.51.0


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

* RE: [PATCH 06/12] net/pcap: remove global variables
  2026-01-06 18:26 ` [PATCH 06/12] net/pcap: remove global variables Stephen Hemminger
@ 2026-01-07  9:48   ` Marat Khalili
  0 siblings, 0 replies; 28+ messages in thread
From: Marat Khalili @ 2026-01-07  9:48 UTC (permalink / raw)
  To: Stephen Hemminger, dev

> -----Original Message-----
> From: Stephen Hemminger <stephen@networkplumber.org>
> Sent: Tuesday 6 January 2026 18:27
> To: dev@dpdk.org
> Cc: Stephen Hemminger <stephen@networkplumber.org>
> Subject: [PATCH 06/12] net/pcap: remove global variables
> 
> Localize variables where possible.
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---
>  drivers/net/pcap/pcap_ethdev.c | 8 ++++++--
>  1 file changed, 6 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
> index 905ca62bd4..1658685a28 100644
> --- a/drivers/net/pcap/pcap_ethdev.c
> +++ b/drivers/net/pcap/pcap_ethdev.c
> @@ -39,11 +39,9 @@
> 
>  #define RTE_PMD_PCAP_MAX_QUEUES 16
> 
> -static char errbuf[PCAP_ERRBUF_SIZE];
>  static struct timespec start_time;
>  static uint64_t start_cycles;
>  static uint64_t hz;
> -static uint8_t iface_idx;
> 
>  static uint64_t timestamp_rx_dynflag;
>  static int timestamp_dynfield_offset = -1;
> @@ -526,6 +524,8 @@ eth_pcap_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
>  static inline int
>  open_iface_live(const char *iface, pcap_t **pcap)
>  {
> +	char errbuf[PCAP_ERRBUF_SIZE];
> +
>  	pcap_t *pc = pcap_create(iface, errbuf);
>  	if (pc == NULL) {
>  		PMD_LOG(ERR, "Couldn't create %s: %s", iface, errbuf);
> @@ -621,6 +621,8 @@ open_single_tx_pcap(const char *pcap_filename, pcap_dumper_t **dumper)
>  static int
>  open_single_rx_pcap(const char *pcap_filename, pcap_t **pcap)
>  {
> +	char errbuf[PCAP_ERRBUF_SIZE];
> +
>  	*pcap = pcap_open_offline_with_tstamp_precision(pcap_filename,
>  							PCAP_TSTAMP_PRECISION_NANO, errbuf);
>  	if (*pcap == NULL) {
> @@ -1314,11 +1316,13 @@ pmd_init_internals(struct rte_vdev_device *vdev,
>  	 * - and point eth_dev structure to new eth_dev_data structure
>  	 */
>  	*internals = (*eth_dev)->data->dev_private;
> +
>  	/*
>  	 * Interface MAC = 02:70:63:61:70:<iface_idx>
>  	 * derived from: 'locally administered':'p':'c':'a':'p':'iface_idx'
>  	 * where the middle 4 characters are converted to hex.
>  	 */
> +	static uint8_t iface_idx;
>  	(*internals)->eth_addr = (struct rte_ether_addr) {
>  		.addr_bytes = { 0x02, 0x70, 0x63, 0x61, 0x70, iface_idx++ }
>  	};
> --
> 2.51.0

Acked-by: Marat Khalili <marat.khalili@huawei.com>

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

* RE: [PATCH 03/12] net/pcap: use bool for flags
  2026-01-06 18:26 ` [PATCH 03/12] net/pcap: use bool for flags Stephen Hemminger
@ 2026-01-07 10:28   ` Marat Khalili
  2026-01-09  0:23     ` Stephen Hemminger
  0 siblings, 1 reply; 28+ messages in thread
From: Marat Khalili @ 2026-01-07 10:28 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev

> -----Original Message-----
> From: Stephen Hemminger <stephen@networkplumber.org>
> Sent: Tuesday 6 January 2026 18:27
> To: dev@dpdk.org
> Cc: Stephen Hemminger <stephen@networkplumber.org>
> Subject: [PATCH 03/12] net/pcap: use bool for flags
> 
> Save some space by using bool for flag values.
> Use common code to parse value of a boolean flag.

Note that there might technically be some performance cost of this. Said that, 
since original code did not leave any comments why ints were used, let's think 
it was not on purpose, while bools are cleaner and less error-prone.

I however think that proper bool conversion should use true or false on 
assignment/initialization, not 1 or 0.

// snip

> @@ -1180,31 +1181,29 @@ open_tx_iface(const char *key, const char *value, void *extra_args)
>  }
> 
>  static int
> -select_phy_mac(const char *key __rte_unused, const char *value,
> -		void *extra_args)
> +process_bool_flag(const char *key, const char *value, void *extra_args)

This function probably belongs in some common library. There is a similar code 
in lib/argparse/rte_argparse.c and lib/cmdline/cmdline_parse_bool.c that might 
benefit. Having some tests could also be useful.

I would also think of naming more, word "process" does not really convey much 
meaning. How about "parse_bool"?

>  {
> -	if (extra_args) {
> -		const int phy_mac = atoi(value);
> -		int *enable_phy_mac = extra_args;
> -
> -		if (phy_mac)
> -			*enable_phy_mac = 1;
> -	}
> -	return 0;
> -}
> +	bool *flag = extra_args;
> +	/* table of possible representation of boolean */
> +	static const char * const values[] = {
> +		"false", "true",
> +		"0", "1",
> +		"disable", "enable",
> +		"on", "off",

Last pair is swapped.

> +	};
> 
> -static int
> -get_infinite_rx_arg(const char *key __rte_unused,
> -		const char *value, void *extra_args)
> -{
> -	if (extra_args) {
> -		const int infinite_rx = atoi(value);
> -		int *enable_infinite_rx = extra_args;
> +	for (unsigned int i = 0; i < RTE_DIM(values); i++) {
> +		if (strcmp(value, values[i]) == 0) {

Maybe strcasecmp (just suggesting)?

> +			*flag = !!(i & 1);
> 
> -		if (infinite_rx > 0)
> -			*enable_infinite_rx = 1;
> +			PMD_LOG(INFO, "%s set to %s",
> +				key, *flag ? "true" : "false");

Should we have a common use {"false", "true"} inline function somewhere? 
Logging of infinite_rx in the same file could use it, as well as a few other 
places.

> +			return 0;
> +		}
>  	}
> -	return 0;
> +
> +	PMD_LOG(ERR, "Invalid '%s' value '%s'", key, value);
> +	return -1;
>  }

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

* RE: [PATCH 07/12] net/pcap: avoid use of volatile
  2026-01-06 18:26 ` [PATCH 07/12] net/pcap: avoid use of volatile Stephen Hemminger
@ 2026-01-07 10:31   ` Marat Khalili
  0 siblings, 0 replies; 28+ messages in thread
From: Marat Khalili @ 2026-01-07 10:31 UTC (permalink / raw)
  To: Stephen Hemminger, dev

> Using volatile for statistics generates expensive atomic rmw
> operations when not necessary.

I am not sure it is true, AFAIK volatile only affects the compiler. In this 
case it mostly guards against these variables being completely cached in 
registers. Your new version is actually more correct, but also potentially 
slower since general fence affects all reads or writes, not just counters in 
question. I think it is common among drivers to not guarantee visibility of 
the most recent counter values for the sake of performance.

> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---
>  drivers/net/pcap/pcap_ethdev.c | 38 ++++++++++++++++++++++++++--------
>  1 file changed, 29 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
> index 1658685a28..175d6998f9 100644
> --- a/drivers/net/pcap/pcap_ethdev.c
> +++ b/drivers/net/pcap/pcap_ethdev.c
> @@ -47,10 +47,10 @@ static uint64_t timestamp_rx_dynflag;
>  static int timestamp_dynfield_offset = -1;
> 
>  struct queue_stat {
> -	volatile unsigned long pkts;
> -	volatile unsigned long bytes;
> -	volatile unsigned long err_pkts;
> -	volatile unsigned long rx_nombuf;
> +	uint64_t pkts;
> +	uint64_t bytes;
> +	uint64_t err_pkts;
> +	uint64_t rx_nombuf;
>  };
> 
>  struct queue_missed_stat {
> @@ -267,6 +267,9 @@ eth_pcap_rx_infinite(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
>  	pcap_q->rx_stat.pkts += i;
>  	pcap_q->rx_stat.bytes += rx_bytes;
> 
> +	/* ensure store operations of statistics are visible */
> +	rte_atomic_thread_fence(rte_memory_order_release);
> +
>  	return i;
>  }
> 
> @@ -345,6 +348,9 @@ eth_pcap_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
>  	pcap_q->rx_stat.pkts += num_rx;
>  	pcap_q->rx_stat.bytes += rx_bytes;
> 
> +	/* ensure store operations of statistics are visible */
> +	rte_atomic_thread_fence(rte_memory_order_release);
> +
>  	return num_rx;
>  }
> 
> @@ -440,6 +446,9 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
>  	dumper_q->tx_stat.bytes += tx_bytes;
>  	dumper_q->tx_stat.err_pkts += nb_pkts - num_tx;
> 
> +	/* ensure store operations of statistics are visible */
> +	rte_atomic_thread_fence(rte_memory_order_release);
> +
>  	return nb_pkts;
>  }
> 
> @@ -464,6 +473,9 @@ eth_tx_drop(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
>  	tx_queue->tx_stat.pkts += nb_pkts;
>  	tx_queue->tx_stat.bytes += tx_bytes;
> 
> +	/* ensure store operations of statistics are visible */
> +	rte_atomic_thread_fence(rte_memory_order_release);
> +
>  	return nb_pkts;
>  }
> 
> @@ -515,6 +527,9 @@ eth_pcap_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
>  	tx_queue->tx_stat.bytes += tx_bytes;
>  	tx_queue->tx_stat.err_pkts += nb_pkts - num_tx;
> 
> +	/* ensure store operations of statistics are visible */
> +	rte_atomic_thread_fence(rte_memory_order_release);
> +
>  	return nb_pkts;
>  }
> 
> @@ -821,13 +836,16 @@ eth_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
>  	      struct eth_queue_stats *qstats)
>  {
>  	unsigned int i;
> -	unsigned long rx_packets_total = 0, rx_bytes_total = 0;
> -	unsigned long rx_missed_total = 0;
> -	unsigned long rx_nombuf_total = 0, rx_err_total = 0;
> -	unsigned long tx_packets_total = 0, tx_bytes_total = 0;
> -	unsigned long tx_packets_err_total = 0;
> +	uint64_t rx_packets_total = 0, rx_bytes_total = 0;
> +	uint64_t rx_missed_total = 0;
> +	uint64_t rx_nombuf_total = 0, rx_err_total = 0;
> +	uint64_t tx_packets_total = 0, tx_bytes_total = 0;
> +	uint64_t tx_packets_err_total = 0;
>  	const struct pmd_internals *internal = dev->data->dev_private;
> 
> +	/* ensure that current statistics are visible */
> +	rte_atomic_thread_fence(rte_memory_order_acquire);
> +
>  	for (i = 0; i < RTE_ETHDEV_QUEUE_STAT_CNTRS &&
>  			i < dev->data->nb_rx_queues; i++) {
>  		if (qstats != NULL) {
> @@ -884,6 +902,8 @@ eth_stats_reset(struct rte_eth_dev *dev)
>  		internal->tx_queue[i].tx_stat.err_pkts = 0;
>  	}
> 
> +	/* ensure store operations of statistics are visible */
> +	rte_atomic_thread_fence(rte_memory_order_release);
>  	return 0;
>  }
> 
> --
> 2.51.0
> 


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

* RE: [PATCH 08/12] net/pcap: optimize calculation of receive timestamp
  2026-01-06 18:26 ` [PATCH 08/12] net/pcap: optimize calculation of receive timestamp Stephen Hemminger
@ 2026-01-07 10:58   ` Marat Khalili
  0 siblings, 0 replies; 28+ messages in thread
From: Marat Khalili @ 2026-01-07 10:58 UTC (permalink / raw)
  To: Stephen Hemminger, dev

> -----Original Message-----
> From: Stephen Hemminger <stephen@networkplumber.org>
> Sent: Tuesday 6 January 2026 18:27
> To: dev@dpdk.org
> Cc: Stephen Hemminger <stephen@networkplumber.org>
> Subject: [PATCH 08/12] net/pcap: optimize calculation of receive timestamp
> 
> Avoid doing slow instructions in receive path when calculating
> timestamp. Give all packets in the same rx burst the same timestamp.
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---
>  drivers/net/pcap/pcap_ethdev.c | 23 +++++++++++++----------
>  1 file changed, 13 insertions(+), 10 deletions(-)
> 
> diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
> index 175d6998f9..e283fb3787 100644
> --- a/drivers/net/pcap/pcap_ethdev.c
> +++ b/drivers/net/pcap/pcap_ethdev.c
> @@ -20,6 +20,7 @@
>  #include <bus_vdev_driver.h>
>  #include <rte_os_shim.h>
>  #include <rte_time.h>
> +#include <rte_reciprocal.h>
> 
>  #include "pcap_osdep.h"
> 
> @@ -41,7 +42,7 @@
> 
>  static struct timespec start_time;
>  static uint64_t start_cycles;
> -static uint64_t hz;
> +static struct rte_reciprocal_u64 hz_inv;
> 
>  static uint64_t timestamp_rx_dynflag;
>  static int timestamp_dynfield_offset = -1;
> @@ -362,8 +363,6 @@ eth_null_rx(void *queue __rte_unused,
>  	return 0;
>  }
> 
> -#define NSEC_PER_SEC	1000000000L
> -
>  /*
>   * This function stores nanoseconds in `tv_usec` field of `struct timeval`,
>   * because `ts` goes directly to nanosecond-precision dump.
> @@ -374,8 +373,10 @@ calculate_timestamp(struct timeval *ts) {
>  	struct timespec cur_time;
> 
>  	cycles = rte_get_timer_cycles() - start_cycles;
> -	cur_time.tv_sec = cycles / hz;
> -	cur_time.tv_nsec = (cycles % hz) * NSEC_PER_SEC / hz;
> +	cur_time.tv_sec = rte_reciprocal_divide_u64(cycles, &hz_inv);
> +	/* compute remainder */
> +	cycles -= cur_time.tv_sec * rte_get_timer_hz();

Can be made faster and safer by caching rte_get_timer_hz() result in current translation unit.

> +	cur_time.tv_nsec = rte_reciprocal_divide_u64(cycles * NS_PER_S, &hz_inv);
> 
>  	ts->tv_sec = start_time.tv_sec + cur_time.tv_sec;
>  	ts->tv_usec = start_time.tv_nsec + cur_time.tv_nsec;
> @@ -394,6 +395,7 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
>  	unsigned int i;
>  	struct pmd_process_private *pp;
>  	struct pcap_tx_queue *dumper_q = queue;
> +	struct pcap_pkthdr header;
>  	uint16_t num_tx = 0;
>  	uint32_t tx_bytes = 0;
>  	pcap_dumper_t *dumper;
> @@ -406,13 +408,14 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
>  	if (unlikely(dumper == NULL || nb_pkts == 0))
>  		return 0;
> 
> -	/* writes the nb_pkts packets to the previously opened pcap file
> -	 * dumper */
> +	/* all packets in burst have same timestamp */
> +	calculate_timestamp(&header.ts);
> +
> +	/* writes the nb_pkts packets to the previously opened pcap file dumper */
>  	for (i = 0; i < nb_pkts; i++) {
>  		struct rte_mbuf *mbuf = bufs[i];
>  		size_t len = rte_pktmbuf_pkt_len(mbuf);
>  		uint8_t temp_data[RTE_ETH_PCAP_SNAPLEN];
> -		struct pcap_pkthdr header;
> 
>  		if (unlikely(len > mtu))
>  			continue;
> @@ -420,7 +423,6 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
>  		if ((mbuf->ol_flags & RTE_MBUF_F_TX_VLAN) && rte_vlan_insert(&mbuf))
>  			continue;
> 
> -		calculate_timestamp(&header.ts);
>  		header.len = len;
>  		header.caplen = len;
> 
> @@ -1530,7 +1532,8 @@ pmd_pcap_probe(struct rte_vdev_device *dev)
> 
>  	timespec_get(&start_time, TIME_UTC);
>  	start_cycles = rte_get_timer_cycles();
> -	hz = rte_get_timer_hz();
> +
> +	hz_inv = rte_reciprocal_value_u64(rte_get_timer_hz());
> 
>  	if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
>  		eth_dev = rte_eth_dev_attach_secondary(name);
> --
> 2.51.0

Apart from one minor optimization suggestion above,

Acked-by: Marat Khalili <marat.khalili@huawei.com>

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

* Re: [PATCH 03/12] net/pcap: use bool for flags
  2026-01-07 10:28   ` Marat Khalili
@ 2026-01-09  0:23     ` Stephen Hemminger
  0 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-09  0:23 UTC (permalink / raw)
  To: Marat Khalili; +Cc: dev

On Wed, 7 Jan 2026 10:28:32 +0000
Marat Khalili <marat.khalili@huawei.com> wrote:

> > @@ -1180,31 +1181,29 @@ open_tx_iface(const char *key, const char *value, void *extra_args)
> >  }
> > 
> >  static int
> > -select_phy_mac(const char *key __rte_unused, const char *value,
> > -		void *extra_args)
> > +process_bool_flag(const char *key, const char *value, void *extra_args)  
> 
> This function probably belongs in some common library. There is a similar code 
> in lib/argparse/rte_argparse.c and lib/cmdline/cmdline_parse_bool.c that might 
> benefit. Having some tests could also be useful.
> 
> I would also think of naming more, word "process" does not really convey much 
> meaning. How about "parse_bool"?

I just went off and tried that and it ends up not being that good.
The problem is that the place for common code in DPDK is currently EAL.

But if you put bool parsing in EAL it creates a circular dependency
since EAL depends on argparse and kvargs. So can't use any new string
parsing there. Cmdline does its own validation so common code won't help
there either.

Good idea, just won't work.
Going back to just 0/1 for now.

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

* [PATCH v2 0/9] pcap: cleanup pcap PMD and add test
  2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
                   ` (11 preceding siblings ...)
  2026-01-06 18:26 ` [PATCH 12/12] test: add test for pcap PMD Stephen Hemminger
@ 2026-01-09  1:16 ` Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 1/9] net/pcap: avoid using rte_malloc and rte_memcpy Stephen Hemminger
                     ` (8 more replies)
  12 siblings, 9 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-09  1:16 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

This is a set of enhancements and tests to the PCAP PMD.
It started out when looking at the handling of timestamps
then realized lots of other cleanups were needed here.

v2 - review feedback
   - consolidate patches

Stephen Hemminger (9):
  net/pcap: avoid using rte_malloc and rte_memcpy
  net/pcap: support MTU set
  net/pcap: use bool for flags
  net/pcap: support Tx offloads
  net/pcap: support nanosecond timestamp precision
  net/pcap: remove global variables
  net/pcap: avoid use of volatile
  net/pcap: support MAC address set
  test: add test for pcap PMD

 app/test/meson.build                  |    2 +
 app/test/test_pmd_pcap.c              | 1846 +++++++++++++++++++++++++
 drivers/net/pcap/pcap_ethdev.c        |  353 +++--
 drivers/net/pcap/pcap_osdep.h         |    2 +
 drivers/net/pcap/pcap_osdep_freebsd.c |   60 +-
 drivers/net/pcap/pcap_osdep_linux.c   |   51 +-
 drivers/net/pcap/pcap_osdep_windows.c |    5 +
 7 files changed, 2184 insertions(+), 135 deletions(-)
 create mode 100644 app/test/test_pmd_pcap.c

-- 
2.51.0


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

* [PATCH v2 1/9] net/pcap: avoid using rte_malloc and rte_memcpy
  2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
@ 2026-01-09  1:16   ` Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 2/9] net/pcap: support MTU set Stephen Hemminger
                     ` (7 subsequent siblings)
  8 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-09  1:16 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

No need to use rte_malloc or rte_memcpy in the short
code to get MAC address.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_osdep_freebsd.c | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/drivers/net/pcap/pcap_osdep_freebsd.c b/drivers/net/pcap/pcap_osdep_freebsd.c
index 20556b3e92..32e4a2bee7 100644
--- a/drivers/net/pcap/pcap_osdep_freebsd.c
+++ b/drivers/net/pcap/pcap_osdep_freebsd.c
@@ -8,9 +8,6 @@
 #include <net/if_dl.h>
 #include <sys/sysctl.h>
 
-#include <rte_malloc.h>
-#include <rte_memcpy.h>
-
 #include "pcap_osdep.h"
 
 int
@@ -41,19 +38,19 @@ osdep_iface_mac_get(const char *if_name, struct rte_ether_addr *mac)
 	if (len == 0)
 		return -1;
 
-	buf = rte_malloc(NULL, len, 0);
+	buf = malloc(len);
 	if (!buf)
 		return -1;
 
 	if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
-		rte_free(buf);
+		free(buf);
 		return -1;
 	}
 	ifm = (struct if_msghdr *)buf;
 	sdl = (struct sockaddr_dl *)(ifm + 1);
 
-	rte_memcpy(mac->addr_bytes, LLADDR(sdl), RTE_ETHER_ADDR_LEN);
+	memcpy(mac->addr_bytes, LLADDR(sdl), RTE_ETHER_ADDR_LEN);
 
-	rte_free(buf);
+	free(buf);
 	return 0;
 }
-- 
2.51.0


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

* [PATCH v2 2/9] net/pcap: support MTU set
  2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 1/9] net/pcap: avoid using rte_malloc and rte_memcpy Stephen Hemminger
@ 2026-01-09  1:16   ` Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 3/9] net/pcap: use bool for flags Stephen Hemminger
                     ` (6 subsequent siblings)
  8 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-09  1:16 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

When used as single interface useful to support pass through
of MTU setting to enable larger frames.

Cleanup the transmit logic so that if packet sent exceeds MTU
is an error.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c        | 94 +++++++++++++++------------
 drivers/net/pcap/pcap_osdep.h         |  1 +
 drivers/net/pcap/pcap_osdep_freebsd.c | 26 ++++++++
 drivers/net/pcap/pcap_osdep_linux.c   | 21 ++++++
 drivers/net/pcap/pcap_osdep_windows.c |  5 ++
 5 files changed, 104 insertions(+), 43 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index f323c0b0df..f4cb444395 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -22,7 +22,7 @@
 #include "pcap_osdep.h"
 
 #define RTE_ETH_PCAP_SNAPSHOT_LEN 65535
-#define RTE_ETH_PCAP_SNAPLEN RTE_ETHER_MAX_JUMBO_FRAME_LEN
+#define RTE_ETH_PCAP_SNAPLEN (RTE_ETHER_MAX_JUMBO_FRAME_LEN - RTE_ETHER_CRC_LEN)
 #define RTE_ETH_PCAP_PROMISC 1
 #define RTE_ETH_PCAP_TIMEOUT -1
 
@@ -377,46 +377,46 @@ static uint16_t
 eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 {
 	unsigned int i;
-	struct rte_mbuf *mbuf;
 	struct pmd_process_private *pp;
 	struct pcap_tx_queue *dumper_q = queue;
 	uint16_t num_tx = 0;
 	uint32_t tx_bytes = 0;
-	struct pcap_pkthdr header;
 	pcap_dumper_t *dumper;
-	unsigned char temp_data[RTE_ETH_PCAP_SNAPLEN];
-	size_t len, caplen;
+	uint16_t mtu;
 
 	pp = rte_eth_devices[dumper_q->port_id].process_private;
 	dumper = pp->tx_dumper[dumper_q->queue_id];
+	mtu = rte_eth_devices[dumper_q->port_id].data->mtu;
 
-	if (dumper == NULL || nb_pkts == 0)
+	if (unlikely(dumper == NULL || nb_pkts == 0))
 		return 0;
 
 	/* writes the nb_pkts packets to the previously opened pcap file
 	 * dumper */
 	for (i = 0; i < nb_pkts; i++) {
-		mbuf = bufs[i];
-		len = caplen = rte_pktmbuf_pkt_len(mbuf);
-		if (unlikely(!rte_pktmbuf_is_contiguous(mbuf) &&
-				len > sizeof(temp_data))) {
-			caplen = sizeof(temp_data);
-		}
+		struct rte_mbuf *mbuf = bufs[i];
+		size_t len = rte_pktmbuf_pkt_len(mbuf);
+		uint8_t temp_data[RTE_ETH_PCAP_SNAPLEN];
+		struct pcap_pkthdr header;
+
+		if (unlikely(len > mtu))
+			continue;
 
 		calculate_timestamp(&header.ts);
 		header.len = len;
-		header.caplen = caplen;
+		header.caplen = len;
+
 		/* rte_pktmbuf_read() returns a pointer to the data directly
 		 * in the mbuf (when the mbuf is contiguous) or, otherwise,
 		 * a pointer to temp_data after copying into it.
 		 */
-		pcap_dump((u_char *)dumper, &header,
-			rte_pktmbuf_read(mbuf, 0, caplen, temp_data));
+		const uint8_t *data = rte_pktmbuf_read(mbuf, 0, len, temp_data);
+		pcap_dump((u_char *)dumper, &header, data);
 
 		num_tx++;
-		tx_bytes += caplen;
-		rte_pktmbuf_free(mbuf);
+		tx_bytes += len;
 	}
+	rte_pktmbuf_free_bulk(bufs, nb_pkts);
 
 	/*
 	 * Since there's no place to hook a callback when the forwarding
@@ -444,15 +444,15 @@ eth_tx_drop(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	if (unlikely(nb_pkts == 0))
 		return 0;
 
-	for (i = 0; i < nb_pkts; i++) {
+	for (i = 0; i < nb_pkts; i++)
 		tx_bytes += bufs[i]->pkt_len;
-		rte_pktmbuf_free(bufs[i]);
-	}
+
+	rte_pktmbuf_free_bulk(bufs, nb_pkts);
 
 	tx_queue->tx_stat.pkts += nb_pkts;
 	tx_queue->tx_stat.bytes += tx_bytes;
 
-	return i;
+	return nb_pkts;
 }
 
 /*
@@ -462,52 +462,45 @@ static uint16_t
 eth_pcap_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 {
 	unsigned int i;
-	int ret;
-	struct rte_mbuf *mbuf;
 	struct pmd_process_private *pp;
 	struct pcap_tx_queue *tx_queue = queue;
 	uint16_t num_tx = 0;
 	uint32_t tx_bytes = 0;
 	pcap_t *pcap;
-	unsigned char temp_data[RTE_ETH_PCAP_SNAPLEN];
-	size_t len;
+	uint16_t mtu;
 
 	pp = rte_eth_devices[tx_queue->port_id].process_private;
 	pcap = pp->tx_pcap[tx_queue->queue_id];
+	mtu = rte_eth_devices[tx_queue->port_id].data->mtu;
 
 	if (unlikely(nb_pkts == 0 || pcap == NULL))
 		return 0;
 
 	for (i = 0; i < nb_pkts; i++) {
-		mbuf = bufs[i];
-		len = rte_pktmbuf_pkt_len(mbuf);
-		if (unlikely(!rte_pktmbuf_is_contiguous(mbuf) &&
-				len > sizeof(temp_data))) {
-			PMD_LOG(ERR,
-				"Dropping multi segment PCAP packet. Size (%zd) > max size (%zd).",
-				len, sizeof(temp_data));
-			rte_pktmbuf_free(mbuf);
+		struct rte_mbuf *mbuf = bufs[i];
+		size_t len = rte_pktmbuf_pkt_len(mbuf);
+		uint8_t temp_data[RTE_ETH_PCAP_SNAPLEN];
+
+		if (unlikely(len > mtu))
 			continue;
-		}
 
 		/* rte_pktmbuf_read() returns a pointer to the data directly
 		 * in the mbuf (when the mbuf is contiguous) or, otherwise,
 		 * a pointer to temp_data after copying into it.
 		 */
-		ret = pcap_sendpacket(pcap,
-			rte_pktmbuf_read(mbuf, 0, len, temp_data), len);
-		if (unlikely(ret != 0))
-			break;
-		num_tx++;
-		tx_bytes += len;
-		rte_pktmbuf_free(mbuf);
+		const uint8_t *data = rte_pktmbuf_read(mbuf, 0, len, temp_data);
+
+		if (likely(pcap_sendpacket(pcap, data, len) == 0)) {
+			num_tx++;
+			tx_bytes += len;
+		}
 	}
 
 	tx_queue->tx_stat.pkts += num_tx;
 	tx_queue->tx_stat.bytes += tx_bytes;
-	tx_queue->tx_stat.err_pkts += i - num_tx;
+	tx_queue->tx_stat.err_pkts += nb_pkts - num_tx;
 
-	return i;
+	return nb_pkts;
 }
 
 /*
@@ -745,6 +738,8 @@ eth_dev_info(struct rte_eth_dev *dev,
 	dev_info->max_rx_queues = dev->data->nb_rx_queues;
 	dev_info->max_tx_queues = dev->data->nb_tx_queues;
 	dev_info->min_rx_bufsize = 0;
+	dev_info->min_mtu = RTE_ETHER_MIN_LEN - RTE_ETHER_HDR_LEN - RTE_ETHER_CRC_LEN;
+	dev_info->max_mtu = RTE_ETH_PCAP_SNAPLEN;
 
 	return 0;
 }
@@ -1002,6 +997,18 @@ eth_tx_queue_stop(struct rte_eth_dev *dev, uint16_t tx_queue_id)
 	return 0;
 }
 
+static int
+eth_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
+{
+	struct pmd_internals *internals = dev->data->dev_private;
+
+	if (internals->single_iface)
+		return osdep_iface_mtu_set(internals->if_index, mtu);
+
+	return 0;
+}
+
+
 static const struct eth_dev_ops ops = {
 	.dev_start = eth_dev_start,
 	.dev_stop = eth_dev_stop,
@@ -1015,6 +1022,7 @@ static const struct eth_dev_ops ops = {
 	.rx_queue_stop = eth_rx_queue_stop,
 	.tx_queue_stop = eth_tx_queue_stop,
 	.link_update = eth_link_update,
+	.mtu_set = eth_mtu_set,
 	.stats_get = eth_stats_get,
 	.stats_reset = eth_stats_reset,
 };
diff --git a/drivers/net/pcap/pcap_osdep.h b/drivers/net/pcap/pcap_osdep.h
index 2aa13f3629..3c8b7ff27b 100644
--- a/drivers/net/pcap/pcap_osdep.h
+++ b/drivers/net/pcap/pcap_osdep.h
@@ -14,5 +14,6 @@ extern int eth_pcap_logtype;
 
 int osdep_iface_index_get(const char *name);
 int osdep_iface_mac_get(const char *name, struct rte_ether_addr *mac);
+int osdep_iface_mtu_set(int index, uint16_t mtu);
 
 #endif
diff --git a/drivers/net/pcap/pcap_osdep_freebsd.c b/drivers/net/pcap/pcap_osdep_freebsd.c
index 32e4a2bee7..0279dbf00b 100644
--- a/drivers/net/pcap/pcap_osdep_freebsd.c
+++ b/drivers/net/pcap/pcap_osdep_freebsd.c
@@ -4,9 +4,12 @@
  * All rights reserved.
  */
 
+#include <unistd.h>
 #include <net/if.h>
 #include <net/if_dl.h>
 #include <sys/sysctl.h>
+#include <sys/ioctl.h>
+#include <sys/sockio.h>
 
 #include "pcap_osdep.h"
 
@@ -54,3 +57,26 @@ osdep_iface_mac_get(const char *if_name, struct rte_ether_addr *mac)
 	free(buf);
 	return 0;
 }
+
+int osdep_iface_mtu_set(int ifindex, uint16_t mtu)
+{
+	char ifname[IFNAMSIZ];
+
+	if (if_indextoname(ifindex, ifname) == NULL)
+		return -errno;
+
+	int s = socket(AF_INET, SOCK_DGRAM, 0);
+	if (s < 0)
+		return -errno;
+
+	struct ifreq ifr = { 0 };
+	if (s < 0)
+		return -EINVAL;
+
+	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
+	ifr.ifr_mtu = mtu;
+
+	int ret = ioctl(s, SIOCSIFMTU, &ifr);
+	close(s);
+	return (ret < 0) ? -errno : 0;
+}
diff --git a/drivers/net/pcap/pcap_osdep_linux.c b/drivers/net/pcap/pcap_osdep_linux.c
index 97033f57c5..d180e9b4b4 100644
--- a/drivers/net/pcap/pcap_osdep_linux.c
+++ b/drivers/net/pcap/pcap_osdep_linux.c
@@ -40,3 +40,24 @@ osdep_iface_mac_get(const char *if_name, struct rte_ether_addr *mac)
 	close(if_fd);
 	return 0;
 }
+
+int
+osdep_iface_mtu_set(int ifindex, uint16_t mtu)
+{
+	char ifname[IFNAMSIZ];
+
+	if (if_indextoname(ifindex, ifname) == NULL)
+		return -errno;
+
+	int s = socket(PF_INET, SOCK_DGRAM, 0);
+	if (s < 0)
+		return -errno;
+
+	struct ifreq ifr = { .ifr_mtu = mtu };
+	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
+
+	int ret = ioctl(s, SIOCSIFMTU, &ifr);
+	close(s);
+
+	return (ret < 0) ? -errno : 0;
+}
diff --git a/drivers/net/pcap/pcap_osdep_windows.c b/drivers/net/pcap/pcap_osdep_windows.c
index 1d398dc7ed..fde0db9961 100644
--- a/drivers/net/pcap/pcap_osdep_windows.c
+++ b/drivers/net/pcap/pcap_osdep_windows.c
@@ -116,3 +116,8 @@ osdep_iface_mac_get(const char *device_name, struct rte_ether_addr *mac)
 	free(info);
 	return ret;
 }
+
+int osdep_iface_mtu_set(int index __rte_unused, uint16_t mtu __rte_unused)
+{
+	return -ENOTSUP;
+}
-- 
2.51.0


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

* [PATCH v2 3/9] net/pcap: use bool for flags
  2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 1/9] net/pcap: avoid using rte_malloc and rte_memcpy Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 2/9] net/pcap: support MTU set Stephen Hemminger
@ 2026-01-09  1:16   ` Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 4/9] net/pcap: support Tx offloads Stephen Hemminger
                     ` (5 subsequent siblings)
  8 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-09  1:16 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Save some space by using bool for flag values.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 67 +++++++++++++++-------------------
 1 file changed, 29 insertions(+), 38 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index f4cb444395..5db44ab1ea 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -5,6 +5,7 @@
  */
 
 #include <stdlib.h>
+#include <stdbool.h>
 #include <time.h>
 
 #include <pcap.h>
@@ -91,9 +92,9 @@ struct pmd_internals {
 	char devargs[ETH_PCAP_ARG_MAXLEN];
 	struct rte_ether_addr eth_addr;
 	int if_index;
-	int single_iface;
-	int phy_mac;
-	unsigned int infinite_rx;
+	bool single_iface;
+	bool phy_mac;
+	bool infinite_rx;
 };
 
 struct pmd_process_private {
@@ -103,25 +104,25 @@ struct pmd_process_private {
 };
 
 struct pmd_devargs {
-	unsigned int num_of_queue;
+	uint16_t num_of_queue;
+	bool phy_mac;
 	struct devargs_queue {
 		pcap_dumper_t *dumper;
 		pcap_t *pcap;
 		const char *name;
 		const char *type;
 	} queue[RTE_PMD_PCAP_MAX_QUEUES];
-	int phy_mac;
 };
 
 struct pmd_devargs_all {
 	struct pmd_devargs rx_queues;
 	struct pmd_devargs tx_queues;
-	int single_iface;
-	unsigned int is_tx_pcap;
-	unsigned int is_tx_iface;
-	unsigned int is_rx_pcap;
-	unsigned int is_rx_iface;
-	unsigned int infinite_rx;
+	bool single_iface;
+	bool is_tx_pcap;
+	bool is_tx_iface;
+	bool is_rx_pcap;
+	bool is_rx_iface;
+	bool infinite_rx;
 };
 
 static const char *valid_arguments[] = {
@@ -858,7 +859,7 @@ eth_dev_close(struct rte_eth_dev *dev)
 		}
 	}
 
-	if (internals->phy_mac == 0)
+	if (!internals->phy_mac)
 		/* not dynamically allocated, must not be freed */
 		dev->data->mac_addrs = NULL;
 
@@ -1180,29 +1181,19 @@ open_tx_iface(const char *key, const char *value, void *extra_args)
 }
 
 static int
-select_phy_mac(const char *key __rte_unused, const char *value,
-		void *extra_args)
+process_bool_flag(const char *key, const char *value, void *extra_args)
 {
-	if (extra_args) {
-		const int phy_mac = atoi(value);
-		int *enable_phy_mac = extra_args;
-
-		if (phy_mac)
-			*enable_phy_mac = 1;
-	}
-	return 0;
-}
-
-static int
-get_infinite_rx_arg(const char *key __rte_unused,
-		const char *value, void *extra_args)
-{
-	if (extra_args) {
-		const int infinite_rx = atoi(value);
-		int *enable_infinite_rx = extra_args;
-
-		if (infinite_rx > 0)
-			*enable_infinite_rx = 1;
+	bool *flag = extra_args;
+
+	if (value == NULL || *value == '\0') {
+		*flag = true; /* default with no additional argument */
+	} else if (strcmp(value, "0") == 0) {
+		*flag = false;
+	} else if (strcmp(value, "1") == 0) {
+		*flag = true;
+	} else {
+		PMD_LOG(ERR, "Invalid '%s' value '%s'", key, value);
+		return -1;
 	}
 	return 0;
 }
@@ -1479,7 +1470,7 @@ pmd_pcap_probe(struct rte_vdev_device *dev)
 		dumpers.queue[0] = pcaps.queue[0];
 
 		ret = rte_kvargs_process(kvlist, ETH_PCAP_PHY_MAC_ARG,
-				&select_phy_mac, &pcaps.phy_mac);
+					 &process_bool_flag, &pcaps.phy_mac);
 		if (ret < 0)
 			goto free_kvlist;
 
@@ -1518,9 +1509,9 @@ pmd_pcap_probe(struct rte_vdev_device *dev)
 
 		if (infinite_rx_arg_cnt == 1) {
 			ret = rte_kvargs_process(kvlist,
-					ETH_PCAP_INFINITE_RX_ARG,
-					&get_infinite_rx_arg,
-					&devargs_all.infinite_rx);
+						 ETH_PCAP_INFINITE_RX_ARG,
+						 &process_bool_flag,
+						 &devargs_all.infinite_rx);
 			if (ret < 0)
 				goto free_kvlist;
 			PMD_LOG(INFO, "infinite_rx has been %s for %s",
-- 
2.51.0


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

* [PATCH v2 4/9] net/pcap: support Tx offloads
  2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
                     ` (2 preceding siblings ...)
  2026-01-09  1:16   ` [PATCH v2 3/9] net/pcap: use bool for flags Stephen Hemminger
@ 2026-01-09  1:16   ` Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 5/9] net/pcap: support nanosecond timestamp precision Stephen Hemminger
                     ` (4 subsequent siblings)
  8 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-09  1:16 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The driver already handles multi-segment mbufs but did not report
that in offload flags. Driver can easily insert vlan tag making
testing easier.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index 5db44ab1ea..a27a132002 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -403,6 +403,9 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		if (unlikely(len > mtu))
 			continue;
 
+		if ((mbuf->ol_flags & RTE_MBUF_F_TX_VLAN) && rte_vlan_insert(&mbuf))
+			continue;
+
 		calculate_timestamp(&header.ts);
 		header.len = len;
 		header.caplen = len;
@@ -485,6 +488,9 @@ eth_pcap_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		if (unlikely(len > mtu))
 			continue;
 
+		if ((mbuf->ol_flags & RTE_MBUF_F_TX_VLAN) && rte_vlan_insert(&mbuf))
+			continue;
+
 		/* rte_pktmbuf_read() returns a pointer to the data directly
 		 * in the mbuf (when the mbuf is contiguous) or, otherwise,
 		 * a pointer to temp_data after copying into it.
@@ -741,6 +747,8 @@ eth_dev_info(struct rte_eth_dev *dev,
 	dev_info->min_rx_bufsize = 0;
 	dev_info->min_mtu = RTE_ETHER_MIN_LEN - RTE_ETHER_HDR_LEN - RTE_ETHER_CRC_LEN;
 	dev_info->max_mtu = RTE_ETH_PCAP_SNAPLEN;
+	dev_info->tx_offload_capa = RTE_ETH_TX_OFFLOAD_MULTI_SEGS |
+		RTE_ETH_TX_OFFLOAD_VLAN_INSERT;
 
 	return 0;
 }
-- 
2.51.0


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

* [PATCH v2 5/9] net/pcap: support nanosecond timestamp precision
  2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
                     ` (3 preceding siblings ...)
  2026-01-09  1:16   ` [PATCH v2 4/9] net/pcap: support Tx offloads Stephen Hemminger
@ 2026-01-09  1:16   ` Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 6/9] net/pcap: remove global variables Stephen Hemminger
                     ` (3 subsequent siblings)
  8 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-09  1:16 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Consistently support nanosecond timestamps across all the
variations of pcap PMD receive.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 142 +++++++++++++++++++++++++--------
 1 file changed, 109 insertions(+), 33 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index a27a132002..25e78f1e3a 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -19,13 +19,13 @@
 #include <rte_mbuf_dyn.h>
 #include <bus_vdev_driver.h>
 #include <rte_os_shim.h>
+#include <rte_time.h>
+#include <rte_reciprocal.h>
 
 #include "pcap_osdep.h"
 
 #define RTE_ETH_PCAP_SNAPSHOT_LEN 65535
 #define RTE_ETH_PCAP_SNAPLEN (RTE_ETHER_MAX_JUMBO_FRAME_LEN - RTE_ETHER_CRC_LEN)
-#define RTE_ETH_PCAP_PROMISC 1
-#define RTE_ETH_PCAP_TIMEOUT -1
 
 #define ETH_PCAP_RX_PCAP_ARG  "rx_pcap"
 #define ETH_PCAP_TX_PCAP_ARG  "tx_pcap"
@@ -44,6 +44,7 @@ static char errbuf[PCAP_ERRBUF_SIZE];
 static struct timespec start_time;
 static uint64_t start_cycles;
 static uint64_t hz;
+static struct rte_reciprocal_u64 hz_inv;
 static uint8_t iface_idx;
 
 static uint64_t timestamp_rx_dynflag;
@@ -68,6 +69,7 @@ struct queue_missed_stat {
 struct pcap_rx_queue {
 	uint16_t port_id;
 	uint16_t queue_id;
+	bool timestamp_offloading;
 	struct rte_mempool *mb_pool;
 	struct queue_stat rx_stat;
 	struct queue_missed_stat missed_stat;
@@ -95,6 +97,7 @@ struct pmd_internals {
 	bool single_iface;
 	bool phy_mac;
 	bool infinite_rx;
+	bool timestamp_offloading;
 };
 
 struct pmd_process_private {
@@ -325,10 +328,20 @@ eth_pcap_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		}
 
 		mbuf->pkt_len = len;
-		uint64_t us = (uint64_t)header->ts.tv_sec * US_PER_S + header->ts.tv_usec;
 
-		*RTE_MBUF_DYNFIELD(mbuf, timestamp_dynfield_offset, rte_mbuf_timestamp_t *) = us;
-		mbuf->ol_flags |= timestamp_rx_dynflag;
+		if (pcap_q->timestamp_offloading) {
+			/*
+			 * Although time stamp in struct pcap_pkthdr is defined as struct timeval,
+			 * it really is a timespec with nanosecond resolution.
+			 */
+			const struct timespec *ts = (struct timespec *)&header->ts;
+
+			*RTE_MBUF_DYNFIELD(mbuf, timestamp_dynfield_offset,
+					   rte_mbuf_timestamp_t *) = rte_timespec_to_ns(ts);
+
+			mbuf->ol_flags |= timestamp_rx_dynflag;
+		}
+
 		mbuf->port = pcap_q->port_id;
 		bufs[num_rx] = mbuf;
 		num_rx++;
@@ -348,20 +361,21 @@ eth_null_rx(void *queue __rte_unused,
 	return 0;
 }
 
-#define NSEC_PER_SEC	1000000000L
-
 /*
  * This function stores nanoseconds in `tv_usec` field of `struct timeval`,
  * because `ts` goes directly to nanosecond-precision dump.
  */
 static inline void
-calculate_timestamp(struct timeval *ts) {
+calculate_timestamp(struct timeval *ts)
+{
 	uint64_t cycles;
 	struct timespec cur_time;
 
 	cycles = rte_get_timer_cycles() - start_cycles;
-	cur_time.tv_sec = cycles / hz;
-	cur_time.tv_nsec = (cycles % hz) * NSEC_PER_SEC / hz;
+	cur_time.tv_sec = rte_reciprocal_divide_u64(cycles, &hz_inv);
+	/* compute remainder */
+	cycles -= cur_time.tv_sec * hz;
+	cur_time.tv_nsec = rte_reciprocal_divide_u64(cycles * NS_PER_S, &hz_inv);
 
 	ts->tv_sec = start_time.tv_sec + cur_time.tv_sec;
 	ts->tv_usec = start_time.tv_nsec + cur_time.tv_nsec;
@@ -380,6 +394,7 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	unsigned int i;
 	struct pmd_process_private *pp;
 	struct pcap_tx_queue *dumper_q = queue;
+	struct pcap_pkthdr header;
 	uint16_t num_tx = 0;
 	uint32_t tx_bytes = 0;
 	pcap_dumper_t *dumper;
@@ -392,13 +407,14 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	if (unlikely(dumper == NULL || nb_pkts == 0))
 		return 0;
 
-	/* writes the nb_pkts packets to the previously opened pcap file
-	 * dumper */
+	/* all packets in burst have same timestamp */
+	calculate_timestamp(&header.ts);
+
+	/* writes the nb_pkts packets to the previously opened pcap file dumper */
 	for (i = 0; i < nb_pkts; i++) {
 		struct rte_mbuf *mbuf = bufs[i];
 		size_t len = rte_pktmbuf_pkt_len(mbuf);
 		uint8_t temp_data[RTE_ETH_PCAP_SNAPLEN];
-		struct pcap_pkthdr header;
 
 		if (unlikely(len > mtu))
 			continue;
@@ -406,7 +422,6 @@ eth_pcap_tx_dumper(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		if ((mbuf->ol_flags & RTE_MBUF_F_TX_VLAN) && rte_vlan_insert(&mbuf))
 			continue;
 
-		calculate_timestamp(&header.ts);
 		header.len = len;
 		header.caplen = len;
 
@@ -514,22 +529,57 @@ eth_pcap_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
  * pcap_open_live wrapper function
  */
 static inline int
-open_iface_live(const char *iface, pcap_t **pcap) {
-	*pcap = pcap_open_live(iface, RTE_ETH_PCAP_SNAPLEN,
-			RTE_ETH_PCAP_PROMISC, RTE_ETH_PCAP_TIMEOUT, errbuf);
+open_iface_live(const char *iface, pcap_t **pcap)
+{
+	pcap_t *pc = pcap_create(iface, errbuf);
+	if (pc == NULL) {
+		PMD_LOG(ERR, "Couldn't create %s: %s", iface, errbuf);
+		goto error;
+	}
 
-	if (*pcap == NULL) {
-		PMD_LOG(ERR, "Couldn't open %s: %s", iface, errbuf);
-		return -1;
+	int status = pcap_set_tstamp_precision(pc, PCAP_TSTAMP_PRECISION_NANO);
+	if (status != 0)
+		PMD_LOG(WARNING, "%s: Could not set to ns precision: %s",
+			iface, pcap_statustostr(status));
+
+	status = pcap_set_immediate_mode(pc, 1);
+	if (status != 0)
+		PMD_LOG(WARNING, "%s: Could not set to immediate mode: %s",
+			iface, pcap_statustostr(status));
+
+	status = pcap_set_promisc(pc, 1);
+	if (status != 0)
+		PMD_LOG(WARNING, "%s: Could not set to promiscious: %s",
+			iface, pcap_statustostr(status));
+
+	status = pcap_set_snaplen(pc, RTE_ETH_PCAP_SNAPSHOT_LEN);
+	if (status != 0)
+		PMD_LOG(WARNING, "%s: Could not set snapshot length: %s",
+			iface, pcap_statustostr(status));
+
+	status = pcap_activate(pc);
+	if (status < 0) {
+		char *cp = pcap_geterr(pc);
+
+		if (status == PCAP_ERROR)
+			PMD_LOG(ERR, "%s: could not activate: %s", iface, cp);
+		else
+			PMD_LOG(ERR, "%s: %s (%s)", iface, pcap_statustostr(status), cp);
+		goto error;
 	}
 
-	if (pcap_setnonblock(*pcap, 1, errbuf)) {
+	if (pcap_setnonblock(pc, 1, errbuf)) {
 		PMD_LOG(ERR, "Couldn't set non-blocking on %s: %s", iface, errbuf);
-		pcap_close(*pcap);
-		return -1;
+		goto error;
 	}
 
+	*pcap = pc;
 	return 0;
+
+error:
+	if (pc)
+		pcap_close(pc);
+	return -1;
 }
 
 static int
@@ -576,7 +626,8 @@ open_single_tx_pcap(const char *pcap_filename, pcap_dumper_t **dumper)
 static int
 open_single_rx_pcap(const char *pcap_filename, pcap_t **pcap)
 {
-	*pcap = pcap_open_offline(pcap_filename, errbuf);
+	*pcap = pcap_open_offline_with_tstamp_precision(pcap_filename,
+							PCAP_TSTAMP_PRECISION_NANO, errbuf);
 	if (*pcap == NULL) {
 		PMD_LOG(ERR, "Couldn't open %s: %s", pcap_filename,
 			errbuf);
@@ -613,6 +664,15 @@ eth_dev_start(struct rte_eth_dev *dev)
 	struct pcap_tx_queue *tx;
 	struct pcap_rx_queue *rx;
 
+	if (internals->timestamp_offloading) {
+		int ret = rte_mbuf_dyn_rx_timestamp_register(&timestamp_dynfield_offset,
+							     &timestamp_rx_dynflag);
+		if (ret != 0) {
+			PMD_LOG(ERR, "Failed to register Rx timestamp field/flag");
+			return ret;
+		}
+	}
+
 	/* Special iface case. Single pcap is open and shared between tx/rx. */
 	if (internals->single_iface) {
 		tx = &internals->tx_queue[0];
@@ -728,8 +788,13 @@ eth_dev_stop(struct rte_eth_dev *dev)
 }
 
 static int
-eth_dev_configure(struct rte_eth_dev *dev __rte_unused)
+eth_dev_configure(struct rte_eth_dev *dev)
 {
+	struct pmd_internals *internals = dev->data->dev_private;
+	struct rte_eth_conf *dev_conf = &dev->data->dev_conf;
+	const struct rte_eth_rxmode *rxmode = &dev_conf->rxmode;
+
+	internals->timestamp_offloading = !!(rxmode->offloads & RTE_ETH_RX_OFFLOAD_TIMESTAMP);
 	return 0;
 }
 
@@ -749,6 +814,7 @@ eth_dev_info(struct rte_eth_dev *dev,
 	dev_info->max_mtu = RTE_ETH_PCAP_SNAPLEN;
 	dev_info->tx_offload_capa = RTE_ETH_TX_OFFLOAD_MULTI_SEGS |
 		RTE_ETH_TX_OFFLOAD_VLAN_INSERT;
+	dev_info->rx_offload_capa = RTE_ETH_RX_OFFLOAD_TIMESTAMP;
 
 	return 0;
 }
@@ -896,6 +962,7 @@ eth_rx_queue_setup(struct rte_eth_dev *dev,
 	pcap_q->port_id = dev->data->port_id;
 	pcap_q->queue_id = rx_queue_id;
 	dev->data->rx_queues[rx_queue_id] = pcap_q;
+	pcap_q->timestamp_offloading = internals->timestamp_offloading;
 
 	if (internals->infinite_rx) {
 		struct pmd_process_private *pp;
@@ -1017,6 +1084,16 @@ eth_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
 	return 0;
 }
 
+/* Timestamp values in receive packets from libpcap are in UTC */
+static int
+eth_rx_clock(struct rte_eth_dev *dev __rte_unused, uint64_t *timestamp)
+{
+	struct timespec cur_time;
+
+	timespec_get(&cur_time, TIME_UTC);
+	*timestamp = rte_timespec_to_ns(&cur_time);
+	return 0;
+}
 
 static const struct eth_dev_ops ops = {
 	.dev_start = eth_dev_start,
@@ -1034,6 +1111,7 @@ static const struct eth_dev_ops ops = {
 	.mtu_set = eth_mtu_set,
 	.stats_get = eth_stats_get,
 	.stats_reset = eth_stats_reset,
+	.read_clock = eth_rx_clock,
 };
 
 static int
@@ -1434,15 +1512,13 @@ pmd_pcap_probe(struct rte_vdev_device *dev)
 	name = rte_vdev_device_name(dev);
 	PMD_LOG(INFO, "Initializing pmd_pcap for %s", name);
 
-	timespec_get(&start_time, TIME_UTC);
-	start_cycles = rte_get_timer_cycles();
-	hz = rte_get_timer_hz();
+	/* Record info for timestamps on first probe */
+	if (hz == 0) {
+		timespec_get(&start_time, TIME_UTC);
+		start_cycles = rte_get_timer_cycles();
 
-	ret = rte_mbuf_dyn_rx_timestamp_register(&timestamp_dynfield_offset,
-			&timestamp_rx_dynflag);
-	if (ret != 0) {
-		PMD_LOG(ERR, "Failed to register Rx timestamp field/flag");
-		return -1;
+		hz = rte_get_timer_hz();
+		hz_inv = rte_reciprocal_value_u64(hz);
 	}
 
 	if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
-- 
2.51.0


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

* [PATCH v2 6/9] net/pcap: remove global variables
  2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
                     ` (4 preceding siblings ...)
  2026-01-09  1:16   ` [PATCH v2 5/9] net/pcap: support nanosecond timestamp precision Stephen Hemminger
@ 2026-01-09  1:16   ` Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 7/9] net/pcap: avoid use of volatile Stephen Hemminger
                     ` (2 subsequent siblings)
  8 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-09  1:16 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Marat Khalili

Localize variables where possible.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Marat Khalili <marat.khalili@huawei.com>
---
 drivers/net/pcap/pcap_ethdev.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index 25e78f1e3a..30734cc09d 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -40,12 +40,10 @@
 
 #define RTE_PMD_PCAP_MAX_QUEUES 16
 
-static char errbuf[PCAP_ERRBUF_SIZE];
 static struct timespec start_time;
 static uint64_t start_cycles;
 static uint64_t hz;
 static struct rte_reciprocal_u64 hz_inv;
-static uint8_t iface_idx;
 
 static uint64_t timestamp_rx_dynflag;
 static int timestamp_dynfield_offset = -1;
@@ -531,6 +529,8 @@ eth_pcap_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 static inline int
 open_iface_live(const char *iface, pcap_t **pcap)
 {
+	char errbuf[PCAP_ERRBUF_SIZE];
+
 	pcap_t *pc = pcap_create(iface, errbuf);
 	if (pc == NULL) {
 		PMD_LOG(ERR, "Couldn't create %s: %s", iface, errbuf);
@@ -626,6 +626,8 @@ open_single_tx_pcap(const char *pcap_filename, pcap_dumper_t **dumper)
 static int
 open_single_rx_pcap(const char *pcap_filename, pcap_t **pcap)
 {
+	char errbuf[PCAP_ERRBUF_SIZE];
+
 	*pcap = pcap_open_offline_with_tstamp_precision(pcap_filename,
 							PCAP_TSTAMP_PRECISION_NANO, errbuf);
 	if (*pcap == NULL) {
@@ -1327,6 +1329,7 @@ pmd_init_internals(struct rte_vdev_device *vdev,
 	 * derived from: 'locally administered':'p':'c':'a':'p':'iface_idx'
 	 * where the middle 4 characters are converted to hex.
 	 */
+	static uint8_t iface_idx;
 	(*internals)->eth_addr = (struct rte_ether_addr) {
 		.addr_bytes = { 0x02, 0x70, 0x63, 0x61, 0x70, iface_idx++ }
 	};
-- 
2.51.0


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

* [PATCH v2 7/9] net/pcap: avoid use of volatile
  2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
                     ` (5 preceding siblings ...)
  2026-01-09  1:16   ` [PATCH v2 6/9] net/pcap: remove global variables Stephen Hemminger
@ 2026-01-09  1:16   ` Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 8/9] net/pcap: support MAC address set Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 9/9] test: add test for pcap PMD Stephen Hemminger
  8 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-09  1:16 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Using volatile for statistics is not necessary since only one
thread is allowed to operate on a queue at a time.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index 30734cc09d..bf03f431dd 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -49,10 +49,10 @@ static uint64_t timestamp_rx_dynflag;
 static int timestamp_dynfield_offset = -1;
 
 struct queue_stat {
-	volatile unsigned long pkts;
-	volatile unsigned long bytes;
-	volatile unsigned long err_pkts;
-	volatile unsigned long rx_nombuf;
+	uint64_t pkts;
+	uint64_t bytes;
+	uint64_t err_pkts;
+	uint64_t rx_nombuf;
 };
 
 struct queue_missed_stat {
@@ -826,11 +826,11 @@ eth_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *stats,
 	      struct eth_queue_stats *qstats)
 {
 	unsigned int i;
-	unsigned long rx_packets_total = 0, rx_bytes_total = 0;
-	unsigned long rx_missed_total = 0;
-	unsigned long rx_nombuf_total = 0, rx_err_total = 0;
-	unsigned long tx_packets_total = 0, tx_bytes_total = 0;
-	unsigned long tx_packets_err_total = 0;
+	uint64_t rx_packets_total = 0, rx_bytes_total = 0;
+	uint64_t rx_missed_total = 0;
+	uint64_t rx_nombuf_total = 0, rx_err_total = 0;
+	uint64_t tx_packets_total = 0, tx_bytes_total = 0;
+	uint64_t tx_packets_err_total = 0;
 	const struct pmd_internals *internal = dev->data->dev_private;
 
 	for (i = 0; i < RTE_ETHDEV_QUEUE_STAT_CNTRS &&
-- 
2.51.0


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

* [PATCH v2 8/9] net/pcap: support MAC address set
  2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
                     ` (6 preceding siblings ...)
  2026-01-09  1:16   ` [PATCH v2 7/9] net/pcap: avoid use of volatile Stephen Hemminger
@ 2026-01-09  1:16   ` Stephen Hemminger
  2026-01-09  1:16   ` [PATCH v2 9/9] test: add test for pcap PMD Stephen Hemminger
  8 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-09  1:16 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Use rte_ether_addr structure to avoid memcpy and void *.

When using pcap on a single interface, it is possible for
driver to proxy the mac address set operation.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/pcap/pcap_ethdev.c        | 19 ++++++++++++++---
 drivers/net/pcap/pcap_osdep.h         |  1 +
 drivers/net/pcap/pcap_osdep_freebsd.c | 23 ++++++++++++++++++++
 drivers/net/pcap/pcap_osdep_linux.c   | 30 ++++++++++++++++++++++++++-
 4 files changed, 69 insertions(+), 4 deletions(-)

diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index bf03f431dd..824cdabab6 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -1086,6 +1086,18 @@ eth_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)
 	return 0;
 }
 
+static int
+eth_dev_macaddr_set(struct rte_eth_dev *dev, struct rte_ether_addr *addr)
+{
+	struct pmd_internals *internals = dev->data->dev_private;
+
+	if (internals->single_iface)
+		return osdep_iface_mac_set(internals->if_index, addr);
+	else
+		return -ENOTSUP;
+}
+
+
 /* Timestamp values in receive packets from libpcap are in UTC */
 static int
 eth_rx_clock(struct rte_eth_dev *dev __rte_unused, uint64_t *timestamp)
@@ -1110,6 +1122,7 @@ static const struct eth_dev_ops ops = {
 	.rx_queue_stop = eth_rx_queue_stop,
 	.tx_queue_stop = eth_tx_queue_stop,
 	.link_update = eth_link_update,
+	.mac_addr_set = eth_dev_macaddr_set,
 	.mtu_set = eth_mtu_set,
 	.stats_get = eth_stats_get,
 	.stats_reset = eth_stats_reset,
@@ -1357,9 +1370,9 @@ pmd_init_internals(struct rte_vdev_device *vdev,
 
 static int
 eth_pcap_update_mac(const char *if_name, struct rte_eth_dev *eth_dev,
-		const unsigned int numa_node)
+		    const unsigned int numa_node)
 {
-	void *mac_addrs;
+	struct rte_ether_addr *mac_addrs;
 	struct rte_ether_addr mac;
 
 	if (osdep_iface_mac_get(if_name, &mac) < 0)
@@ -1370,7 +1383,7 @@ eth_pcap_update_mac(const char *if_name, struct rte_eth_dev *eth_dev,
 		return -1;
 
 	PMD_LOG(INFO, "Setting phy MAC for %s", if_name);
-	rte_memcpy(mac_addrs, mac.addr_bytes, RTE_ETHER_ADDR_LEN);
+	rte_ether_addr_copy(&mac, mac_addrs);
 	eth_dev->data->mac_addrs = mac_addrs;
 	return 0;
 }
diff --git a/drivers/net/pcap/pcap_osdep.h b/drivers/net/pcap/pcap_osdep.h
index 3c8b7ff27b..00944e0843 100644
--- a/drivers/net/pcap/pcap_osdep.h
+++ b/drivers/net/pcap/pcap_osdep.h
@@ -14,6 +14,7 @@ extern int eth_pcap_logtype;
 
 int osdep_iface_index_get(const char *name);
 int osdep_iface_mac_get(const char *name, struct rte_ether_addr *mac);
+int osdep_iface_mac_set(int ifindex, const struct rte_ether_addr *mac);
 int osdep_iface_mtu_set(int index, uint16_t mtu);
 
 #endif
diff --git a/drivers/net/pcap/pcap_osdep_freebsd.c b/drivers/net/pcap/pcap_osdep_freebsd.c
index 0279dbf00b..39227da63a 100644
--- a/drivers/net/pcap/pcap_osdep_freebsd.c
+++ b/drivers/net/pcap/pcap_osdep_freebsd.c
@@ -58,6 +58,29 @@ osdep_iface_mac_get(const char *if_name, struct rte_ether_addr *mac)
 	return 0;
 }
 
+int
+osdep_iface_mac_set(int ifindex, const struct rte_ether_addr *mac)
+{
+	char ifname[IFNAMSIZ];
+
+	if (if_indextoname(ifindex, ifname) == NULL)
+		return -errno;
+
+	int s = socket(AF_INET, SOCK_DGRAM, 0);
+	if (s < 0)
+		return -errno;
+
+	struct ifreq ifr = { 0 };
+	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
+	ifr.ifr_addr.sa_family = AF_LINK;
+	memcpy(ifr.ifr_addr.sa_data, mac, sizeof(*mac));
+
+	int ret = ioctl(s, SIOCSIFLLADDR, &ifr);
+	close(s);
+
+	return (ret < 0) ? -errno : 0;
+}
+
 int osdep_iface_mtu_set(int ifindex, uint16_t mtu)
 {
 	char ifname[IFNAMSIZ];
diff --git a/drivers/net/pcap/pcap_osdep_linux.c b/drivers/net/pcap/pcap_osdep_linux.c
index d180e9b4b4..d5a3855a0a 100644
--- a/drivers/net/pcap/pcap_osdep_linux.c
+++ b/drivers/net/pcap/pcap_osdep_linux.c
@@ -4,13 +4,18 @@
  * All rights reserved.
  */
 
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
 #include <net/if.h>
+#include <net/if_arp.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
-#include <unistd.h>
 
 #include <rte_memcpy.h>
 #include <rte_string_fns.h>
+#include <rte_ether.h>
 
 #include "pcap_osdep.h"
 
@@ -41,6 +46,29 @@ osdep_iface_mac_get(const char *if_name, struct rte_ether_addr *mac)
 	return 0;
 }
 
+int
+osdep_iface_mac_set(int ifindex, const struct rte_ether_addr *mac)
+{
+	char ifname[IFNAMSIZ];
+
+	if (if_indextoname(ifindex, ifname) == NULL)
+		return -errno;
+
+	int s = socket(AF_INET, SOCK_DGRAM, 0);
+	if (s < 0)
+		return -errno;
+
+	struct ifreq ifr = { 0 };
+	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
+	ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
+	memcpy(ifr.ifr_hwaddr.sa_data, mac, sizeof(*mac));
+
+	int ret = ioctl(s, SIOCSIFHWADDR, &ifr);
+	close(s);
+
+	return (ret < 0) ? -errno : 0;
+}
+
 int
 osdep_iface_mtu_set(int ifindex, uint16_t mtu)
 {
-- 
2.51.0


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

* [PATCH v2 9/9] test: add test for pcap PMD
  2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
                     ` (7 preceding siblings ...)
  2026-01-09  1:16   ` [PATCH v2 8/9] net/pcap: support MAC address set Stephen Hemminger
@ 2026-01-09  1:16   ` Stephen Hemminger
  8 siblings, 0 replies; 28+ messages in thread
From: Stephen Hemminger @ 2026-01-09  1:16 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

This test was generated by Claude AI with some prompting and
pointing at existing ring PMD test. It tests basic operations,
timestamps, jumbo frame, and multiple queues.

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

diff --git a/app/test/meson.build b/app/test/meson.build
index efec42a6bf..237bc271a7 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -141,6 +141,7 @@ source_file_deps = {
     'test_per_lcore.c': [],
     'test_pflock.c': [],
     'test_pie.c': ['sched'],
+    'test_pmd_pcap.c': ['net_pcap', 'ethdev',  'bus_vdev']  + packet_burst_generator_deps,
     'test_pmd_perf.c': ['ethdev', 'net'] + packet_burst_generator_deps,
     'test_pmd_ring.c': ['net_ring', 'ethdev', 'bus_vdev'],
     'test_pmd_ring_perf.c': ['ethdev', 'net_ring', 'bus_vdev'],
@@ -216,6 +217,7 @@ source_file_deps = {
 source_file_ext_deps = {
     'test_compressdev.c': ['zlib'],
     'test_pcapng.c': ['pcap'],
+    'test_pmd_pcap.c': ['pcap'],
 }
 
 def_lib = get_option('default_library')
diff --git a/app/test/test_pmd_pcap.c b/app/test/test_pmd_pcap.c
new file mode 100644
index 0000000000..ac02e30aa1
--- /dev/null
+++ b/app/test/test_pmd_pcap.c
@@ -0,0 +1,1846 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+#include "test.h"
+#include "packet_burst_generator.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <pcap/pcap.h>
+
+#include <rte_ethdev.h>
+#include <rte_bus_vdev.h>
+#include <rte_mbuf.h>
+#include <rte_mbuf_dyn.h>
+#include <rte_mempool.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_udp.h>
+
+#define SOCKET0 0
+#define RING_SIZE 256
+#define NB_MBUF 1024
+#define NUM_PACKETS 64
+#define MAX_PKT_BURST 32
+#define PCAP_SNAPLEN 65535
+
+/* Packet sizes to test */
+#define PKT_SIZE_MIN 60
+#define PKT_SIZE_SMALL 128
+#define PKT_SIZE_MEDIUM 512
+#define PKT_SIZE_LARGE 1024
+#define PKT_SIZE_MTU 1500
+#define PKT_SIZE_JUMBO 9000
+
+static struct rte_mempool *mp;
+
+/* Timestamp dynamic field access */
+static int timestamp_dynfield_offset = -1;
+static uint64_t timestamp_rx_dynflag;
+
+/* Temporary file paths */
+static char tx_pcap_path[PATH_MAX];
+static char rx_pcap_path[PATH_MAX];
+static char infinite_pcap_path[PATH_MAX];
+static char timestamp_pcap_path[PATH_MAX];
+static char varied_pcap_path[PATH_MAX];
+static char jumbo_pcap_path[PATH_MAX];
+
+/* Constants for multi-queue tests */
+#define MULTI_QUEUE_NUM_QUEUES   4U
+#define MULTI_QUEUE_NUM_PACKETS  100U
+#define MULTI_QUEUE_BURST_SIZE   32U
+
+static char multi_tx_pcap_paths[MULTI_QUEUE_NUM_QUEUES][PATH_MAX];
+static char multi_rx_pcap_path[PATH_MAX];
+
+/* MAC addresses for packet generation */
+static struct rte_ether_addr src_mac;
+static struct rte_ether_addr dst_mac = {
+	.addr_bytes = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }
+};
+
+/* Sample Ethernet/IPv4/UDP packet for testing */
+static const uint8_t test_packet[] = {
+	/* Ethernet header */
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  /* dst MAC (broadcast) */
+	0x00, 0x11, 0x22, 0x33, 0x44, 0x55,  /* src MAC */
+	0x08, 0x00,                          /* EtherType: IPv4 */
+	/* IPv4 header */
+	0x45, 0x00, 0x00, 0x2e,              /* ver, ihl, tos, len */
+	0x00, 0x01, 0x00, 0x00,              /* id, flags, frag */
+	0x40, 0x11, 0x00, 0x00,              /* ttl, proto(UDP), csum */
+	0x0a, 0x00, 0x00, 0x01,              /* src: 10.0.0.1 */
+	0x0a, 0x00, 0x00, 0x02,              /* dst: 10.0.0.2 */
+	/* UDP header */
+	0x04, 0xd2, 0x04, 0xd2,              /* sport, dport (1234) */
+	0x00, 0x1a, 0x00, 0x00,              /* len, csum */
+	/* Payload: "Test packet!" */
+	0x54, 0x65, 0x73, 0x74, 0x20, 0x70,
+	0x61, 0x63, 0x6b, 0x65, 0x74, 0x21
+};
+
+/* Helper: Get timestamp from mbuf using dynamic field */
+static inline rte_mbuf_timestamp_t
+mbuf_timestamp_get(const struct rte_mbuf *mbuf)
+{
+	return *RTE_MBUF_DYNFIELD(mbuf, timestamp_dynfield_offset, rte_mbuf_timestamp_t *);
+}
+
+/* Helper: Check if mbuf has valid timestamp */
+static inline int
+mbuf_has_timestamp(const struct rte_mbuf *mbuf)
+{
+	return (mbuf->ol_flags & timestamp_rx_dynflag) != 0;
+}
+
+/* Helper: Initialize timestamp dynamic field access */
+static int
+timestamp_init(void)
+{
+	int offset;
+
+	offset = rte_mbuf_dynfield_lookup(RTE_MBUF_DYNFIELD_TIMESTAMP_NAME, NULL);
+	if (offset < 0) {
+		printf("Timestamp dynfield not registered\n");
+		return -1;
+	}
+	timestamp_dynfield_offset = offset;
+
+	offset = rte_mbuf_dynflag_lookup(RTE_MBUF_DYNFLAG_RX_TIMESTAMP_NAME, NULL);
+	if (offset < 0) {
+		printf("Timestamp dynflag not registered\n");
+		return -1;
+	}
+	timestamp_rx_dynflag = RTE_BIT64(offset);
+	return 0;
+}
+
+/*
+ * Helper: Create a unique temporary file path
+ */
+static int
+create_temp_path(char *buf, size_t buflen, const char *prefix)
+{
+	int fd;
+
+	snprintf(buf, buflen, "/tmp/%s_XXXXXX.pcap", prefix);
+	fd = mkstemps(buf, 5);  /* 5 = strlen(".pcap") */
+	if (fd < 0)
+		return -1;
+	close(fd);
+	return 0;
+}
+
+/*
+ * Helper: Create a pcap file with test packets using libpcap
+ */
+static int
+create_test_pcap(const char *path, unsigned int num_pkts)
+{
+	pcap_t *pd;
+	pcap_dumper_t *dumper;
+	struct pcap_pkthdr hdr;
+	unsigned int i;
+
+	pd = pcap_open_dead(DLT_EN10MB, PCAP_SNAPLEN);
+	if (pd == NULL) {
+		printf("pcap_open_dead failed\n");
+		return -1;
+	}
+
+	dumper = pcap_dump_open(pd, path);
+	if (dumper == NULL) {
+		printf("pcap_dump_open failed: %s\n", pcap_geterr(pd));
+		pcap_close(pd);
+		return -1;
+	}
+
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.caplen = sizeof(test_packet);
+	hdr.len = sizeof(test_packet);
+
+	for (i = 0; i < num_pkts; i++) {
+		hdr.ts.tv_sec = i;
+		hdr.ts.tv_usec = 0;
+		pcap_dump((u_char *)dumper, &hdr, test_packet);
+	}
+
+	pcap_dump_close(dumper);
+	pcap_close(pd);
+	return 0;
+}
+
+/*
+ * Helper: Create pcap file with packets of specified size
+ */
+static int
+create_sized_pcap(const char *path, unsigned int num_pkts, uint16_t pkt_size)
+{
+	pcap_t *pd;
+	pcap_dumper_t *dumper;
+	struct pcap_pkthdr hdr;
+	uint8_t *pkt_data;
+	unsigned int i;
+
+	/* Minimum valid ethernet frame */
+	if (pkt_size < 60)
+		pkt_size = 60;
+
+	pkt_data = calloc(1, pkt_size);
+	if (pkt_data == NULL)
+		return -1;
+
+	/* Build ethernet header */
+	struct rte_ether_hdr *eth_hdr = (struct rte_ether_hdr *)pkt_data;
+	rte_ether_addr_copy(&src_mac, &eth_hdr->src_addr);
+	rte_ether_addr_copy(&dst_mac, &eth_hdr->dst_addr);
+	eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+	/* Build IP header */
+	struct rte_ipv4_hdr *ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
+	uint16_t ip_len = pkt_size - sizeof(struct rte_ether_hdr);
+	ip_hdr->version_ihl = RTE_IPV4_VHL_DEF;
+	ip_hdr->total_length = rte_cpu_to_be_16(ip_len);
+	ip_hdr->time_to_live = 64;
+	ip_hdr->next_proto_id = IPPROTO_UDP;
+	ip_hdr->src_addr = rte_cpu_to_be_32(IPV4_ADDR(10, 0, 0, 1));
+	ip_hdr->dst_addr = rte_cpu_to_be_32(IPV4_ADDR(10, 0, 0, 2));
+	ip_hdr->hdr_checksum = 0;
+	ip_hdr->hdr_checksum = rte_ipv4_cksum(ip_hdr);
+
+	/* Build UDP header */
+	struct rte_udp_hdr *udp_hdr = (struct rte_udp_hdr *)(ip_hdr + 1);
+	uint16_t udp_len = ip_len - sizeof(struct rte_ipv4_hdr);
+	udp_hdr->src_port = rte_cpu_to_be_16(1234);
+	udp_hdr->dst_port = rte_cpu_to_be_16(1234);
+	udp_hdr->dgram_len = rte_cpu_to_be_16(udp_len);
+	udp_hdr->dgram_cksum = 0;
+
+	/* Fill payload with pattern */
+	uint8_t *payload = (uint8_t *)(udp_hdr + 1);
+	uint16_t payload_len = udp_len - sizeof(struct rte_udp_hdr);
+	for (uint16_t j = 0; j < payload_len; j++)
+		payload[j] = (uint8_t)(j & 0xFF);
+
+	pd = pcap_open_dead(DLT_EN10MB, PCAP_SNAPLEN);
+	if (pd == NULL) {
+		free(pkt_data);
+		return -1;
+	}
+
+	dumper = pcap_dump_open(pd, path);
+	if (dumper == NULL) {
+		pcap_close(pd);
+		free(pkt_data);
+		return -1;
+	}
+
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.caplen = pkt_size;
+	hdr.len = pkt_size;
+
+	for (i = 0; i < num_pkts; i++) {
+		hdr.ts.tv_sec = i;
+		hdr.ts.tv_usec = 0;
+		/* Vary sequence byte in payload */
+		payload[0] = (uint8_t)(i & 0xFF);
+		pcap_dump((u_char *)dumper, &hdr, pkt_data);
+	}
+
+	pcap_dump_close(dumper);
+	pcap_close(pd);
+	free(pkt_data);
+	return 0;
+}
+
+/*
+ * Helper: Create pcap file with varied packet sizes
+ */
+static int
+create_varied_pcap(const char *path, unsigned int num_pkts)
+{
+	static const uint16_t sizes[] = {
+		PKT_SIZE_MIN, PKT_SIZE_SMALL, PKT_SIZE_MEDIUM,
+		PKT_SIZE_LARGE, PKT_SIZE_MTU
+	};
+	pcap_t *pd;
+	pcap_dumper_t *dumper;
+	struct pcap_pkthdr hdr;
+	uint8_t *pkt_data;
+	unsigned int i;
+
+	pkt_data = calloc(1, PKT_SIZE_MTU);
+	if (pkt_data == NULL)
+		return -1;
+
+	pd = pcap_open_dead(DLT_EN10MB, PCAP_SNAPLEN);
+	if (pd == NULL) {
+		free(pkt_data);
+		return -1;
+	}
+
+	dumper = pcap_dump_open(pd, path);
+	if (dumper == NULL) {
+		pcap_close(pd);
+		free(pkt_data);
+		return -1;
+	}
+
+	for (i = 0; i < num_pkts; i++) {
+		uint16_t pkt_size = sizes[i % RTE_DIM(sizes)];
+
+		memset(pkt_data, 0, pkt_size);
+
+		/* Build ethernet header */
+		struct rte_ether_hdr *eth_hdr = (struct rte_ether_hdr *)pkt_data;
+		rte_ether_addr_copy(&src_mac, &eth_hdr->src_addr);
+		rte_ether_addr_copy(&dst_mac, &eth_hdr->dst_addr);
+		eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+		/* Build IP header */
+		struct rte_ipv4_hdr *ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
+		uint16_t ip_len = pkt_size - sizeof(struct rte_ether_hdr);
+		ip_hdr->version_ihl = RTE_IPV4_VHL_DEF;
+		ip_hdr->total_length = rte_cpu_to_be_16(ip_len);
+		ip_hdr->time_to_live = 64;
+		ip_hdr->next_proto_id = IPPROTO_UDP;
+		ip_hdr->src_addr = rte_cpu_to_be_32(IPV4_ADDR(10, 0, 0, 1));
+		ip_hdr->dst_addr = rte_cpu_to_be_32(IPV4_ADDR(10, 0, 0, 2));
+		ip_hdr->hdr_checksum = 0;
+		ip_hdr->hdr_checksum = rte_ipv4_cksum(ip_hdr);
+
+		/* Build UDP header */
+		struct rte_udp_hdr *udp_hdr = (struct rte_udp_hdr *)(ip_hdr + 1);
+		uint16_t udp_len = ip_len - sizeof(struct rte_ipv4_hdr);
+		udp_hdr->src_port = rte_cpu_to_be_16(1234);
+		udp_hdr->dst_port = rte_cpu_to_be_16(1234);
+		udp_hdr->dgram_len = rte_cpu_to_be_16(udp_len);
+
+		memset(&hdr, 0, sizeof(hdr));
+		hdr.ts.tv_sec = i;
+		hdr.caplen = pkt_size;
+		hdr.len = pkt_size;
+
+		pcap_dump((u_char *)dumper, &hdr, pkt_data);
+	}
+
+	pcap_dump_close(dumper);
+	pcap_close(pd);
+	free(pkt_data);
+	return 0;
+}
+
+/*
+ * Helper: Create pcap file with specific timestamps for testing
+ */
+static int
+create_timestamped_pcap(const char *path, unsigned int num_pkts,
+			uint32_t base_sec, uint32_t usec_increment)
+{
+	pcap_t *pd;
+	pcap_dumper_t *dumper;
+	struct pcap_pkthdr hdr;
+	unsigned int i;
+
+	pd = pcap_open_dead_with_tstamp_precision(DLT_EN10MB, PCAP_SNAPLEN,
+						  PCAP_TSTAMP_PRECISION_MICRO);
+	if (pd == NULL)
+		return -1;
+
+	dumper = pcap_dump_open(pd, path);
+	if (dumper == NULL) {
+		pcap_close(pd);
+		return -1;
+	}
+
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.caplen = sizeof(test_packet);
+	hdr.len = sizeof(test_packet);
+
+	for (i = 0; i < num_pkts; i++) {
+		uint64_t total_usec = (uint64_t)i * usec_increment;
+		hdr.ts.tv_sec = base_sec + total_usec / 1000000;
+		hdr.ts.tv_usec = total_usec % 1000000;
+		pcap_dump((u_char *)dumper, &hdr, test_packet);
+	}
+
+	pcap_dump_close(dumper);
+	pcap_close(pd);
+	return 0;
+}
+
+/*
+ * Helper: Count packets in a pcap file using libpcap
+ */
+static int
+count_pcap_packets(const char *path)
+{
+	pcap_t *pd;
+	char errbuf[PCAP_ERRBUF_SIZE];
+	struct pcap_pkthdr *hdr;
+	const u_char *data;
+	int count = 0;
+
+	pd = pcap_open_offline(path, errbuf);
+	if (pd == NULL)
+		return -1;
+
+	while (pcap_next_ex(pd, &hdr, &data) == 1)
+		count++;
+
+	pcap_close(pd);
+	return count;
+}
+
+/*
+ * Helper: Get packet sizes from pcap file
+ */
+static int
+get_pcap_packet_sizes(const char *path, uint16_t *sizes, unsigned int max_pkts)
+{
+	pcap_t *pd;
+	char errbuf[PCAP_ERRBUF_SIZE];
+	struct pcap_pkthdr *hdr;
+	const u_char *data;
+	unsigned int count = 0;
+
+	pd = pcap_open_offline(path, errbuf);
+	if (pd == NULL)
+		return -1;
+
+	while (pcap_next_ex(pd, &hdr, &data) == 1 && count < max_pkts) {
+		sizes[count] = hdr->caplen;
+		count++;
+	}
+
+	pcap_close(pd);
+	return count;
+}
+
+/*
+ * Helper: Configure and start a pcap ethdev port
+ */
+static int
+setup_pcap_port(uint16_t port)
+{
+	struct rte_eth_conf port_conf = {
+		.rxmode.offloads = RTE_ETH_RX_OFFLOAD_TIMESTAMP,
+	};
+	int ret;
+
+	ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+	TEST_ASSERT(ret == 0, "Failed to configure port %u: %s",
+		    port, rte_strerror(-ret));
+
+	ret = rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, mp);
+	TEST_ASSERT(ret == 0, "Failed to setup RX queue on port %u: %s",
+		    port, rte_strerror(-ret));
+
+	ret = rte_eth_tx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL);
+	TEST_ASSERT(ret == 0, "Failed to setup TX queue on port %u: %s",
+		    port, rte_strerror(-ret));
+
+	ret = rte_eth_dev_start(port);
+	TEST_ASSERT(ret == 0, "Failed to start port %u: %s",
+		    port, rte_strerror(-ret));
+
+	return 0;
+}
+
+/*
+ * Helper: Create a pcap vdev and return its port ID
+ */
+static int
+create_pcap_vdev(const char *name, const char *devargs, uint16_t *port_id)
+{
+	int ret;
+
+	ret = rte_vdev_init(name, devargs);
+	TEST_ASSERT(ret == 0, "Failed to create vdev %s: %s",
+		    name, rte_strerror(-ret));
+
+	ret = rte_eth_dev_get_port_by_name(name, port_id);
+	TEST_ASSERT(ret == 0, "Failed to get port ID for %s", name);
+
+	return 0;
+}
+
+/*
+ * Helper: Cleanup a pcap vdev
+ */
+static void
+cleanup_pcap_vdev(const char *name, uint16_t port_id)
+{
+	rte_eth_dev_stop(port_id);
+	rte_vdev_uninit(name);
+}
+
+/*
+ * Helper: Generate test packets using packet_burst_generator
+ */
+static int
+generate_test_packets(struct rte_mempool *pool, struct rte_mbuf **mbufs,
+		      unsigned int count, uint8_t pkt_len)
+{
+	struct rte_ether_hdr eth_hdr;
+	struct rte_ipv4_hdr ip_hdr;
+	struct rte_udp_hdr udp_hdr;
+	uint16_t ip_pkt_data_len;
+	int nb_pkt;
+
+	/* Initialize ethernet header */
+	initialize_eth_header(&eth_hdr, &src_mac, &dst_mac,
+			      RTE_ETHER_TYPE_IPV4, 0, 0);
+
+	/* Calculate IP payload length (total - eth - ip headers) */
+	ip_pkt_data_len = pkt_len - sizeof(struct rte_ether_hdr) -
+			  sizeof(struct rte_ipv4_hdr);
+
+	/* Initialize UDP header */
+	initialize_udp_header(&udp_hdr, 1234, 1234,
+			      ip_pkt_data_len - sizeof(struct rte_udp_hdr));
+
+	/* Initialize IPv4 header */
+	initialize_ipv4_header(&ip_hdr, IPV4_ADDR(10, 0, 0, 1),
+			       IPV4_ADDR(10, 0, 0, 2), ip_pkt_data_len);
+
+	/* Generate packet burst */
+	nb_pkt = generate_packet_burst(pool, mbufs, &eth_hdr, 0,
+				       &ip_hdr, 1, &udp_hdr,
+				       count, pkt_len, 1);
+
+	return nb_pkt;
+}
+
+/*
+ * Helper: Allocate mbufs and fill with test packet data (legacy method)
+ */
+static int
+alloc_test_mbufs(struct rte_mbuf **mbufs, unsigned int count)
+{
+	unsigned int i;
+	int ret;
+
+	ret = rte_pktmbuf_alloc_bulk(mp, mbufs, count);
+	if (ret != 0)
+		return -1;
+
+	for (i = 0; i < count; i++) {
+		rte_memcpy(rte_pktmbuf_mtod(mbufs[i], void *),
+			   test_packet, sizeof(test_packet));
+		mbufs[i]->data_len = sizeof(test_packet);
+		mbufs[i]->pkt_len = sizeof(test_packet);
+	}
+	return 0;
+}
+
+/*
+ * Helper: Allocate a multi-segment mbuf for jumbo frames
+ * Returns the head mbuf with chained segments, or NULL on failure
+ */
+static struct rte_mbuf *
+alloc_jumbo_mbuf(uint32_t pkt_len, uint8_t fill_byte)
+{
+	struct rte_mbuf *head = NULL;
+	struct rte_mbuf **prev = &head;
+	uint32_t remaining = pkt_len;
+	uint16_t nb_segs = 0;
+
+	while (remaining > 0) {
+		struct rte_mbuf *seg = rte_pktmbuf_alloc(mp);
+		uint16_t seg_size;
+
+		if (seg == NULL) {
+			rte_pktmbuf_free(head);
+			return NULL;
+		}
+
+		seg_size = RTE_MIN(remaining, rte_pktmbuf_tailroom(seg));
+		seg->data_len = seg_size;
+
+		/* Fill segment with pattern */
+		memset(rte_pktmbuf_mtod(seg, void *), fill_byte, seg_size);
+
+		*prev = seg;
+		prev = &seg->next;
+		remaining -= seg_size;
+		nb_segs++;
+	}
+
+	if (head != NULL) {
+		head->pkt_len = pkt_len;
+		head->nb_segs = nb_segs;
+	}
+
+	return head;
+}
+
+/*
+ * Helper: Receive packets from port (no retry needed for file-based RX)
+ */
+static int
+receive_packets(uint16_t port, struct rte_mbuf **mbufs,
+		unsigned int max_pkts, unsigned int *received)
+{
+	unsigned int total = 0;
+
+	while (total < max_pkts) {
+		uint16_t nb_rx = rte_eth_rx_burst(port, 0, &mbufs[total], max_pkts - total);
+		if (nb_rx == 0)
+			break;
+		total += nb_rx;
+	}
+	*received = total;
+	return 0;
+}
+
+/*
+ * Helper: Verify mbuf contains expected test packet
+ */
+static int
+verify_packet(struct rte_mbuf *mbuf)
+{
+	TEST_ASSERT_EQUAL(rte_pktmbuf_data_len(mbuf), sizeof(test_packet),
+			  "Packet length mismatch");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(rte_pktmbuf_mtod(mbuf, void *),
+				      test_packet, sizeof(test_packet),
+				      "Packet data mismatch");
+	return 0;
+}
+
+/*
+ * Helper: Check if network interface exists
+ */
+static int
+iface_exists(const char *name)
+{
+	struct ifreq ifr;
+	int sock, ret;
+
+	sock = socket(AF_INET, SOCK_DGRAM, 0);
+	if (sock < 0)
+		return 0;
+
+	memset(&ifr, 0, sizeof(ifr));
+	strlcpy(ifr.ifr_name, name, IFNAMSIZ);
+	ret = ioctl(sock, SIOCGIFINDEX, &ifr);
+	close(sock);
+	return ret == 0;
+}
+
+/*
+ * Helper: Find a usable test interface
+ */
+static const char *
+find_test_iface(void)
+{
+	if (iface_exists("dummy0"))
+		return "dummy0";
+	if (iface_exists("lo"))
+		return "lo";
+	return NULL;
+}
+
+/*
+ * Test: Transmit packets to pcap file
+ */
+static int
+test_tx_to_file(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	char devargs[256];
+	uint16_t port_id;
+	int nb_tx, pkt_count;
+
+	printf("Testing TX to pcap file\n");
+
+	TEST_ASSERT(create_temp_path(tx_pcap_path, sizeof(tx_pcap_path),
+				     "pcap_tx") == 0,
+		    "Failed to create temp file path");
+
+	snprintf(devargs, sizeof(devargs), "tx_pcap=%s", tx_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_tx", devargs, &port_id) == 0,
+		    "Failed to create TX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup TX port");
+	TEST_ASSERT(alloc_test_mbufs(mbufs, NUM_PACKETS) == 0,
+		    "Failed to allocate mbufs");
+
+	nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, NUM_PACKETS);
+	TEST_ASSERT_EQUAL(nb_tx, NUM_PACKETS,
+			  "TX burst failed: sent %d/%d", nb_tx, NUM_PACKETS);
+
+	cleanup_pcap_vdev("net_pcap_tx", port_id);
+
+	pkt_count = count_pcap_packets(tx_pcap_path);
+	TEST_ASSERT_EQUAL(pkt_count, NUM_PACKETS,
+			  "Pcap file has %d packets, expected %d",
+			  pkt_count, NUM_PACKETS);
+
+	printf("TX to file PASSED: %d packets written\n", NUM_PACKETS);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Receive packets from pcap file
+ * Uses output from TX test as input
+ */
+static int
+test_rx_from_file(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int received, i;
+
+	printf("Testing RX from pcap file\n");
+
+	/* Create input file if TX test didn't run */
+	if (access(tx_pcap_path, F_OK) != 0) {
+		TEST_ASSERT(create_temp_path(tx_pcap_path, sizeof(tx_pcap_path),
+					     "pcap_rx_input") == 0,
+			    "Failed to create temp path");
+		TEST_ASSERT(create_test_pcap(tx_pcap_path, NUM_PACKETS) == 0,
+			    "Failed to create input pcap");
+	}
+
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s", tx_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_rx", devargs, &port_id) == 0,
+		    "Failed to create RX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup RX port");
+
+	receive_packets(port_id, mbufs, NUM_PACKETS, &received);
+	TEST_ASSERT_EQUAL(received, NUM_PACKETS,
+			  "Received %u packets, expected %d", received, NUM_PACKETS);
+
+	for (i = 0; i < received; i++) {
+		TEST_ASSERT(verify_packet(mbufs[i]) == 0,
+			    "Packet %u verification failed", i);
+	}
+	rte_pktmbuf_free_bulk(mbufs, received);
+
+	cleanup_pcap_vdev("net_pcap_rx", port_id);
+
+	printf("RX from file PASSED: %u packets verified\n", received);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: TX with varied packet sizes using packet_burst_generator
+ */
+static int
+test_tx_varied_sizes(void)
+{
+	static const uint8_t test_sizes[] = {
+		PKT_SIZE_MIN, PKT_SIZE_SMALL, PACKET_BURST_GEN_PKT_LEN_128
+	};
+	char tx_path[PATH_MAX];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int i;
+
+	printf("Testing TX with varied packet sizes\n");
+
+	TEST_ASSERT(create_temp_path(tx_path, sizeof(tx_path),
+				     "pcap_tx_varied") == 0,
+		    "Failed to create temp file path");
+
+	snprintf(devargs, sizeof(devargs), "tx_pcap=%s", tx_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_tx_var", devargs, &port_id) == 0,
+		    "Failed to create TX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup TX port");
+
+	for (i = 0; i < RTE_DIM(test_sizes); i++) {
+		struct rte_mbuf *mbufs[MAX_PKT_BURST];
+		int nb_pkt, nb_tx;
+
+		nb_pkt = generate_test_packets(mp, mbufs, MAX_PKT_BURST,
+					       test_sizes[i]);
+		TEST_ASSERT(nb_pkt > 0,
+			    "Failed to generate packets of size %u",
+			    test_sizes[i]);
+
+		nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, nb_pkt);
+		if (nb_tx < nb_pkt)
+			rte_pktmbuf_free_bulk(&mbufs[nb_tx], nb_pkt - nb_tx);
+
+		printf("  Size %u: generated %d, transmitted %d\n",
+		       test_sizes[i], nb_pkt, nb_tx);
+		TEST_ASSERT(nb_tx > 0, "Failed to TX packets of size %u",
+			    test_sizes[i]);
+	}
+
+	cleanup_pcap_vdev("net_pcap_tx_var", port_id);
+	unlink(tx_path);
+
+	printf("TX varied sizes PASSED\n");
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: RX with varied packet sizes
+ */
+static int
+test_rx_varied_sizes(void)
+{
+	static const uint16_t expected_sizes[] = {
+		PKT_SIZE_MIN, PKT_SIZE_SMALL, PKT_SIZE_MEDIUM,
+		PKT_SIZE_LARGE, PKT_SIZE_MTU
+	};
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	uint16_t rx_sizes[NUM_PACKETS];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int received, i;
+
+	printf("Testing RX with varied packet sizes\n");
+
+	TEST_ASSERT(create_temp_path(varied_pcap_path, sizeof(varied_pcap_path),
+				     "pcap_varied") == 0,
+		    "Failed to create temp path");
+	TEST_ASSERT(create_varied_pcap(varied_pcap_path, NUM_PACKETS) == 0,
+		    "Failed to create varied pcap");
+
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s", varied_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_var", devargs, &port_id) == 0,
+		    "Failed to create varied RX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup varied RX port");
+
+	receive_packets(port_id, mbufs, NUM_PACKETS, &received);
+	TEST_ASSERT_EQUAL(received, NUM_PACKETS,
+			  "Received %u packets, expected %d", received, NUM_PACKETS);
+
+	/* Verify packet sizes match expected pattern */
+	for (i = 0; i < received; i++) {
+		uint16_t expected = expected_sizes[i % RTE_DIM(expected_sizes)];
+		rx_sizes[i] = rte_pktmbuf_pkt_len(mbufs[i]);
+		TEST_ASSERT_EQUAL(rx_sizes[i], expected,
+				  "Packet %u: size %u, expected %u",
+				  i, rx_sizes[i], expected);
+	}
+
+	rte_pktmbuf_free_bulk(mbufs, received);
+	cleanup_pcap_vdev("net_pcap_var", port_id);
+
+	printf("RX varied sizes PASSED: %u packets with correct sizes\n", received);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Infinite RX mode - loops through pcap file continuously
+ */
+static int
+test_infinite_rx(void)
+{
+	struct rte_mbuf *mbufs[MAX_PKT_BURST];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int total_rx = 0;
+	int iter, attempts;
+
+	printf("Testing infinite RX mode\n");
+
+	TEST_ASSERT(create_temp_path(infinite_pcap_path, sizeof(infinite_pcap_path),
+				     "pcap_inf") == 0,
+		    "Failed to create temp path");
+	TEST_ASSERT(create_test_pcap(infinite_pcap_path, NUM_PACKETS) == 0,
+		    "Failed to create input pcap");
+
+	snprintf(devargs, sizeof(devargs),
+		 "rx_pcap=%s,infinite_rx=1", infinite_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_inf", devargs, &port_id) == 0,
+		    "Failed to create infinite RX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup infinite RX port");
+
+	/* Read more packets than file contains to verify looping */
+	for (iter = 0; iter < 3 && total_rx < NUM_PACKETS * 2; iter++) {
+		for (attempts = 0; attempts < 100 && total_rx < NUM_PACKETS * 2;
+		     attempts++) {
+			uint16_t nb_rx = rte_eth_rx_burst(port_id, 0, mbufs,
+							  MAX_PKT_BURST);
+			if (nb_rx > 0)
+				rte_pktmbuf_free_bulk(mbufs, nb_rx);
+			total_rx += nb_rx;
+			if (nb_rx == 0)
+				usleep(100);
+		}
+	}
+
+	cleanup_pcap_vdev("net_pcap_inf", port_id);
+
+	TEST_ASSERT(total_rx >= NUM_PACKETS * 2,
+		    "Infinite RX: got %u packets, need >= %d",
+		    total_rx, NUM_PACKETS * 2);
+
+	printf("Infinite RX PASSED: %u packets (file has %d)\n",
+	       total_rx, NUM_PACKETS);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: TX drop mode - packets dropped when no tx_pcap specified
+ */
+static int
+test_tx_drop(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	struct rte_eth_stats stats;
+	char devargs[256];
+	uint16_t port_id;
+	int nb_tx;
+
+	printf("Testing TX drop mode\n");
+
+	TEST_ASSERT(create_temp_path(rx_pcap_path, sizeof(rx_pcap_path),
+				     "pcap_drop") == 0,
+		    "Failed to create temp path");
+	TEST_ASSERT(create_test_pcap(rx_pcap_path, NUM_PACKETS) == 0,
+		    "Failed to create input pcap");
+
+	/* Only rx_pcap - TX should silently drop */
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s", rx_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_drop", devargs, &port_id) == 0,
+		    "Failed to create drop vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup drop port");
+	TEST_ASSERT(alloc_test_mbufs(mbufs, NUM_PACKETS) == 0,
+		    "Failed to allocate mbufs");
+
+	TEST_ASSERT(rte_eth_stats_reset(port_id) == 0,
+		    "Failed to reset stats");
+	nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, NUM_PACKETS);
+
+	/* Packets should be accepted even in drop mode */
+	TEST_ASSERT_EQUAL(nb_tx, NUM_PACKETS,
+			  "Drop mode TX: %d/%d accepted", nb_tx, NUM_PACKETS);
+
+	TEST_ASSERT(rte_eth_stats_get(port_id, &stats) == 0,
+		    "Failed to get stats");
+	cleanup_pcap_vdev("net_pcap_drop", port_id);
+
+	printf("TX drop PASSED: %d packets dropped, opackets=%" PRIu64"\n",
+	       nb_tx, stats.opackets);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Statistics accuracy and reset
+ */
+static int
+test_stats(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	struct rte_eth_stats stats;
+	char devargs[256];
+	char stats_tx_path[PATH_MAX];
+	uint16_t port_id;
+	unsigned int received;
+	int nb_tx;
+
+	printf("Testing statistics accuracy\n");
+
+	TEST_ASSERT(create_temp_path(rx_pcap_path, sizeof(rx_pcap_path),
+				     "pcap_stats_rx") == 0,
+		    "Failed to create RX temp path");
+	TEST_ASSERT(create_temp_path(stats_tx_path, sizeof(stats_tx_path),
+				     "pcap_stats_tx") == 0,
+		    "Failed to create TX temp path");
+	TEST_ASSERT(create_test_pcap(rx_pcap_path, NUM_PACKETS) == 0,
+		    "Failed to create input pcap");
+
+	snprintf(devargs, sizeof(devargs),
+		 "rx_pcap=%s,tx_pcap=%s", rx_pcap_path, stats_tx_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_stats", devargs, &port_id) == 0,
+		    "Failed to create stats vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup stats port");
+
+	/* Verify stats start at zero */
+	TEST_ASSERT(rte_eth_stats_reset(port_id) == 0,
+		    "Failed to reset stats");
+	TEST_ASSERT(rte_eth_stats_get(port_id, &stats) == 0,
+		    "Failed to get stats");
+	TEST_ASSERT(stats.ipackets == 0 && stats.opackets == 0 &&
+		    stats.ibytes == 0 && stats.obytes == 0,
+		    "Initial stats not zero");
+
+	/* RX and verify stats */
+	receive_packets(port_id, mbufs, NUM_PACKETS, &received);
+	TEST_ASSERT(rte_eth_stats_get(port_id, &stats) == 0,
+		    "Failed to get stats after RX");
+	TEST_ASSERT_EQUAL(stats.ipackets, received,
+			  "RX stats: ipackets=%"PRIu64", received=%u",
+			  stats.ipackets, received);
+
+	/* TX and verify stats */
+	nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, received);
+	TEST_ASSERT(rte_eth_stats_get(port_id, &stats) == 0,
+		    "Failed to get stats after TX");
+	TEST_ASSERT_EQUAL(stats.opackets, (uint64_t)nb_tx,
+			  "TX stats: opackets=%"PRIu64", sent=%u",
+			  stats.opackets, nb_tx);
+
+	/* Verify stats reset */
+	TEST_ASSERT(rte_eth_stats_reset(port_id) == 0,
+		    "Failed to reset stats");
+	TEST_ASSERT(rte_eth_stats_get(port_id, &stats) == 0,
+		    "Failed to get stats after reset");
+	TEST_ASSERT(stats.ipackets == 0 && stats.opackets == 0,
+		    "Stats not reset to zero");
+
+	cleanup_pcap_vdev("net_pcap_stats", port_id);
+	unlink(stats_tx_path);
+
+	printf("Statistics PASSED: RX=%u, TX=%d\n", received, nb_tx);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: MTU configuration
+ */
+static int
+test_set_mtu(void)
+{
+	char devargs[256];
+	char mtu_tx_path[PATH_MAX];
+	uint16_t port_id;
+	static const uint16_t mtu_values[] = {1500, 9000, 1280};
+	int ret = 0;
+	size_t i;
+
+	printf("Testing MTU configuration\n");
+
+	TEST_ASSERT(create_temp_path(rx_pcap_path, sizeof(rx_pcap_path),
+				     "pcap_mtu_rx") == 0,
+		    "Failed to create RX temp path");
+	TEST_ASSERT(create_temp_path(mtu_tx_path, sizeof(mtu_tx_path),
+				     "pcap_mtu_tx") == 0,
+		    "Failed to create TX temp path");
+	TEST_ASSERT(create_test_pcap(rx_pcap_path, 1) == 0,
+		    "Failed to create input pcap");
+
+	snprintf(devargs, sizeof(devargs),
+		 "rx_pcap=%s,tx_pcap=%s", rx_pcap_path, mtu_tx_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_mtu", devargs, &port_id) == 0,
+		    "Failed to create MTU vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup MTU port");
+
+	for (i = 0; i < RTE_DIM(mtu_values); i++) {
+		uint16_t mtu;
+
+		ret = rte_eth_dev_set_mtu(port_id, mtu_values[i]);
+		if (ret != 0)
+			break;
+
+		TEST_ASSERT(rte_eth_dev_get_mtu(port_id, &mtu) == 0, "Failed to get MTU");
+		TEST_ASSERT_EQUAL(mtu, mtu_values[i], "MTU set mismatch for %u", mtu_values[i]);
+	}
+
+	cleanup_pcap_vdev("net_pcap_mtu", port_id);
+	unlink(mtu_tx_path);
+
+	if (ret == 0) {
+		printf("MTU test completed\n");
+		return TEST_SUCCESS;
+	}
+
+	if (ret == -ENOTSUP) {
+		printf("MTU set not supported\n");
+		return TEST_SKIPPED;
+	}
+
+	printf("Failed to set MTU: %s", strerror(-ret));
+	return TEST_FAILED;
+}
+
+/*
+ * Test: Jumbo frame RX (multi-segment mbufs)
+ */
+static int
+test_jumbo_rx(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int received, i;
+	const unsigned int num_jumbo = 16;
+
+	printf("Testing jumbo frame RX (%u byte packets, multi-segment)\n",
+	       PKT_SIZE_JUMBO);
+
+	TEST_ASSERT(create_temp_path(jumbo_pcap_path, sizeof(jumbo_pcap_path),
+				     "pcap_jumbo") == 0,
+		    "Failed to create temp path");
+	TEST_ASSERT(create_sized_pcap(jumbo_pcap_path, num_jumbo,
+				      PKT_SIZE_JUMBO) == 0,
+		    "Failed to create jumbo pcap");
+
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s", jumbo_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_jumbo", devargs, &port_id) == 0,
+		    "Failed to create jumbo RX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup jumbo RX port");
+
+	receive_packets(port_id, mbufs, num_jumbo, &received);
+	TEST_ASSERT_EQUAL(received, num_jumbo,
+			  "Received %u packets, expected %u", received, num_jumbo);
+
+	/* Verify all packets are jumbo size (may be multi-segment) */
+	for (i = 0; i < received; i++) {
+		uint32_t pkt_len = rte_pktmbuf_pkt_len(mbufs[i]);
+		uint16_t nb_segs = mbufs[i]->nb_segs;
+
+		TEST_ASSERT_EQUAL(pkt_len, PKT_SIZE_JUMBO,
+				  "Packet %u: size %u, expected %u",
+				  i, pkt_len, PKT_SIZE_JUMBO);
+
+		/* Jumbo frames should use multiple segments */
+		if (nb_segs > 1)
+			printf("  Packet %u: %u segments\n", i, nb_segs);
+	}
+
+	rte_pktmbuf_free_bulk(mbufs, received);
+	cleanup_pcap_vdev("net_pcap_jumbo", port_id);
+
+	printf("Jumbo RX PASSED: %u jumbo packets received\n", received);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Jumbo frame TX (multi-segment mbufs)
+ */
+static int
+test_jumbo_tx(void)
+{
+	struct rte_mbuf *mbufs[MAX_PKT_BURST];
+	char tx_path[PATH_MAX];
+	char devargs[256];
+	uint16_t port_id;
+	uint16_t sizes[MAX_PKT_BURST];
+	int nb_tx, pkt_count, ret;
+	unsigned int i;
+	const unsigned int num_jumbo = 8;
+
+	printf("Testing jumbo frame TX (multi-segment mbufs)\n");
+
+	TEST_ASSERT(create_temp_path(tx_path, sizeof(tx_path),
+				     "pcap_jumbo_tx") == 0,
+		    "Failed to create temp file path");
+
+	snprintf(devargs, sizeof(devargs), "tx_pcap=%s", tx_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_jumbo_tx", devargs, &port_id) == 0,
+		    "Failed to create TX vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup TX port");
+
+	/* Set MTU to allow jumbo frames - PMD drops packets exceeding MTU */
+	ret = rte_eth_dev_set_mtu(port_id, PKT_SIZE_JUMBO);
+	if (ret != 0) {
+		printf("Failed to set MTU to %u: %s\n",
+		       PKT_SIZE_JUMBO, rte_strerror(-ret));
+		cleanup_pcap_vdev("net_pcap_jumbo_tx", port_id);
+		unlink(tx_path);
+		return TEST_SKIPPED;
+	}
+
+	/* Allocate multi-segment mbufs for jumbo frames */
+	for (i = 0; i < num_jumbo; i++) {
+		mbufs[i] = alloc_jumbo_mbuf(PKT_SIZE_JUMBO, (uint8_t)(i & 0xFF));
+		if (mbufs[i] == NULL) {
+			/* Free already allocated mbufs */
+			while (i > 0)
+				rte_pktmbuf_free(mbufs[--i]);
+			cleanup_pcap_vdev("net_pcap_jumbo_tx", port_id);
+			unlink(tx_path);
+			return TEST_FAILED;
+		}
+		printf("  Packet %u: %u segments for %u bytes\n",
+		       i, mbufs[i]->nb_segs, PKT_SIZE_JUMBO);
+	}
+
+	nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, num_jumbo);
+	/* Free any unsent mbufs */
+	for (i = nb_tx; i < num_jumbo; i++)
+		rte_pktmbuf_free(mbufs[i]);
+
+	TEST_ASSERT_EQUAL(nb_tx, (int)num_jumbo,
+			  "TX burst failed: sent %d/%u", nb_tx, num_jumbo);
+
+	cleanup_pcap_vdev("net_pcap_jumbo_tx", port_id);
+
+	/* Verify pcap file has correct packet count and sizes */
+	pkt_count = get_pcap_packet_sizes(tx_path, sizes, MAX_PKT_BURST);
+	TEST_ASSERT_EQUAL(pkt_count, (int)num_jumbo,
+			  "Pcap file has %d packets, expected %u",
+			  pkt_count, num_jumbo);
+
+	for (i = 0; i < (unsigned int)pkt_count; i++) {
+		TEST_ASSERT_EQUAL(sizes[i], PKT_SIZE_JUMBO,
+				  "Packet %u: size %u, expected %u",
+				  i, sizes[i], PKT_SIZE_JUMBO);
+	}
+
+	unlink(tx_path);
+
+	printf("Jumbo TX PASSED: %d jumbo packets written\n", nb_tx);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Layering on Linux network interface
+ */
+static int
+test_iface(void)
+{
+	struct rte_mbuf *mbufs[MAX_PKT_BURST];
+	struct rte_eth_dev_info dev_info;
+	char devargs[256];
+	uint16_t port_id;
+	const char *iface;
+	int ret, nb_tx, nb_pkt;
+
+	printf("Testing pcap on network interface\n");
+
+	iface = find_test_iface();
+	if (iface == NULL) {
+		printf("No suitable interface, skipping\n");
+		return TEST_SKIPPED;
+	}
+	printf("Using interface: %s\n", iface);
+
+	snprintf(devargs, sizeof(devargs), "iface=%s", iface);
+	if (rte_vdev_init("net_pcap_iface", devargs) < 0) {
+		printf("Cannot create iface vdev (needs root?), skipping\n");
+		return TEST_SKIPPED;
+	}
+
+	TEST_ASSERT(rte_eth_dev_get_port_by_name("net_pcap_iface",
+						 &port_id) == 0,
+		    "Failed to get iface port ID");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup iface port");
+
+	ret = rte_eth_dev_info_get(port_id, &dev_info);
+	TEST_ASSERT(ret == 0, "Failed to get dev info: %s", rte_strerror(-ret));
+
+	printf("Driver: %s, max_rx_queues=%u, max_tx_queues=%u\n",
+	       dev_info.driver_name, dev_info.max_rx_queues,
+	       dev_info.max_tx_queues);
+
+	/* Use packet_burst_generator for interface test */
+	nb_pkt = generate_test_packets(mp, mbufs, MAX_PKT_BURST,
+				       PACKET_BURST_GEN_PKT_LEN);
+	TEST_ASSERT(nb_pkt > 0, "Failed to generate packets");
+
+	nb_tx = rte_eth_tx_burst(port_id, 0, mbufs, nb_pkt);
+	if (nb_tx < nb_pkt)
+		rte_pktmbuf_free_bulk(&mbufs[nb_tx], nb_pkt - nb_tx);
+
+	cleanup_pcap_vdev("net_pcap_iface", port_id);
+
+	printf("Interface test PASSED: sent %d packets\n", nb_tx);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: MAC address with phy_mac option
+ */
+static int
+test_mac_address(void)
+{
+	struct rte_ether_addr orig_mac, new_mac, read_mac;
+	char devargs[256];
+	uint16_t port_id;
+	const char *iface;
+	int ret;
+
+	printf("Testing MAC address configuration\n");
+
+	iface = find_test_iface();
+	if (iface == NULL) {
+		printf("No suitable interface, skipping\n");
+		return TEST_SKIPPED;
+	}
+
+	if (strcmp(iface, "lo") == 0) {
+		printf("Need dummy interface to test setting mac address\n");
+		return TEST_SKIPPED;
+	}
+
+	snprintf(devargs, sizeof(devargs), "iface=%s,phy_mac=1", iface);
+	if (rte_vdev_init("net_pcap_mac", devargs) < 0) {
+		printf("Cannot create mac vdev (needs root?), skipping\n");
+		return TEST_SKIPPED;
+	}
+
+	TEST_ASSERT(rte_eth_dev_get_port_by_name("net_pcap_mac", &port_id) == 0,
+		    "Failed to get mac port ID");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup mac port");
+
+	ret = rte_eth_macaddr_get(port_id, &orig_mac);
+	TEST_ASSERT(ret == 0, "Failed to get original MAC");
+	printf("Original MAC: " RTE_ETHER_ADDR_PRT_FMT "\n",
+	       RTE_ETHER_ADDR_BYTES(&orig_mac));
+
+	/* Try to set a new MAC */
+	rte_eth_random_addr(new_mac.addr_bytes);
+
+	ret = rte_eth_dev_default_mac_addr_set(port_id, &new_mac);
+	if (ret == 0) {
+		rte_eth_macaddr_get(port_id, &read_mac);
+		printf("New MAC: " RTE_ETHER_ADDR_PRT_FMT "\n",
+		       RTE_ETHER_ADDR_BYTES(&read_mac));
+		/* Restore original */
+		rte_eth_dev_default_mac_addr_set(port_id, &orig_mac);
+		ret = TEST_SUCCESS;
+	} else if (ret == -ENOTSUP) {
+		printf("MAC change not supported\n");
+		ret = TEST_SKIPPED;
+	} else {
+		printf("MAC change failed: %s\n", rte_strerror(-ret));
+		ret = TEST_FAILED;
+	}
+
+	cleanup_pcap_vdev("net_pcap_mac", port_id);
+
+	printf("MAC address test completed\n");
+	return ret;
+}
+
+/*
+ * Test: Verify receive timestamps from pcap file
+ */
+static int
+test_rx_timestamp(void)
+{
+	struct rte_mbuf *mbufs[NUM_PACKETS];
+	char devargs[256];
+	uint16_t port_id;
+	unsigned int received, i;
+	const uint32_t base_sec = 1000;
+	const uint32_t usec_increment = 10000; /* 10ms between packets */
+	rte_mbuf_timestamp_t prev_ts = 0;
+
+	printf("Testing RX timestamp accuracy\n");
+
+	TEST_ASSERT(create_temp_path(timestamp_pcap_path, sizeof(timestamp_pcap_path),
+				     "pcap_ts") == 0,
+		    "Failed to create temp path");
+	TEST_ASSERT(create_timestamped_pcap(timestamp_pcap_path, NUM_PACKETS,
+					    base_sec, usec_increment) == 0,
+		    "Failed to create timestamped pcap");
+
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s", timestamp_pcap_path);
+	TEST_ASSERT(create_pcap_vdev("net_pcap_ts", devargs, &port_id) == 0,
+		    "Failed to create timestamp vdev");
+	TEST_ASSERT(setup_pcap_port(port_id) == 0,
+		    "Failed to setup timestamp port");
+
+	/* Try to initialize timestamp dynamic field access */
+	TEST_ASSERT(timestamp_init() == 0, "Timestamp dynfield not available");
+
+	receive_packets(port_id, mbufs, NUM_PACKETS, &received);
+	TEST_ASSERT_EQUAL(received, NUM_PACKETS,
+			  "Received %u packets, expected %d", received, NUM_PACKETS);
+
+	/* Check if first packet has timestamp flag set */
+	if (!mbuf_has_timestamp(mbufs[0])) {
+		printf("Timestamps not enabled in mbufs, skipping validation\n");
+		rte_pktmbuf_free_bulk(mbufs, received);
+		cleanup_pcap_vdev("net_pcap_ts", port_id);
+		return TEST_SUCCESS;
+	}
+
+	for (i = 0; i < received; i++) {
+		struct rte_mbuf *m = mbufs[i];
+
+		TEST_ASSERT(mbuf_has_timestamp(m),
+			    "Packet %u missing timestamp flag", i);
+
+		/* PCAP PMD stores timestamp in nanoseconds */
+		rte_mbuf_timestamp_t ts = mbuf_timestamp_get(mbufs[i]);
+		uint64_t expected = (uint64_t)base_sec * NS_PER_S
+			+ (uint64_t)i * usec_increment * 1000;
+
+		if (ts != expected)
+			printf("Packet %u: timestamp mismatch, expected=%"PRIu64" actual=%"PRIu64"\n",
+			       i, expected, ts);
+
+		/* Verify monotonically increasing timestamps */
+		if (i > 0) {
+			TEST_ASSERT(ts >= prev_ts,
+				    "Packet %u: timestamp not monotonic %"PRIu64" > %"PRIu64,
+				    i, prev_ts, ts);
+		}
+		prev_ts = ts;
+	}
+
+	rte_pktmbuf_free_bulk(mbufs, received);
+	cleanup_pcap_vdev("net_pcap_ts", port_id);
+
+	printf("RX timestamp PASSED: %u packets with valid timestamps\n", received);
+	return TEST_SUCCESS;
+}
+
+/* Helper: Generate packets for multi-queue tests */
+static int
+generate_mq_test_packets(struct rte_mbuf **pkts, unsigned int nb_pkts, uint16_t queue_id)
+{
+	struct rte_ether_hdr eth_hdr;
+	struct rte_ipv4_hdr ip_hdr;
+	struct rte_udp_hdr udp_hdr;
+	uint16_t pkt_data_len;
+	unsigned int i;
+
+	initialize_eth_header(&eth_hdr, &src_mac, &dst_mac, RTE_ETHER_TYPE_IPV4, 0, 0);
+	pkt_data_len = sizeof(struct rte_udp_hdr);
+	initialize_udp_header(&udp_hdr, 1234, 1234, pkt_data_len);
+	initialize_ipv4_header(&ip_hdr, IPV4_ADDR(192, 168, 1, 1), IPV4_ADDR(192, 168, 1, 2),
+			       pkt_data_len + sizeof(struct rte_udp_hdr));
+
+	for (i = 0; i < nb_pkts; i++) {
+		pkts[i] = rte_pktmbuf_alloc(mp);
+		if (pkts[i] == NULL) {
+			printf("Failed to allocate mbuf\n");
+			while (i > 0)
+				rte_pktmbuf_free(pkts[--i]);
+			return -1;
+		}
+
+		char *pkt_data = rte_pktmbuf_append(pkts[i], PACKET_BURST_GEN_PKT_LEN);
+		if (pkt_data == NULL) {
+			printf("Failed to append data to mbuf\n");
+			rte_pktmbuf_free(pkts[i]);
+			while (i > 0)
+				rte_pktmbuf_free(pkts[--i]);
+			return -1;
+		}
+
+		size_t offset = 0;
+		memcpy(pkt_data + offset, &eth_hdr, sizeof(eth_hdr));
+		offset += sizeof(eth_hdr);
+
+		/* Mark packet with queue ID in IP packet_id field for tracing */
+		ip_hdr.packet_id = rte_cpu_to_be_16((queue_id << 8) | (i & 0xFF));
+		ip_hdr.hdr_checksum = 0;
+		ip_hdr.hdr_checksum = rte_ipv4_cksum(&ip_hdr);
+
+		memcpy(pkt_data + offset, &ip_hdr, sizeof(ip_hdr));
+		offset += sizeof(ip_hdr);
+		memcpy(pkt_data + offset, &udp_hdr, sizeof(udp_hdr));
+	}
+	return (int)nb_pkts;
+}
+
+/* Helper: Validate pcap file structure using libpcap */
+static int
+validate_pcap_file(const char *filename)
+{
+	pcap_t *pcap;
+	char errbuf[PCAP_ERRBUF_SIZE];
+
+	pcap = pcap_open_offline(filename, errbuf);
+	if (pcap == NULL) {
+		printf("Failed to validate pcap file %s: %s\n", filename, errbuf);
+		return -1;
+	}
+	if (pcap_datalink(pcap) != DLT_EN10MB) {
+		printf("Unexpected datalink type: %d\n", pcap_datalink(pcap));
+		pcap_close(pcap);
+		return -1;
+	}
+	pcap_close(pcap);
+	return 0;
+}
+
+/*
+ * Test: Multiple TX queues writing to separate pcap files
+ *
+ * This test creates a pcap PMD with multiple TX queues, each configured
+ * to write to its own output file. We verify that:
+ * 1. All packets from all queues are written
+ * 2. Each resulting pcap file is valid
+ * 3. Each file has the expected packet count
+ */
+static int
+test_multi_tx_queue(void)
+{
+	char devargs[512];
+	uint16_t port_id;
+	struct rte_eth_conf port_conf;
+	struct rte_eth_txconf tx_conf;
+	struct rte_mbuf *pkts[MULTI_QUEUE_BURST_SIZE];
+	uint16_t q;
+	int ret;
+	unsigned int total_tx = 0;
+	unsigned int tx_per_queue[MULTI_QUEUE_NUM_QUEUES] = {0};
+
+	printf("Testing multiple TX queues to separate files\n");
+
+	/* Create temp paths for each TX queue */
+	for (q = 0; q < MULTI_QUEUE_NUM_QUEUES; q++) {
+		char prefix[32];
+		snprintf(prefix, sizeof(prefix), "pcap_multi_tx%u", q);
+		TEST_ASSERT(create_temp_path(multi_tx_pcap_paths[q],
+					     sizeof(multi_tx_pcap_paths[q]), prefix) == 0,
+			    "Failed to create temp path for queue %u", q);
+	}
+
+	/* Create the pcap PMD with multiple TX queues to separate files */
+	snprintf(devargs, sizeof(devargs), "tx_pcap=%s,tx_pcap=%s,tx_pcap=%s,tx_pcap=%s",
+		 multi_tx_pcap_paths[0], multi_tx_pcap_paths[1],
+		 multi_tx_pcap_paths[2], multi_tx_pcap_paths[3]);
+
+	ret = rte_vdev_init("net_pcap_multi_tx", devargs);
+	TEST_ASSERT_SUCCESS(ret, "Failed to create pcap PMD: %s", rte_strerror(-ret));
+
+	ret = rte_eth_dev_get_port_by_name("net_pcap_multi_tx", &port_id);
+	TEST_ASSERT_SUCCESS(ret, "Cannot find added pcap device");
+
+	memset(&port_conf, 0, sizeof(port_conf));
+	ret = rte_eth_dev_configure(port_id, 0, MULTI_QUEUE_NUM_QUEUES, &port_conf);
+	TEST_ASSERT_SUCCESS(ret, "Failed to configure device: %s", rte_strerror(-ret));
+
+	memset(&tx_conf, 0, sizeof(tx_conf));
+	for (q = 0; q < MULTI_QUEUE_NUM_QUEUES; q++) {
+		ret = rte_eth_tx_queue_setup(port_id, q, RING_SIZE,
+					     rte_eth_dev_socket_id(port_id), &tx_conf);
+		TEST_ASSERT_SUCCESS(ret, "Failed to setup TX queue %u: %s", q, rte_strerror(-ret));
+	}
+
+	ret = rte_eth_dev_start(port_id);
+	TEST_ASSERT_SUCCESS(ret, "Failed to start device: %s", rte_strerror(-ret));
+
+	/* Transmit packets from each queue */
+	for (q = 0; q < MULTI_QUEUE_NUM_QUEUES; q++) {
+		unsigned int pkts_to_send = MULTI_QUEUE_NUM_PACKETS / MULTI_QUEUE_NUM_QUEUES;
+
+		while (tx_per_queue[q] < pkts_to_send) {
+			unsigned int burst = RTE_MIN(MULTI_QUEUE_BURST_SIZE,
+						     pkts_to_send - tx_per_queue[q]);
+
+			ret = generate_mq_test_packets(pkts, burst, q);
+			TEST_ASSERT(ret >= 0, "Failed to generate packets for queue %u", q);
+
+			uint16_t nb_tx = rte_eth_tx_burst(port_id, q, pkts, burst);
+			for (unsigned int i = nb_tx; i < burst; i++)
+				rte_pktmbuf_free(pkts[i]);
+
+			tx_per_queue[q] += nb_tx;
+			total_tx += nb_tx;
+
+			if (nb_tx == 0) {
+				printf("TX stall on queue %u\n", q);
+				break;
+			}
+		}
+		printf("  Queue %u: transmitted %u packets\n", q, tx_per_queue[q]);
+	}
+
+	rte_eth_dev_stop(port_id);
+	rte_vdev_uninit("net_pcap_multi_tx");
+	rte_delay_ms(100);
+
+	/* Validate each pcap file */
+	unsigned int total_in_files = 0;
+	for (q = 0; q < MULTI_QUEUE_NUM_QUEUES; q++) {
+		ret = validate_pcap_file(multi_tx_pcap_paths[q]);
+		TEST_ASSERT_SUCCESS(ret, "pcap file for queue %u is invalid", q);
+
+		int pkt_count = count_pcap_packets(multi_tx_pcap_paths[q]);
+		TEST_ASSERT(pkt_count >= 0, "Could not count packets in pcap file for queue %u", q);
+
+		printf("  Queue %u file: %d packets\n", q, pkt_count);
+		TEST_ASSERT_EQUAL((unsigned int)pkt_count, tx_per_queue[q],
+				  "Queue %u: file has %d packets, expected %u",
+				  q, pkt_count, tx_per_queue[q]);
+		total_in_files += pkt_count;
+	}
+
+	printf("  Total packets transmitted: %u\n", total_tx);
+	printf("  Total packets in all files: %u\n", total_in_files);
+
+	TEST_ASSERT_EQUAL(total_in_files, total_tx,
+			  "Total packet count mismatch: expected %u, got %u", total_tx, total_in_files);
+
+	printf("Multi-TX queue PASSED\n");
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Multiple RX queues reading from the same pcap file
+ *
+ * This test creates a pcap PMD with multiple RX queues all configured
+ * to read from the same input file. We verify that:
+ * 1. Each queue can read packets
+ * 2. The total packets read equals the file content (or expected behavior)
+ */
+static int
+test_multi_rx_queue_same_file(void)
+{
+	char devargs[512];
+	uint16_t port_id;
+	struct rte_eth_conf port_conf;
+	struct rte_eth_rxconf rx_conf;
+	struct rte_mbuf *pkts[MULTI_QUEUE_BURST_SIZE];
+	uint16_t q;
+	int ret;
+	unsigned int total_rx = 0;
+	unsigned int rx_per_queue[MULTI_QUEUE_NUM_QUEUES] = {0};
+	unsigned int seed_packets = MULTI_QUEUE_NUM_PACKETS;
+	unsigned int expected_total;
+
+	printf("Testing multiple RX queues from same file\n");
+
+	TEST_ASSERT(create_temp_path(multi_rx_pcap_path, sizeof(multi_rx_pcap_path),
+				     "pcap_multi_rx") == 0, "Failed to create temp path");
+
+	ret = create_test_pcap(multi_rx_pcap_path, seed_packets);
+	TEST_ASSERT_SUCCESS(ret, "Failed to create seed pcap file");
+	printf("  Created seed pcap file with %u packets\n", seed_packets);
+
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s,rx_pcap=%s,rx_pcap=%s,rx_pcap=%s",
+		 multi_rx_pcap_path, multi_rx_pcap_path, multi_rx_pcap_path, multi_rx_pcap_path);
+
+	ret = rte_vdev_init("net_pcap_multi_rx", devargs);
+	TEST_ASSERT_SUCCESS(ret, "Failed to create pcap PMD: %s", rte_strerror(-ret));
+
+	ret = rte_eth_dev_get_port_by_name("net_pcap_multi_rx", &port_id);
+	TEST_ASSERT_SUCCESS(ret, "Cannot find added pcap device");
+
+	memset(&port_conf, 0, sizeof(port_conf));
+	ret = rte_eth_dev_configure(port_id, MULTI_QUEUE_NUM_QUEUES, 0, &port_conf);
+	TEST_ASSERT_SUCCESS(ret, "Failed to configure device: %s", rte_strerror(-ret));
+
+	memset(&rx_conf, 0, sizeof(rx_conf));
+	for (q = 0; q < MULTI_QUEUE_NUM_QUEUES; q++) {
+		ret = rte_eth_rx_queue_setup(port_id, q, RING_SIZE,
+					     rte_eth_dev_socket_id(port_id), &rx_conf, mp);
+		TEST_ASSERT_SUCCESS(ret, "Failed to setup RX queue %u: %s", q, rte_strerror(-ret));
+	}
+
+	ret = rte_eth_dev_start(port_id);
+	TEST_ASSERT_SUCCESS(ret, "Failed to start device: %s", rte_strerror(-ret));
+
+	/* Receive packets from all queues. Each queue has its own file handle. */
+	int empty_rounds = 0;
+	while (empty_rounds < 10) {
+		int received_this_round = 0;
+		for (q = 0; q < MULTI_QUEUE_NUM_QUEUES; q++) {
+			uint16_t nb_rx = rte_eth_rx_burst(port_id, q, pkts, MULTI_QUEUE_BURST_SIZE);
+			if (nb_rx > 0) {
+				rx_per_queue[q] += nb_rx;
+				total_rx += nb_rx;
+				received_this_round += nb_rx;
+				rte_pktmbuf_free_bulk(pkts, nb_rx);
+			}
+		}
+		if (received_this_round == 0)
+			empty_rounds++;
+		else
+			empty_rounds = 0;
+	}
+
+	printf("  RX Results:\n");
+	for (q = 0; q < MULTI_QUEUE_NUM_QUEUES; q++)
+		printf("    Queue %u: received %u packets\n", q, rx_per_queue[q]);
+	printf("    Total received: %u packets\n", total_rx);
+
+	/* Each RX queue opens its own file handle, so each reads all packets */
+	expected_total = seed_packets * MULTI_QUEUE_NUM_QUEUES;
+	printf("    Expected total (each queue reads all): %u packets\n", expected_total);
+
+	rte_eth_dev_stop(port_id);
+	rte_vdev_uninit("net_pcap_multi_rx");
+
+	TEST_ASSERT(total_rx > 0, "No packets received at all");
+	for (q = 0; q < MULTI_QUEUE_NUM_QUEUES; q++) {
+		TEST_ASSERT(rx_per_queue[q] > 0, "Queue %u received no packets", q);
+		TEST_ASSERT_EQUAL(rx_per_queue[q], seed_packets,
+				  "Queue %u received %u packets, expected %u",
+				  q, rx_per_queue[q], seed_packets);
+	}
+	TEST_ASSERT_EQUAL(total_rx, expected_total,
+			  "Total RX mismatch: expected %u, got %u", expected_total, total_rx);
+
+	printf("Multi-RX queue PASSED\n");
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Device info reports correct queue counts and MTU limits
+ *
+ * This test verifies that rte_eth_dev_info_get() returns correct values:
+ * 1. max_rx_queues matches the number of rx_pcap files passed
+ * 2. max_tx_queues matches the number of tx_pcap files passed
+ * 3. min_mtu and max_mtu are set to reasonable values
+ */
+static int
+test_dev_info(void)
+{
+	struct rte_eth_dev_info dev_info;
+	char devargs[512];
+	char rx_paths[3][PATH_MAX];
+	char tx_paths[2][PATH_MAX];
+	uint16_t port_id;
+	int ret;
+	unsigned int i;
+
+	printf("Testing device info reporting\n");
+
+	/* Create temp RX pcap files (3 queues) */
+	for (i = 0; i < 3; i++) {
+		char prefix[32];
+		snprintf(prefix, sizeof(prefix), "pcap_devinfo_rx%u", i);
+		TEST_ASSERT(create_temp_path(rx_paths[i], sizeof(rx_paths[i]), prefix) == 0,
+			    "Failed to create RX temp path %u", i);
+		TEST_ASSERT(create_test_pcap(rx_paths[i], 1) == 0, "Failed to create RX pcap %u", i);
+	}
+
+	/* Create temp TX pcap files (2 queues) */
+	for (i = 0; i < 2; i++) {
+		char prefix[32];
+		snprintf(prefix, sizeof(prefix), "pcap_devinfo_tx%u", i);
+		TEST_ASSERT(create_temp_path(tx_paths[i], sizeof(tx_paths[i]), prefix) == 0,
+			    "Failed to create TX temp path %u", i);
+	}
+
+	/* Create device with 3 RX queues and 2 TX queues */
+	snprintf(devargs, sizeof(devargs), "rx_pcap=%s,rx_pcap=%s,rx_pcap=%s,tx_pcap=%s,tx_pcap=%s",
+		 rx_paths[0], rx_paths[1], rx_paths[2], tx_paths[0], tx_paths[1]);
+
+	ret = rte_vdev_init("net_pcap_devinfo", devargs);
+	TEST_ASSERT_SUCCESS(ret, "Failed to create pcap PMD: %s", rte_strerror(-ret));
+
+	ret = rte_eth_dev_get_port_by_name("net_pcap_devinfo", &port_id);
+	TEST_ASSERT_SUCCESS(ret, "Cannot find added pcap device");
+
+	ret = rte_eth_dev_info_get(port_id, &dev_info);
+	TEST_ASSERT_SUCCESS(ret, "Failed to get device info: %s", rte_strerror(-ret));
+
+	printf("  Device info:\n");
+	printf("    driver_name: %s\n", dev_info.driver_name);
+	printf("    max_rx_queues: %u (expected: 3)\n", dev_info.max_rx_queues);
+	printf("    max_tx_queues: %u (expected: 2)\n", dev_info.max_tx_queues);
+	printf("    min_mtu: %u\n", dev_info.min_mtu);
+	printf("    max_mtu: %u\n", dev_info.max_mtu);
+
+	/* Verify queue counts match number of pcap files */
+	TEST_ASSERT_EQUAL(dev_info.max_rx_queues, 3U,
+			  "max_rx_queues mismatch: expected 3, got %u", dev_info.max_rx_queues);
+	TEST_ASSERT_EQUAL(dev_info.max_tx_queues, 2U,
+			  "max_tx_queues mismatch: expected 2, got %u", dev_info.max_tx_queues);
+
+	/* Verify MTU limits are reasonable */
+	TEST_ASSERT(dev_info.min_mtu > 0, "min_mtu should be > 0, got %u", dev_info.min_mtu);
+	TEST_ASSERT(dev_info.min_mtu <= RTE_ETHER_MIN_MTU,
+		    "min_mtu should be <= %u, got %u", RTE_ETHER_MIN_MTU, dev_info.min_mtu);
+	TEST_ASSERT(dev_info.max_mtu <= RTE_ETHER_MAX_JUMBO_FRAME_LEN,
+		    "max_mtu should be <= %u, got %u", RTE_ETHER_MAX_JUMBO_FRAME_LEN, dev_info.max_mtu);
+
+	rte_vdev_uninit("net_pcap_devinfo");
+
+	/* Cleanup temp files */
+	for (i = 0; i < 3; i++)
+		unlink(rx_paths[i]);
+	for (i = 0; i < 2; i++)
+		unlink(tx_paths[i]);
+
+	printf("Device info PASSED\n");
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test suite setup
+ */
+static int
+test_setup(void)
+{
+	/* Generate random source MAC address */
+	rte_eth_random_addr(src_mac.addr_bytes);
+
+	mp = rte_pktmbuf_pool_create("pcap_test_pool", NB_MBUF, 32, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE,
+				     rte_socket_id());
+	TEST_ASSERT_NOT_NULL(mp, "Failed to create mempool");
+
+	return 0;
+}
+
+/*
+ * Test suite teardown
+ */
+static void
+test_teardown(void)
+{
+	unsigned int i;
+
+	/* Cleanup temp files */
+	if (tx_pcap_path[0] != '\0')
+		unlink(tx_pcap_path);
+	if (rx_pcap_path[0] != '\0')
+		unlink(rx_pcap_path);
+	if (infinite_pcap_path[0] != '\0')
+		unlink(infinite_pcap_path);
+	if (timestamp_pcap_path[0] != '\0')
+		unlink(timestamp_pcap_path);
+	if (varied_pcap_path[0] != '\0')
+		unlink(varied_pcap_path);
+	if (jumbo_pcap_path[0] != '\0')
+		unlink(jumbo_pcap_path);
+	for (i = 0; i < RTE_DIM(multi_tx_pcap_paths); i++) {
+		if (multi_tx_pcap_paths[i][0] != '\0')
+			unlink(multi_tx_pcap_paths[i]);
+	}
+	if (multi_rx_pcap_path[0] != '\0')
+		unlink(multi_rx_pcap_path);
+
+	rte_mempool_free(mp);
+	mp = NULL;
+}
+
+static struct unit_test_suite test_pmd_pcap_suite = {
+	.setup = test_setup,
+	.teardown = test_teardown,
+	.suite_name = "PCAP PMD Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_dev_info),
+		TEST_CASE(test_tx_to_file),
+		TEST_CASE(test_rx_from_file),
+		TEST_CASE(test_tx_varied_sizes),
+		TEST_CASE(test_rx_varied_sizes),
+		TEST_CASE(test_jumbo_rx),
+		TEST_CASE(test_jumbo_tx),
+		TEST_CASE(test_infinite_rx),
+		TEST_CASE(test_tx_drop),
+		TEST_CASE(test_stats),
+		TEST_CASE(test_set_mtu),
+		TEST_CASE(test_iface),
+		TEST_CASE(test_mac_address),
+		TEST_CASE(test_rx_timestamp),
+		TEST_CASE(test_multi_tx_queue),
+		TEST_CASE(test_multi_rx_queue_same_file),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_pmd_pcap(void)
+{
+	return unit_test_suite_runner(&test_pmd_pcap_suite);
+}
+
+REGISTER_FAST_TEST(pcap_pmd_autotest, true, true, test_pmd_pcap);
-- 
2.51.0


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

end of thread, other threads:[~2026-01-09  1:18 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-06 18:26 [PATCH 00/12] net/pcap: cleanups and test Stephen Hemminger
2026-01-06 18:26 ` [PATCH 01/12] net/pcap: avoid using rte_malloc and rte_memcpy Stephen Hemminger
2026-01-06 18:26 ` [PATCH 02/12] net/pcap: support MTU set Stephen Hemminger
2026-01-06 18:26 ` [PATCH 03/12] net/pcap: use bool for flags Stephen Hemminger
2026-01-07 10:28   ` Marat Khalili
2026-01-09  0:23     ` Stephen Hemminger
2026-01-06 18:26 ` [PATCH 04/12] net/pcap: support Tx offloads Stephen Hemminger
2026-01-06 18:26 ` [PATCH 05/12] net/pcap: support nanosecond timestamp precision Stephen Hemminger
2026-01-06 18:26 ` [PATCH 06/12] net/pcap: remove global variables Stephen Hemminger
2026-01-07  9:48   ` Marat Khalili
2026-01-06 18:26 ` [PATCH 07/12] net/pcap: avoid use of volatile Stephen Hemminger
2026-01-07 10:31   ` Marat Khalili
2026-01-06 18:26 ` [PATCH 08/12] net/pcap: optimize calculation of receive timestamp Stephen Hemminger
2026-01-07 10:58   ` Marat Khalili
2026-01-06 18:26 ` [PATCH 09/12] net/pcap: report receive clock Stephen Hemminger
2026-01-06 18:26 ` [PATCH 10/12] net/pcap: cleanup MAC address handling Stephen Hemminger
2026-01-06 18:26 ` [PATCH 11/12] net/pcap: support MAC address set Stephen Hemminger
2026-01-06 18:26 ` [PATCH 12/12] test: add test for pcap PMD Stephen Hemminger
2026-01-09  1:16 ` [PATCH v2 0/9] pcap: cleanup pcap PMD and add test Stephen Hemminger
2026-01-09  1:16   ` [PATCH v2 1/9] net/pcap: avoid using rte_malloc and rte_memcpy Stephen Hemminger
2026-01-09  1:16   ` [PATCH v2 2/9] net/pcap: support MTU set Stephen Hemminger
2026-01-09  1:16   ` [PATCH v2 3/9] net/pcap: use bool for flags Stephen Hemminger
2026-01-09  1:16   ` [PATCH v2 4/9] net/pcap: support Tx offloads Stephen Hemminger
2026-01-09  1:16   ` [PATCH v2 5/9] net/pcap: support nanosecond timestamp precision Stephen Hemminger
2026-01-09  1:16   ` [PATCH v2 6/9] net/pcap: remove global variables Stephen Hemminger
2026-01-09  1:16   ` [PATCH v2 7/9] net/pcap: avoid use of volatile Stephen Hemminger
2026-01-09  1:16   ` [PATCH v2 8/9] net/pcap: support MAC address set Stephen Hemminger
2026-01-09  1:16   ` [PATCH v2 9/9] test: add test for pcap PMD Stephen Hemminger

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).