public inbox for dev@dpdk.org
 help / color / mirror / Atom feed
* [PATCH 00/10] net/tap: tests, cleanups, and error path fixes
@ 2026-02-15 19:52 Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 01/10] test: add unit tests for TAP PMD Stephen Hemminger
                   ` (14 more replies)
  0 siblings, 15 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-15 19:52 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a unit test suite for the TAP PMD, clean up minor code issues,
and fix several resource leaks and a use-after-free on error paths.

Stephen Hemminger (10):
  test: add unit tests for TAP PMD
  net/tap: replace runtime speed capability with constant
  net/tap: clarify TUN/TAP flag assignment
  net/tap: extend fixed MAC range to 16 bits
  net/tap: skip checksum on truncated L4 headers
  net/tap: fix resource leaks in tap create error path
  net/tap: fix resource leaks in secondary process probe
  net/tap: free IPC reply buffer on queue count mismatch
  net/tap: fix use-after-free on remote flow creation failure
  net/tap: free remote flow when implicit rule already exists

 app/test/meson.build          |   1 +
 app/test/test_pmd_tap.c       | 896 ++++++++++++++++++++++++++++++++++
 drivers/net/tap/rte_eth_tap.c |  99 ++--
 drivers/net/tap/tap_flow.c    |  23 +-
 4 files changed, 957 insertions(+), 62 deletions(-)
 create mode 100644 app/test/test_pmd_tap.c

-- 
2.51.0


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

* [PATCH 01/10] test: add unit tests for TAP PMD
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
@ 2026-02-15 19:52 ` Stephen Hemminger
  2026-02-16  0:46   ` Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 02/10] net/tap: replace runtime speed capability with constant Stephen Hemminger
                   ` (13 subsequent siblings)
  14 siblings, 1 reply; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-15 19:52 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a standalone test suite for the TAP PMD, modeled on the existing
test_pmd_ring tests. Exercises device configuration, link status,
stats, MTU, MAC address, promiscuous/allmulticast modes, queue
start/stop, link up/down, device stop/start, and multi-queue setup.

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

diff --git a/app/test/meson.build b/app/test/meson.build
index 48874037eb..b57fed424b 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -144,6 +144,7 @@ source_file_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'],
+    'test_pmd_tap.c': ['ethdev', 'net_tap', 'bus_vdev'],
     'test_pmu.c': ['pmu'],
     'test_power.c': ['power', 'power_acpi', 'power_kvm_vm', 'power_intel_pstate',
         'power_amd_pstate', 'power_cppc'],
diff --git a/app/test/test_pmd_tap.c b/app/test/test_pmd_tap.c
new file mode 100644
index 0000000000..998691e152
--- /dev/null
+++ b/app/test/test_pmd_tap.c
@@ -0,0 +1,896 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2024 Intel Corporation
+ */
+
+#include "test.h"
+
+#include <stdio.h>
+
+#ifndef RTE_EXEC_ENV_LINUX
+
+/* TAP PMD is only available on Linux */
+static int
+test_pmd_tap(void)
+{
+	printf("TAP PMD not supported on this platform, skipping test\n");
+	return TEST_SKIPPED;
+}
+
+#else /* RTE_EXEC_ENV_LINUX */
+
+#include <string.h>
+#include <unistd.h>
+
+#include <rte_ethdev.h>
+#include <rte_bus_vdev.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+
+#define SOCKET0 0
+#define RING_SIZE 256
+#define NB_MBUF 2048
+#define MAX_PKT_BURST 32
+#define PKT_LEN 64
+
+static struct rte_mempool *mp;
+static int tap_port0 = -1;
+static int tap_port1 = -1;
+
+static int
+test_tap_ethdev_configure(int port)
+{
+	struct rte_eth_conf port_conf;
+	struct rte_eth_link link;
+	int ret;
+
+	memset(&port_conf, 0, sizeof(struct rte_eth_conf));
+
+	ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+	if (ret < 0) {
+		printf("Configure failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_tx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL);
+	if (ret < 0) {
+		printf("TX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, mp);
+	if (ret < 0) {
+		printf("RX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0) {
+		printf("Error starting port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_link_get(port, &link);
+	if (ret < 0) {
+		printf("Link get failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d: link status %s, speed %u Mbps\n",
+	       port,
+	       link.link_status ? "up" : "down",
+	       link.link_speed);
+
+	return 0;
+}
+
+static struct rte_mbuf *
+create_test_packet(struct rte_mempool *pool, uint16_t pkt_len)
+{
+	struct rte_mbuf *mbuf;
+	struct rte_ether_hdr *eth_hdr;
+	struct rte_ipv4_hdr *ip_hdr;
+	uint8_t *payload;
+	uint16_t i;
+
+	mbuf = rte_pktmbuf_alloc(pool);
+	if (mbuf == NULL)
+		return NULL;
+
+	/* Ensure minimum packet size for Ethernet */
+	if (pkt_len < RTE_ETHER_MIN_LEN)
+		pkt_len = RTE_ETHER_MIN_LEN;
+
+	mbuf->data_len = pkt_len;
+	mbuf->pkt_len = pkt_len;
+
+	/* Create Ethernet header */
+	eth_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
+	memset(&eth_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN); /* broadcast */
+	memset(&eth_hdr->src_addr, 0x02, RTE_ETHER_ADDR_LEN);
+	eth_hdr->src_addr.addr_bytes[5] = 0x01;
+	eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+	/* Create simple IPv4 header */
+	ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
+	memset(ip_hdr, 0, sizeof(*ip_hdr));
+	ip_hdr->version_ihl = 0x45; /* IPv4, 20 byte header */
+	ip_hdr->total_length = rte_cpu_to_be_16(pkt_len - sizeof(*eth_hdr));
+	ip_hdr->time_to_live = 64;
+	ip_hdr->next_proto_id = IPPROTO_UDP;
+	ip_hdr->src_addr = rte_cpu_to_be_32(0x0A000001); /* 10.0.0.1 */
+	ip_hdr->dst_addr = rte_cpu_to_be_32(0x0A000002); /* 10.0.0.2 */
+
+	/* Fill payload with pattern */
+	payload = (uint8_t *)(ip_hdr + 1);
+	for (i = 0; i < pkt_len - sizeof(*eth_hdr) - sizeof(*ip_hdr); i++)
+		payload[i] = (uint8_t)(i & 0xFF);
+
+	return mbuf;
+}
+
+static int
+test_tap_send_receive(void)
+{
+	struct rte_mbuf *tx_mbufs[MAX_PKT_BURST];
+	struct rte_mbuf *rx_mbufs[MAX_PKT_BURST];
+	uint16_t nb_tx, nb_rx;
+	int i;
+
+	printf("Testing TAP packet send and receive\n");
+
+	/* Create test packets */
+	for (i = 0; i < MAX_PKT_BURST / 2; i++) {
+		tx_mbufs[i] = create_test_packet(mp, PKT_LEN);
+		if (tx_mbufs[i] == NULL) {
+			printf("Failed to create test packet %d\n", i);
+			/* Free already allocated packets */
+			while (--i >= 0)
+				rte_pktmbuf_free(tx_mbufs[i]);
+			return TEST_FAILED;
+		}
+	}
+
+	/* Send packets */
+	nb_tx = rte_eth_tx_burst(tap_port0, 0, tx_mbufs, MAX_PKT_BURST / 2);
+	printf("Transmitted %u packets on port %d\n", nb_tx, tap_port0);
+
+	/* Free any unsent packets */
+	for (i = nb_tx; i < MAX_PKT_BURST / 2; i++)
+		rte_pktmbuf_free(tx_mbufs[i]);
+
+	if (nb_tx == 0) {
+		printf("Warning: No packets transmitted (this may be expected if interface is not up)\n");
+		return TEST_SUCCESS;
+	}
+
+	/* Small delay to allow packets to be processed */
+	usleep(10000);
+
+	/* Try to receive packets (note: TAP loopback depends on kernel config) */
+	nb_rx = rte_eth_rx_burst(tap_port0, 0, rx_mbufs, MAX_PKT_BURST);
+	printf("Received %u packets on port %d\n", nb_rx, tap_port0);
+
+	/* Free received packets */
+	for (i = 0; i < nb_rx; i++)
+		rte_pktmbuf_free(rx_mbufs[i]);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_stats_get(int port)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	printf("Testing TAP PMD stats_get for port %d\n", port);
+
+	ret = rte_eth_stats_get(port, &stats);
+	if (ret != 0) {
+		printf("Error: failed to get stats for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d stats:\n", port);
+	printf("  ipackets: %"PRIu64"\n", stats.ipackets);
+	printf("  opackets: %"PRIu64"\n", stats.opackets);
+	printf("  ibytes:   %"PRIu64"\n", stats.ibytes);
+	printf("  obytes:   %"PRIu64"\n", stats.obytes);
+	printf("  ierrors:  %"PRIu64"\n", stats.ierrors);
+	printf("  oerrors:  %"PRIu64"\n", stats.oerrors);
+
+	return 0;
+}
+
+static int
+test_tap_stats_reset(int port)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	printf("Testing TAP PMD stats_reset for port %d\n", port);
+
+	ret = rte_eth_stats_reset(port);
+	if (ret != 0) {
+		printf("Error: failed to reset stats for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_stats_get(port, &stats);
+	if (ret != 0) {
+		printf("Error: failed to get stats after reset for port %d\n",
+		       port);
+		return -1;
+	}
+
+	/* After reset, all stats should be zero */
+	if (stats.ipackets != 0 || stats.opackets != 0 ||
+	    stats.ibytes != 0 || stats.obytes != 0 ||
+	    stats.ierrors != 0 || stats.oerrors != 0) {
+		printf("Error: port %d stats are not zero after reset\n", port);
+		return -1;
+	}
+
+	printf("Stats reset successful for port %d\n", port);
+	return 0;
+}
+
+static int
+test_tap_link_status(int port)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	printf("Testing TAP PMD link status for port %d\n", port);
+
+	ret = rte_eth_link_get_nowait(port, &link);
+	if (ret < 0) {
+		printf("Error: failed to get link status for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d link: status=%s speed=%u duplex=%s\n",
+	       port,
+	       link.link_status ? "up" : "down",
+	       link.link_speed,
+	       link.link_duplex ? "full" : "half");
+
+	return 0;
+}
+
+static int
+test_tap_dev_info(int port)
+{
+	struct rte_eth_dev_info dev_info;
+	int ret;
+
+	printf("Testing TAP PMD dev_info for port %d\n", port);
+
+	ret = rte_eth_dev_info_get(port, &dev_info);
+	if (ret != 0) {
+		printf("Error: failed to get dev info for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d device info:\n", port);
+	printf("  driver_name: %s\n", dev_info.driver_name);
+	printf("  if_index: %u\n", dev_info.if_index);
+	printf("  max_rx_queues: %u\n", dev_info.max_rx_queues);
+	printf("  max_tx_queues: %u\n", dev_info.max_tx_queues);
+	printf("  max_rx_pktlen: %u\n", dev_info.max_rx_pktlen);
+
+	/* Verify this is indeed a TAP device */
+	if (strcmp(dev_info.driver_name, "net_tap") != 0 &&
+	    strcmp(dev_info.driver_name, "net_tun") != 0) {
+		printf("Warning: unexpected driver name: %s\n",
+		       dev_info.driver_name);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_mtu(int port)
+{
+	uint16_t mtu;
+	int ret;
+
+	printf("Testing TAP PMD MTU operations for port %d\n", port);
+
+	/* Get current MTU */
+	ret = rte_eth_dev_get_mtu(port, &mtu);
+	if (ret != 0) {
+		printf("Error: failed to get MTU for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Current MTU for port %d: %u\n", port, mtu);
+
+	/* Try to set a new MTU */
+	ret = rte_eth_dev_set_mtu(port, 1400);
+	if (ret != 0) {
+		printf("Warning: failed to set MTU to 1400 for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		/* Not a fatal error - may require privileges */
+	} else {
+		printf("MTU set to 1400 for port %d\n", port);
+
+		/* Restore original MTU */
+		ret = rte_eth_dev_set_mtu(port, mtu);
+		if (ret != 0)
+			printf("Warning: failed to restore MTU for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_mac_addr(int port)
+{
+	struct rte_ether_addr mac_addr;
+	int ret;
+
+	printf("Testing TAP PMD MAC address for port %d\n", port);
+
+	ret = rte_eth_macaddr_get(port, &mac_addr);
+	if (ret != 0) {
+		printf("Error: failed to get MAC address for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d MAC address: " RTE_ETHER_ADDR_PRT_FMT "\n",
+	       port, RTE_ETHER_ADDR_BYTES(&mac_addr));
+
+	return 0;
+}
+
+static int
+test_tap_promiscuous(int port)
+{
+	int ret;
+	int promisc_enabled;
+
+	printf("Testing TAP PMD promiscuous mode for port %d\n", port);
+
+	/* Get current promiscuous state */
+	promisc_enabled = rte_eth_promiscuous_get(port);
+	printf("Promiscuous mode initially %s for port %d\n",
+	       promisc_enabled ? "enabled" : "disabled", port);
+
+	/* Enable promiscuous mode */
+	ret = rte_eth_promiscuous_enable(port);
+	if (ret != 0) {
+		printf("Warning: failed to enable promiscuous mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_promiscuous_get(port) != 1) {
+			printf("Error: promiscuous mode not enabled after enable call\n");
+			return -1;
+		}
+		printf("Promiscuous mode enabled for port %d\n", port);
+	}
+
+	/* Disable promiscuous mode */
+	ret = rte_eth_promiscuous_disable(port);
+	if (ret != 0) {
+		printf("Warning: failed to disable promiscuous mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_promiscuous_get(port) != 0) {
+			printf("Error: promiscuous mode not disabled after disable call\n");
+			return -1;
+		}
+		printf("Promiscuous mode disabled for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_allmulti(int port)
+{
+	int ret;
+	int allmulti_enabled;
+
+	printf("Testing TAP PMD allmulticast mode for port %d\n", port);
+
+	/* Get current allmulticast state */
+	allmulti_enabled = rte_eth_allmulticast_get(port);
+	printf("Allmulticast mode initially %s for port %d\n",
+	       allmulti_enabled ? "enabled" : "disabled", port);
+
+	/* Enable allmulticast mode */
+	ret = rte_eth_allmulticast_enable(port);
+	if (ret != 0) {
+		printf("Warning: failed to enable allmulticast mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_allmulticast_get(port) != 1) {
+			printf("Error: allmulticast mode not enabled after enable call\n");
+			return -1;
+		}
+		printf("Allmulticast mode enabled for port %d\n", port);
+	}
+
+	/* Disable allmulticast mode */
+	ret = rte_eth_allmulticast_disable(port);
+	if (ret != 0) {
+		printf("Warning: failed to disable allmulticast mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_allmulticast_get(port) != 0) {
+			printf("Error: allmulticast mode not disabled after disable call\n");
+			return -1;
+		}
+		printf("Allmulticast mode disabled for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_queue_start_stop(int port)
+{
+	int ret;
+
+	printf("Testing TAP PMD queue start/stop for port %d\n", port);
+
+	/* Stop RX queue */
+	ret = rte_eth_dev_rx_queue_stop(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to stop RX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("RX queue stopped for port %d\n", port);
+
+	/* Stop TX queue */
+	ret = rte_eth_dev_tx_queue_stop(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to stop TX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("TX queue stopped for port %d\n", port);
+
+	/* Start RX queue */
+	ret = rte_eth_dev_rx_queue_start(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to start RX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("RX queue started for port %d\n", port);
+
+	/* Start TX queue */
+	ret = rte_eth_dev_tx_queue_start(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to start TX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("TX queue started for port %d\n", port);
+
+	return 0;
+}
+
+static int
+test_tap_link_up_down(int port)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	printf("Testing TAP PMD link up/down for port %d\n", port);
+
+	/* Set link down */
+	ret = rte_eth_dev_set_link_down(port);
+	if (ret != 0) {
+		printf("Warning: failed to set link down for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		ret = rte_eth_link_get_nowait(port, &link);
+		if (ret == 0)
+			printf("Link status after set_link_down: %s\n",
+			       link.link_status ? "up" : "down");
+	}
+
+	/* Set link up */
+	ret = rte_eth_dev_set_link_up(port);
+	if (ret != 0) {
+		printf("Warning: failed to set link up for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		ret = rte_eth_link_get_nowait(port, &link);
+		if (ret == 0)
+			printf("Link status after set_link_up: %s\n",
+			       link.link_status ? "up" : "down");
+	}
+
+	return 0;
+}
+
+static int
+test_tap_dev_stop_start(int port)
+{
+	int ret;
+
+	printf("Testing TAP PMD device stop/start for port %d\n", port);
+
+	/* Stop the device */
+	ret = rte_eth_dev_stop(port);
+	if (ret != 0) {
+		printf("Error: failed to stop port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Device stopped for port %d\n", port);
+
+	/* Start the device again */
+	ret = rte_eth_dev_start(port);
+	if (ret != 0) {
+		printf("Error: failed to start port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Device started for port %d\n", port);
+
+	return 0;
+}
+
+static int
+test_tap_multi_queue(void)
+{
+	struct rte_eth_conf port_conf;
+	struct rte_mempool *mq_mp;
+	const uint16_t nb_queues = 2;
+	int ret;
+	int port = -1;
+
+	printf("Testing TAP PMD multi-queue configuration\n");
+
+	/* Create a separate mempool for multi-queue test */
+	mq_mp = rte_pktmbuf_pool_create("tap_mq_pool", NB_MBUF, 32, 0,
+					RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+	if (mq_mp == NULL) {
+		printf("Warning: failed to create mempool for multi-queue test: %s\n",
+		       rte_strerror(rte_errno));
+		return TEST_SKIPPED;
+	}
+
+	/* Create a new TAP device for multi-queue test */
+	ret = rte_vdev_init("net_tap_mq", "iface=dtap_mq");
+	if (ret < 0) {
+		printf("Warning: failed to create multi-queue TAP device: %s\n",
+		       rte_strerror(-ret));
+		rte_mempool_free(mq_mp);
+		return TEST_SKIPPED;
+	}
+
+	/* Find the port */
+	RTE_ETH_FOREACH_DEV(port) {
+		char name[RTE_ETH_NAME_MAX_LEN];
+		if (rte_eth_dev_get_name_by_port(port, name) == 0 &&
+		    strstr(name, "net_tap_mq"))
+			break;
+	}
+
+	if (port < 0 || port >= RTE_MAX_ETHPORTS) {
+		printf("Error: could not find multi-queue TAP device\n");
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_FAILED;
+	}
+
+	memset(&port_conf, 0, sizeof(struct rte_eth_conf));
+
+	/* Configure with multiple queues */
+	ret = rte_eth_dev_configure(port, nb_queues, nb_queues, &port_conf);
+	if (ret < 0) {
+		printf("Warning: multi-queue configure failed: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_SKIPPED;
+	}
+
+	/* Setup TX queues */
+	for (uint16_t q = 0; q < nb_queues; q++) {
+		ret = rte_eth_tx_queue_setup(port, q, RING_SIZE, SOCKET0, NULL);
+		if (ret < 0) {
+			printf("Error: TX queue %u setup failed: %s\n",
+			       q, rte_strerror(-ret));
+			rte_vdev_uninit("net_tap_mq");
+			rte_mempool_free(mq_mp);
+			return TEST_FAILED;
+		}
+	}
+
+	/* Setup RX queues */
+	for (uint16_t q = 0; q < nb_queues; q++) {
+		ret = rte_eth_rx_queue_setup(port, q, RING_SIZE, SOCKET0, NULL, mq_mp);
+		if (ret < 0) {
+			printf("Error: RX queue %u setup failed: %s\n",
+			       q, rte_strerror(-ret));
+			rte_vdev_uninit("net_tap_mq");
+			rte_mempool_free(mq_mp);
+			return TEST_FAILED;
+		}
+	}
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0) {
+		printf("Error: failed to start multi-queue port: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_FAILED;
+	}
+
+	printf("Multi-queue TAP device configured with %u queues\n", nb_queues);
+
+	/* Cleanup */
+	rte_eth_dev_stop(port);
+	rte_eth_dev_close(port);
+	rte_vdev_uninit("net_tap_mq");
+	rte_mempool_free(mq_mp);
+
+	return TEST_SUCCESS;
+}
+
+static void
+test_tap_cleanup(void)
+{
+	int ret;
+
+	printf("Cleaning up TAP PMD test resources\n");
+
+	if (tap_port0 >= 0) {
+		ret = rte_eth_dev_stop(tap_port0);
+		if (ret != 0)
+			printf("Warning: failed to stop port %d: %s\n",
+			       tap_port0, rte_strerror(-ret));
+		rte_eth_dev_close(tap_port0);
+	}
+
+	if (tap_port1 >= 0) {
+		ret = rte_eth_dev_stop(tap_port1);
+		if (ret != 0)
+			printf("Warning: failed to stop port %d: %s\n",
+			       tap_port1, rte_strerror(-ret));
+		rte_eth_dev_close(tap_port1);
+	}
+
+	rte_vdev_uninit("net_tap0");
+	rte_vdev_uninit("net_tap1");
+
+	if (mp != NULL) {
+		rte_mempool_free(mp);
+		mp = NULL;
+	}
+
+	tap_port0 = -1;
+	tap_port1 = -1;
+}
+
+static int
+test_tap_setup(void)
+{
+	int ret;
+	uint16_t port_id;
+
+	printf("Setting up TAP PMD test\n");
+
+	/* Create mempool */
+	mp = rte_pktmbuf_pool_create("tap_test_pool", NB_MBUF, 32, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+	if (mp == NULL) {
+		printf("Error: failed to create mempool: %s\n",
+		       rte_strerror(rte_errno));
+		return -1;
+	}
+
+	/* Create first TAP device */
+	ret = rte_vdev_init("net_tap0", "iface=dtap_test0");
+	if (ret < 0) {
+		printf("Error: failed to create TAP device net_tap0: %s\n",
+		       rte_strerror(-ret));
+		rte_mempool_free(mp);
+		mp = NULL;
+		return -1;
+	}
+
+	/* Create second TAP device */
+	ret = rte_vdev_init("net_tap1", "iface=dtap_test1");
+	if (ret < 0) {
+		printf("Error: failed to create TAP device net_tap1: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap0");
+		rte_mempool_free(mp);
+		mp = NULL;
+		return -1;
+	}
+
+	/* Find the port IDs */
+	RTE_ETH_FOREACH_DEV(port_id) {
+		char name[RTE_ETH_NAME_MAX_LEN];
+		if (rte_eth_dev_get_name_by_port(port_id, name) != 0)
+			continue;
+
+		if (strstr(name, "net_tap0"))
+			tap_port0 = port_id;
+		else if (strstr(name, "net_tap1"))
+			tap_port1 = port_id;
+	}
+
+	if (tap_port0 < 0 || tap_port1 < 0) {
+		printf("Error: failed to find TAP port IDs\n");
+		test_tap_cleanup();
+		return -1;
+	}
+
+	printf("Created TAP devices: tap_port0=%d, tap_port1=%d\n",
+	       tap_port0, tap_port1);
+
+	return 0;
+}
+
+/* Individual test case wrappers */
+
+static int
+test_tap_configure_port0(void)
+{
+	return test_tap_ethdev_configure(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_configure_port1(void)
+{
+	return test_tap_ethdev_configure(tap_port1) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_packet_send_receive(void)
+{
+	return test_tap_send_receive();
+}
+
+static int
+test_tap_get_stats(void)
+{
+	if (test_tap_stats_get(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_stats_get(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_reset_stats(void)
+{
+	if (test_tap_stats_reset(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_stats_reset(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_get_link_status(void)
+{
+	if (test_tap_link_status(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_link_status(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_get_dev_info(void)
+{
+	if (test_tap_dev_info(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_dev_info(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_mtu_ops(void)
+{
+	return test_tap_mtu(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_mac_addr_get(void)
+{
+	if (test_tap_mac_addr(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_mac_addr(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_promisc_mode(void)
+{
+	return test_tap_promiscuous(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_allmulti_mode(void)
+{
+	return test_tap_allmulti(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_queue_ops(void)
+{
+	return test_tap_queue_start_stop(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_link_ops(void)
+{
+	return test_tap_link_up_down(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_stop_start(void)
+{
+	return test_tap_dev_stop_start(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_multiqueue(void)
+{
+	return test_tap_multi_queue();
+}
+
+static struct unit_test_suite test_pmd_tap_suite = {
+	.setup = test_tap_setup,
+	.teardown = test_tap_cleanup,
+	.suite_name = "TAP PMD Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_tap_configure_port0),
+		TEST_CASE(test_tap_configure_port1),
+		TEST_CASE(test_tap_get_dev_info),
+		TEST_CASE(test_tap_get_link_status),
+		TEST_CASE(test_tap_mac_addr_get),
+		TEST_CASE(test_tap_get_stats),
+		TEST_CASE(test_tap_reset_stats),
+		TEST_CASE(test_tap_packet_send_receive),
+		TEST_CASE(test_tap_promisc_mode),
+		TEST_CASE(test_tap_allmulti_mode),
+		TEST_CASE(test_tap_mtu_ops),
+		TEST_CASE(test_tap_queue_ops),
+		TEST_CASE(test_tap_link_ops),
+		TEST_CASE(test_tap_stop_start),
+		TEST_CASE(test_tap_multiqueue),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_pmd_tap(void)
+{
+	return unit_test_suite_runner(&test_pmd_tap_suite);
+}
+
+#endif /* RTE_EXEC_ENV_LINUX */
+
+REGISTER_FAST_TEST(tap_pmd_autotest, NOHUGE_OK, ASAN_OK, test_pmd_tap);
-- 
2.51.0


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

* [PATCH 02/10] net/tap: replace runtime speed capability with constant
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 01/10] test: add unit tests for TAP PMD Stephen Hemminger
@ 2026-02-15 19:52 ` Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 03/10] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-15 19:52 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The TAP/TUN virtual device always operates at 10G. The link speed is
set in the static initializer for pmd_link, so the runtime assignments
in rte_pmd_tap_probe() and rte_pmd_tun_probe() are redundant.

Replace tap_dev_speed_capa(), which dynamically computed speed
capabilities against pmd_link.link_speed, with a compile-time
constant TAP_SPEED_CAPA. Remove the unused 'speed' variable and
redundant assignments.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 7a8a98cddb..aa236cf967 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -891,39 +891,13 @@ tap_dev_configure(struct rte_eth_dev *dev)
 	return 0;
 }
 
-static uint32_t
-tap_dev_speed_capa(void)
-{
-	uint32_t speed = pmd_link.link_speed;
-	uint32_t capa = 0;
-
-	if (speed >= RTE_ETH_SPEED_NUM_10M)
-		capa |= RTE_ETH_LINK_SPEED_10M;
-	if (speed >= RTE_ETH_SPEED_NUM_100M)
-		capa |= RTE_ETH_LINK_SPEED_100M;
-	if (speed >= RTE_ETH_SPEED_NUM_1G)
-		capa |= RTE_ETH_LINK_SPEED_1G;
-	if (speed >= RTE_ETH_SPEED_NUM_5G)
-		capa |= RTE_ETH_LINK_SPEED_2_5G;
-	if (speed >= RTE_ETH_SPEED_NUM_5G)
-		capa |= RTE_ETH_LINK_SPEED_5G;
-	if (speed >= RTE_ETH_SPEED_NUM_10G)
-		capa |= RTE_ETH_LINK_SPEED_10G;
-	if (speed >= RTE_ETH_SPEED_NUM_20G)
-		capa |= RTE_ETH_LINK_SPEED_20G;
-	if (speed >= RTE_ETH_SPEED_NUM_25G)
-		capa |= RTE_ETH_LINK_SPEED_25G;
-	if (speed >= RTE_ETH_SPEED_NUM_40G)
-		capa |= RTE_ETH_LINK_SPEED_40G;
-	if (speed >= RTE_ETH_SPEED_NUM_50G)
-		capa |= RTE_ETH_LINK_SPEED_50G;
-	if (speed >= RTE_ETH_SPEED_NUM_56G)
-		capa |= RTE_ETH_LINK_SPEED_56G;
-	if (speed >= RTE_ETH_SPEED_NUM_100G)
-		capa |= RTE_ETH_LINK_SPEED_100G;
-
-	return capa;
-}
+/* Speed capabilities for virtual TAP/TUN device (always 10G) */
+#define TAP_SPEED_CAPA (RTE_ETH_LINK_SPEED_10M | \
+			RTE_ETH_LINK_SPEED_100M | \
+			RTE_ETH_LINK_SPEED_1G | \
+			RTE_ETH_LINK_SPEED_2_5G | \
+			RTE_ETH_LINK_SPEED_5G | \
+			RTE_ETH_LINK_SPEED_10G)
 
 static int
 tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
@@ -936,7 +910,7 @@ tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
 	dev_info->max_rx_queues = RTE_PMD_TAP_MAX_QUEUES;
 	dev_info->max_tx_queues = RTE_PMD_TAP_MAX_QUEUES;
 	dev_info->min_rx_bufsize = 0;
-	dev_info->speed_capa = tap_dev_speed_capa();
+	dev_info->speed_capa = TAP_SPEED_CAPA;
 	dev_info->rx_queue_offload_capa = TAP_RX_OFFLOAD;
 	dev_info->rx_offload_capa = dev_info->rx_queue_offload_capa;
 	dev_info->tx_queue_offload_capa = TAP_TX_OFFLOAD;
@@ -2325,7 +2299,6 @@ set_mac_type(const char *key __rte_unused,
  * Open a TUN interface device. TUN PMD
  * 1) sets tap_type as false
  * 2) intakes iface as argument.
- * 3) as interface is virtual set speed to 10G
  */
 static int
 rte_pmd_tun_probe(struct rte_vdev_device *dev)
@@ -2373,7 +2346,6 @@ rte_pmd_tun_probe(struct rte_vdev_device *dev)
 			}
 		}
 	}
-	pmd_link.link_speed = RTE_ETH_SPEED_NUM_10G;
 
 	TAP_LOG(DEBUG, "Initializing pmd_tun for %s", name);
 
@@ -2495,7 +2467,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 	const char *name, *params;
 	int ret;
 	struct rte_kvargs *kvlist = NULL;
-	int speed;
 	char tap_name[RTE_ETH_NAME_MAX_LEN];
 	char remote_iface[RTE_ETH_NAME_MAX_LEN];
 	struct rte_ether_addr user_mac = { .addr_bytes = {0} };
@@ -2545,8 +2516,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 		return 0;
 	}
 
-	speed = RTE_ETH_SPEED_NUM_10G;
-
 	/* use tap%d which causes kernel to choose next available */
 	strlcpy(tap_name, DEFAULT_TAP_NAME "%d", RTE_ETH_NAME_MAX_LEN);
 	memset(remote_iface, 0, RTE_ETH_NAME_MAX_LEN);
@@ -2587,7 +2556,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 				persist = 1;
 		}
 	}
-	pmd_link.link_speed = speed;
 
 	TAP_LOG(DEBUG, "Initializing pmd_tap for %s", name);
 
-- 
2.51.0


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

* [PATCH 03/10] net/tap: clarify TUN/TAP flag assignment
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 01/10] test: add unit tests for TAP PMD Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 02/10] net/tap: replace runtime speed capability with constant Stephen Hemminger
@ 2026-02-15 19:52 ` Stephen Hemminger
  2026-02-15 21:45   ` Morten Brørup
  2026-02-15 19:52 ` [PATCH 04/10] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
                   ` (11 subsequent siblings)
  14 siblings, 1 reply; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-15 19:52 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Replace a ternary expression that relied on operator precedence
between '?:' and '|' with an explicit if/else. No functional change.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index aa236cf967..31c8c185e9 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -154,8 +154,11 @@ tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
 	 * Do not set IFF_NO_PI as packet information header will be needed
 	 * to check if a received packet has been truncated.
 	 */
-	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
-		IFF_TAP : IFF_TUN | IFF_POINTOPOINT;
+	if (pmd->type == ETH_TUNTAP_TYPE_TAP)
+		ifr.ifr_flags = IFF_TAP;
+	else
+		ifr.ifr_flags = IFF_TUN | IFF_POINTOPOINT;
+
 	strlcpy(ifr.ifr_name, pmd->name, IFNAMSIZ);
 
 	fd = open(TUN_TAP_DEV_PATH, O_RDWR);
-- 
2.51.0


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

* [PATCH 04/10] net/tap: extend fixed MAC range to 16 bits
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (2 preceding siblings ...)
  2026-02-15 19:52 ` [PATCH 03/10] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
@ 2026-02-15 19:52 ` Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 05/10] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
                   ` (10 subsequent siblings)
  14 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-15 19:52 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The generated fixed MAC address stored the interface index in a single
byte, which wraps after 256 devices and produces duplicate MACs under
repeated hot-plug. Spread the index across the last two bytes of the
address, extending the unique range to 65536.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 31c8c185e9..fcc452527b 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2276,13 +2276,13 @@ set_mac_type(const char *key __rte_unused,
 		return 0;
 
 	if (!strncasecmp(ETH_TAP_MAC_FIXED, value, strlen(ETH_TAP_MAC_FIXED))) {
-		static int iface_idx;
+		static uint16_t iface_idx;
 
 		/* fixed mac = 02:64:74:61:70:<iface_idx> */
-		memcpy((char *)user_mac->addr_bytes, "\002dtap",
-			RTE_ETHER_ADDR_LEN);
-		user_mac->addr_bytes[RTE_ETHER_ADDR_LEN - 1] =
-			iface_idx++ + '0';
+		memcpy((char *)user_mac->addr_bytes, "\002dtap", RTE_ETHER_ADDR_LEN);
+		user_mac->addr_bytes[4] = iface_idx >> 8;
+		user_mac->addr_bytes[5] = (uint8_t)iface_idx;
+		++iface_idx;
 		goto success;
 	}
 
-- 
2.51.0


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

* [PATCH 05/10] net/tap: skip checksum on truncated L4 headers
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (3 preceding siblings ...)
  2026-02-15 19:52 ` [PATCH 04/10] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
@ 2026-02-15 19:52 ` Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 06/10] net/tap: fix resource leaks in tap create error path Stephen Hemminger
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-15 19:52 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a bounds check before accessing the UDP or TCP header in
tap_verify_csum(). A single-segment packet whose L4 header extends
past rte_pktmbuf_data_len() would cause an out-of-bounds read.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index fcc452527b..4fa71429fe 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -342,7 +342,7 @@ tap_verify_csum(struct rte_mbuf *mbuf)
 		 * greater than the total received size
 		 */
 		if (l2_len + rte_be_to_cpu_16(iph->total_length) >
-				rte_pktmbuf_data_len(mbuf))
+		    rte_pktmbuf_data_len(mbuf))
 			return;
 
 		cksum = ~rte_raw_cksum(iph, l3_len);
@@ -357,7 +357,7 @@ tap_verify_csum(struct rte_mbuf *mbuf)
 		 * greater than the total received size
 		 */
 		if (l2_len + l3_len + rte_be_to_cpu_16(iph->payload_len) >
-				rte_pktmbuf_data_len(mbuf))
+		    rte_pktmbuf_data_len(mbuf))
 			return;
 	} else {
 		/* - RTE_PTYPE_L3_IPV4_EXT_UNKNOWN cannot happen because
@@ -367,8 +367,15 @@ tap_verify_csum(struct rte_mbuf *mbuf)
 		 */
 		return;
 	}
+
 	if (l4 == RTE_PTYPE_L4_UDP || l4 == RTE_PTYPE_L4_TCP) {
 		int cksum_ok;
+		const unsigned int l4_min_len = (l4 == RTE_PTYPE_L4_UDP)
+			? sizeof(struct rte_udp_hdr) : sizeof(struct rte_tcp_hdr);
+
+		/* Don't verify checksum if L4 header is truncated */
+		if (l2_len + l3_len + l4_min_len > rte_pktmbuf_data_len(mbuf))
+			return;
 
 		l4_hdr = rte_pktmbuf_mtod_offset(mbuf, void *, l2_len + l3_len);
 		/* Don't verify checksum for multi-segment packets. */
-- 
2.51.0


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

* [PATCH 06/10] net/tap: fix resource leaks in tap create error path
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (4 preceding siblings ...)
  2026-02-15 19:52 ` [PATCH 05/10] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
@ 2026-02-15 19:52 ` Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 07/10] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-15 19:52 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Raslan Darawsheh, Ferruh Yigit

Correct two leaks in eth_dev_tap_create():

- process_private was never freed on error_exit. Add free() before
  releasing the port.
- If the process_private malloc failed, the function returned -1
  directly without releasing the ethdev port allocated by
  rte_eth_vdev_allocate(). Jump to a new error_exit_nodev_release
  label instead.

Bugzilla ID: 1881
Fixes: ed8132e7c912 ("net/tap: move fds of queues to be in process private")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 4fa71429fe..0bc0e11e0b 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2012,7 +2012,7 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	process_private = malloc(sizeof(struct pmd_process_private));
 	if (process_private == NULL) {
 		TAP_LOG(ERR, "Failed to alloc memory for process private");
-		return -1;
+		goto error_exit_nodev_release;
 	}
 	memset(process_private, 0, sizeof(struct pmd_process_private));
 
@@ -2202,9 +2202,12 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 		close(pmd->nlsk_fd);
 	if (pmd->ka_fd != -1)
 		close(pmd->ka_fd);
+	rte_intr_instance_free(pmd->intr_handle);
+	free(dev->process_private);
+
+error_exit_nodev_release:
 	/* mac_addrs must not be freed alone because part of dev_private */
 	dev->data->mac_addrs = NULL;
-	rte_intr_instance_free(pmd->intr_handle);
 	rte_eth_dev_release_port(dev);
 
 error_exit_nodev:
-- 
2.51.0


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

* [PATCH 07/10] net/tap: fix resource leaks in secondary process probe
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (5 preceding siblings ...)
  2026-02-15 19:52 ` [PATCH 06/10] net/tap: fix resource leaks in tap create error path Stephen Hemminger
@ 2026-02-15 19:52 ` Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 08/10] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-15 19:52 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, stable, Thomas Monjalon, Raslan Darawsheh,
	Ferruh Yigit

Four error paths in the secondary-process branch of
rte_pmd_tap_probe() returned -1 without cleanup:

- primary process not alive: leaked eth_dev
- process_private malloc failure: leaked eth_dev
- tap_mp_attach_queues failure: leaked eth_dev and process_private
- rte_mp_action_register failure: leaked eth_dev and process_private

Add secondary_fail and secondary_fail_pp labels to free
process_private and release the ethdev port.

Bugzilla ID: 1881
Fixes: c9aa56edec8e ("net/tap: access primary process queues from secondary")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 0bc0e11e0b..251b05b27b 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2502,31 +2502,38 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 		eth_dev->tx_pkt_burst = pmd_tx_burst;
 		if (!rte_eal_primary_proc_alive(NULL)) {
 			TAP_LOG(ERR, "Primary process is missing");
-			return -1;
+			goto secondary_fail;
 		}
 		eth_dev->process_private = malloc(sizeof(struct pmd_process_private));
 		if (eth_dev->process_private == NULL) {
 			TAP_LOG(ERR,
 				"Failed to alloc memory for process private");
-			return -1;
+			goto secondary_fail;
 		}
 		memset(eth_dev->process_private, 0, sizeof(struct pmd_process_private));
 
 		ret = tap_mp_attach_queues(name, eth_dev);
 		if (ret != 0)
-			return -1;
+			goto secondary_fail_pp;
 
 		if (!tap_devices_count) {
 			ret = rte_mp_action_register(TAP_MP_REQ_START_RXTX, tap_mp_req_start_rxtx);
 			if (ret < 0 && rte_errno != ENOTSUP) {
 				TAP_LOG(ERR, "tap: Failed to register IPC callback: %s",
 					strerror(rte_errno));
-				return -1;
+				goto secondary_fail_pp;
 			}
 		}
 		tap_devices_count++;
 		rte_eth_dev_probing_finish(eth_dev);
 		return 0;
+
+secondary_fail_pp:
+		free(eth_dev->process_private);
+		eth_dev->process_private = NULL;
+secondary_fail:
+		rte_eth_dev_release_port(eth_dev);
+		return -1;
 	}
 
 	/* use tap%d which causes kernel to choose next available */
-- 
2.51.0


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

* [PATCH 08/10] net/tap: free IPC reply buffer on queue count mismatch
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (6 preceding siblings ...)
  2026-02-15 19:52 ` [PATCH 07/10] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
@ 2026-02-15 19:52 ` Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 09/10] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
                   ` (6 subsequent siblings)
  14 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-15 19:52 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, stable, Anatoly Burakov, Herakliusz Lipiec,
	Ferruh Yigit

In tap_mp_attach_queues(), if the reply queue count does not match
the number of received file descriptors, the function returns -1
without freeing the reply buffer allocated by rte_mp_request_sync().
Add the missing free().

Bugzilla ID: 1881
Fixes: 9ad43ad8fbee ("net/tap: fix potential IPC buffer overrun")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 251b05b27b..6676a40d69 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2407,6 +2407,7 @@ tap_mp_attach_queues(const char *port_name, struct rte_eth_dev *dev)
 	/* Attach the queues from received file descriptors */
 	if (reply_param->q_count != reply->num_fds) {
 		TAP_LOG(ERR, "Unexpected number of fds received");
+		free(reply);
 		return -1;
 	}
 
-- 
2.51.0


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

* [PATCH 09/10] net/tap: fix use-after-free on remote flow creation failure
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (7 preceding siblings ...)
  2026-02-15 19:52 ` [PATCH 08/10] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
@ 2026-02-15 19:52 ` Stephen Hemminger
  2026-02-15 19:52 ` [PATCH 10/10] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
                   ` (5 subsequent siblings)
  14 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-15 19:52 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Pascal Mazon, Olga Shern, Keith Wiles

After a local TC filter rule is installed and the flow is inserted
into pmd->flows, failure during remote flow creation jumps to the
fail label which frees the flow without removing it from the list
and without deleting the kernel-side TC rule.

Send RTM_DELTFILTER to clean up the local rule and call
LIST_REMOVE before freeing.

Bugzilla ID: 1881
Fixes: 2bc06869cd94 ("net/tap: add remote netdevice traffic capture")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 9d4ef27a8a..427faf75d5 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -1293,7 +1293,7 @@ tap_flow_create(struct rte_eth_dev *dev,
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
 				"cannot allocate memory for rte_flow");
-			goto fail;
+			goto fail_remove;
 		}
 		msg = &remote_flow->msg;
 		/* set the rule if_index for the remote netdevice */
@@ -1307,14 +1307,14 @@ tap_flow_create(struct rte_eth_dev *dev,
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "rte flow rule validation failed");
-			goto fail;
+			goto fail_remove;
 		}
 		err = tap_nl_send(pmd->nlsk_fd, &msg->nh);
 		if (err < 0) {
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "Failure sending nl request");
-			goto fail;
+			goto fail_remove;
 		}
 		err = tap_nl_recv_ack(pmd->nlsk_fd);
 		if (err < 0) {
@@ -1325,15 +1325,22 @@ tap_flow_create(struct rte_eth_dev *dev,
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL,
 				"overlapping rules or Kernel too old for flower support");
-			goto fail;
+			goto fail_remove;
 		}
 		flow->remote_flow = remote_flow;
 	}
 	return flow;
+
+fail_remove:
+	/* Delete the local TC rule that was already installed */
+	flow->msg.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	flow->msg.nh.nlmsg_type = RTM_DELTFILTER;
+	if (tap_nl_send(pmd->nlsk_fd, &flow->msg.nh) >= 0)
+		tap_nl_recv_ack(pmd->nlsk_fd);
+	LIST_REMOVE(flow, next);
 fail:
 	rte_free(remote_flow);
-	if (flow)
-		tap_flow_free(pmd, flow);
+	tap_flow_free(pmd, flow);
 	return NULL;
 }
 
-- 
2.51.0


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

* [PATCH 10/10] net/tap: free remote flow when implicit rule already exists
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (8 preceding siblings ...)
  2026-02-15 19:52 ` [PATCH 09/10] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
@ 2026-02-15 19:52 ` Stephen Hemminger
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-15 19:52 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Ophir Munk, Raslan Darawsheh,
	Keith Wiles

When tap_flow_implicit_create() gets EEXIST from the kernel, the
allocated remote_flow is never freed. Add rte_free() on that path.

Bugzilla ID: 1880
Fixes: 2ef1c0da894a ("net/tap: fix isolation mode toggling")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/tap_flow.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 427faf75d5..da1e70019a 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -1625,8 +1625,10 @@ int tap_flow_implicit_create(struct pmd_internals *pmd,
 	err = tap_nl_recv_ack(pmd->nlsk_fd);
 	if (err < 0) {
 		/* Silently ignore re-entering existing rule */
-		if (errno == EEXIST)
+		if (errno == EEXIST) {
+			rte_free(remote_flow);
 			goto success;
+		}
 		TAP_LOG(ERR,
 			"Kernel refused TC filter rule creation (%d): %s",
 			errno, strerror(errno));
-- 
2.51.0


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

* RE: [PATCH 03/10] net/tap: clarify TUN/TAP flag assignment
  2026-02-15 19:52 ` [PATCH 03/10] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
@ 2026-02-15 21:45   ` Morten Brørup
  2026-02-16  0:47     ` Stephen Hemminger
  0 siblings, 1 reply; 82+ messages in thread
From: Morten Brørup @ 2026-02-15 21:45 UTC (permalink / raw)
  To: Stephen Hemminger, dev

> Replace a ternary expression that relied on operator precedence
> between '?:' and '|' with an explicit if/else. No functional change.
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---
>  drivers/net/tap/rte_eth_tap.c | 7 +++++--
>  1 file changed, 5 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/net/tap/rte_eth_tap.c
> b/drivers/net/tap/rte_eth_tap.c
> index aa236cf967..31c8c185e9 100644
> --- a/drivers/net/tap/rte_eth_tap.c
> +++ b/drivers/net/tap/rte_eth_tap.c
> @@ -154,8 +154,11 @@ tun_alloc(struct pmd_internals *pmd, int
> is_keepalive, int persistent)
>  	 * Do not set IFF_NO_PI as packet information header will be
> needed
>  	 * to check if a received packet has been truncated.
>  	 */
> -	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
> -		IFF_TAP : IFF_TUN | IFF_POINTOPOINT;

For unconditional assignment, I prefer ternary expression over if/else.
If you want to improve readability instead of relying on operator precedence, suggest:

	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
			IFF_TAP : (IFF_TUN | IFF_POINTOPOINT);

> +	if (pmd->type == ETH_TUNTAP_TYPE_TAP)
> +		ifr.ifr_flags = IFF_TAP;
> +	else
> +		ifr.ifr_flags = IFF_TUN | IFF_POINTOPOINT;
> +
>  	strlcpy(ifr.ifr_name, pmd->name, IFNAMSIZ);
> 
>  	fd = open(TUN_TAP_DEV_PATH, O_RDWR);
> --
> 2.51.0


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

* Re: [PATCH 01/10] test: add unit tests for TAP PMD
  2026-02-15 19:52 ` [PATCH 01/10] test: add unit tests for TAP PMD Stephen Hemminger
@ 2026-02-16  0:46   ` Stephen Hemminger
  0 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16  0:46 UTC (permalink / raw)
  To: dev

On Sun, 15 Feb 2026 11:52:19 -0800
Stephen Hemminger <stephen@networkplumber.org> wrote:

> +++ b/app/test/test_pmd_tap.c
> @@ -0,0 +1,896 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2024 Intel Corporation
> + */

Should be Intel copyright ;-)

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

* Re: [PATCH 03/10] net/tap: clarify TUN/TAP flag assignment
  2026-02-15 21:45   ` Morten Brørup
@ 2026-02-16  0:47     ` Stephen Hemminger
  2026-02-16  5:56       ` Morten Brørup
  0 siblings, 1 reply; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16  0:47 UTC (permalink / raw)
  To: Morten Brørup; +Cc: dev

On Sun, 15 Feb 2026 22:45:53 +0100
Morten Brørup <mb@smartsharesystems.com> wrote:

> > 
> > Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> > ---
> >  drivers/net/tap/rte_eth_tap.c | 7 +++++--
> >  1 file changed, 5 insertions(+), 2 deletions(-)
> > 
> > diff --git a/drivers/net/tap/rte_eth_tap.c
> > b/drivers/net/tap/rte_eth_tap.c
> > index aa236cf967..31c8c185e9 100644
> > --- a/drivers/net/tap/rte_eth_tap.c
> > +++ b/drivers/net/tap/rte_eth_tap.c
> > @@ -154,8 +154,11 @@ tun_alloc(struct pmd_internals *pmd, int
> > is_keepalive, int persistent)
> >  	 * Do not set IFF_NO_PI as packet information header will be
> > needed
> >  	 * to check if a received packet has been truncated.
> >  	 */
> > -	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
> > -		IFF_TAP : IFF_TUN | IFF_POINTOPOINT;  
> 
> For unconditional assignment, I prefer ternary expression over if/else.
> If you want to improve readability instead of relying on operator precedence, suggest:
> 
> 	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
> 			IFF_TAP : (IFF_TUN | IFF_POINTOPOINT);


I was pre-conditioned by MS style guide lines that always called out
excessive use of ternary as confusing

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

* RE: [PATCH 03/10] net/tap: clarify TUN/TAP flag assignment
  2026-02-16  0:47     ` Stephen Hemminger
@ 2026-02-16  5:56       ` Morten Brørup
  0 siblings, 0 replies; 82+ messages in thread
From: Morten Brørup @ 2026-02-16  5:56 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev

> From: Stephen Hemminger [mailto:stephen@networkplumber.org]
> Sent: Monday, 16 February 2026 01.48
> 
> On Sun, 15 Feb 2026 22:45:53 +0100
> Morten Brørup <mb@smartsharesystems.com> wrote:
> 
> > >
> > > Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> > > ---
> > >  drivers/net/tap/rte_eth_tap.c | 7 +++++--
> > >  1 file changed, 5 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/drivers/net/tap/rte_eth_tap.c
> > > b/drivers/net/tap/rte_eth_tap.c
> > > index aa236cf967..31c8c185e9 100644
> > > --- a/drivers/net/tap/rte_eth_tap.c
> > > +++ b/drivers/net/tap/rte_eth_tap.c
> > > @@ -154,8 +154,11 @@ tun_alloc(struct pmd_internals *pmd, int
> > > is_keepalive, int persistent)
> > >  	 * Do not set IFF_NO_PI as packet information header will be
> > > needed
> > >  	 * to check if a received packet has been truncated.
> > >  	 */
> > > -	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
> > > -		IFF_TAP : IFF_TUN | IFF_POINTOPOINT;
> >
> > For unconditional assignment, I prefer ternary expression over
> if/else.
> > If you want to improve readability instead of relying on operator
> precedence, suggest:
> >
> > 	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
> > 			IFF_TAP : (IFF_TUN | IFF_POINTOPOINT);
> 
> 
> I was pre-conditioned by MS style guide lines that always called out
> excessive use of ternary as confusing

Just tested on Godbolt; GCC, Clang and MSVC emits similar code for ternary and if/else, so it's only a matter of preference.

Since it's an unconditional assignment, I prefer ternary.
Ternary also works when initializing a variable with its declaration. And with assigning field values to structures.

Well... it's your code, so I'll leave you with the decision. ;-)


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

* [PATCH v2 00/11] net/tap: test, cleanups and error path fixes
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (9 preceding siblings ...)
  2026-02-15 19:52 ` [PATCH 10/10] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
@ 2026-02-16 23:02 ` Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 01/11] test: add unit tests for TAP PMD Stephen Hemminger
                     ` (10 more replies)
  2026-02-17 15:04 ` [RFT 0/4] net/mlx5: fix several correctness bugs Stephen Hemminger
                   ` (3 subsequent siblings)
  14 siblings, 11 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a unit test suite for the TAP PMD, clean up minor code issues,
and fix several resource leaks and a use-after-free on error paths.

v2
  - fix typos in previous version
  - preserve same fixed MAC address as original

Stephen Hemminger (11):
  test: add unit tests for TAP PMD
  net/tap: replace runtime speed capability with constant
  net/tap: clarify TUN/TAP flag assignment
  net/tap: extend fixed MAC range to 16 bits
  net/tap: skip checksum on truncated L4 headers
  net/tap: fix resource leaks in tap create error path
  net/tap: fix resource leaks in secondary process probe
  net/tap: free IPC reply buffer on queue count mismatch
  net/tap: fix use-after-free on remote flow creation failure
  net/tap: free remote flow when implicit rule already exists
  net/tap: track device by ifindex instead of name

 app/test/meson.build          |   1 +
 app/test/test_pmd_tap.c       | 901 ++++++++++++++++++++++++++++++++++
 drivers/net/tap/rte_eth_tap.c | 291 +++++------
 drivers/net/tap/rte_eth_tap.h |   2 -
 drivers/net/tap/tap_flow.c    |  23 +-
 5 files changed, 1069 insertions(+), 149 deletions(-)
 create mode 100644 app/test/test_pmd_tap.c

-- 
2.51.0


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

* [PATCH v2 01/11] test: add unit tests for TAP PMD
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
@ 2026-02-16 23:02   ` Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 02/11] net/tap: replace runtime speed capability with constant Stephen Hemminger
                     ` (9 subsequent siblings)
  10 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a standalone test suite for the TAP PMD, modeled on the existing
test_pmd_ring tests. Exercises device configuration, link status,
stats, MTU, MAC address, promiscuous/allmulticast modes, queue
start/stop, link up/down, device stop/start, and multi-queue setup.

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

diff --git a/app/test/meson.build b/app/test/meson.build
index 48874037eb..b57fed424b 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -144,6 +144,7 @@ source_file_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'],
+    'test_pmd_tap.c': ['ethdev', 'net_tap', 'bus_vdev'],
     'test_pmu.c': ['pmu'],
     'test_power.c': ['power', 'power_acpi', 'power_kvm_vm', 'power_intel_pstate',
         'power_amd_pstate', 'power_cppc'],
diff --git a/app/test/test_pmd_tap.c b/app/test/test_pmd_tap.c
new file mode 100644
index 0000000000..6f27bc1092
--- /dev/null
+++ b/app/test/test_pmd_tap.c
@@ -0,0 +1,901 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+/*
+ * Basic test of TAP device functionality
+ * based off of PMD ring test.
+ */
+
+#include "test.h"
+
+#include <stdio.h>
+
+#ifndef RTE_EXEC_ENV_LINUX
+
+/* TAP PMD is only available on Linux */
+static int
+test_pmd_tap(void)
+{
+	printf("TAP PMD not supported on this platform, skipping test\n");
+	return TEST_SKIPPED;
+}
+
+#else /* RTE_EXEC_ENV_LINUX */
+
+#include <string.h>
+#include <unistd.h>
+
+#include <rte_ethdev.h>
+#include <rte_bus_vdev.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+
+#define SOCKET0 0
+#define RING_SIZE 256
+#define NB_MBUF 2048
+#define MAX_PKT_BURST 32
+#define PKT_LEN 64
+
+static struct rte_mempool *mp;
+static int tap_port0 = -1;
+static int tap_port1 = -1;
+
+static int
+test_tap_ethdev_configure(int port)
+{
+	struct rte_eth_conf port_conf;
+	struct rte_eth_link link;
+	int ret;
+
+	memset(&port_conf, 0, sizeof(struct rte_eth_conf));
+
+	ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+	if (ret < 0) {
+		printf("Configure failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_tx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL);
+	if (ret < 0) {
+		printf("TX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, mp);
+	if (ret < 0) {
+		printf("RX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0) {
+		printf("Error starting port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_link_get(port, &link);
+	if (ret < 0) {
+		printf("Link get failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d: link status %s, speed %u Mbps\n",
+	       port,
+	       link.link_status ? "up" : "down",
+	       link.link_speed);
+
+	return 0;
+}
+
+static struct rte_mbuf *
+create_test_packet(struct rte_mempool *pool, uint16_t pkt_len)
+{
+	struct rte_mbuf *mbuf;
+	struct rte_ether_hdr *eth_hdr;
+	struct rte_ipv4_hdr *ip_hdr;
+	uint8_t *payload;
+	uint16_t i;
+
+	mbuf = rte_pktmbuf_alloc(pool);
+	if (mbuf == NULL)
+		return NULL;
+
+	/* Ensure minimum packet size for Ethernet */
+	if (pkt_len < RTE_ETHER_MIN_LEN)
+		pkt_len = RTE_ETHER_MIN_LEN;
+
+	mbuf->data_len = pkt_len;
+	mbuf->pkt_len = pkt_len;
+
+	/* Create Ethernet header */
+	eth_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
+	memset(&eth_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN); /* broadcast */
+	memset(&eth_hdr->src_addr, 0x02, RTE_ETHER_ADDR_LEN);
+	eth_hdr->src_addr.addr_bytes[5] = 0x01;
+	eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+	/* Create simple IPv4 header */
+	ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
+	memset(ip_hdr, 0, sizeof(*ip_hdr));
+	ip_hdr->version_ihl = 0x45; /* IPv4, 20 byte header */
+	ip_hdr->total_length = rte_cpu_to_be_16(pkt_len - sizeof(*eth_hdr));
+	ip_hdr->time_to_live = 64;
+	ip_hdr->next_proto_id = IPPROTO_UDP;
+	ip_hdr->src_addr = rte_cpu_to_be_32(0x0A000001); /* 10.0.0.1 */
+	ip_hdr->dst_addr = rte_cpu_to_be_32(0x0A000002); /* 10.0.0.2 */
+
+	/* Fill payload with pattern */
+	payload = (uint8_t *)(ip_hdr + 1);
+	for (i = 0; i < pkt_len - sizeof(*eth_hdr) - sizeof(*ip_hdr); i++)
+		payload[i] = (uint8_t)(i & 0xFF);
+
+	return mbuf;
+}
+
+static int
+test_tap_send_receive(void)
+{
+	struct rte_mbuf *tx_mbufs[MAX_PKT_BURST];
+	struct rte_mbuf *rx_mbufs[MAX_PKT_BURST];
+	uint16_t nb_tx, nb_rx;
+	int i;
+
+	printf("Testing TAP packet send and receive\n");
+
+	/* Create test packets */
+	for (i = 0; i < MAX_PKT_BURST / 2; i++) {
+		tx_mbufs[i] = create_test_packet(mp, PKT_LEN);
+		if (tx_mbufs[i] == NULL) {
+			printf("Failed to create test packet %d\n", i);
+			/* Free already allocated packets */
+			while (--i >= 0)
+				rte_pktmbuf_free(tx_mbufs[i]);
+			return TEST_FAILED;
+		}
+	}
+
+	/* Send packets */
+	nb_tx = rte_eth_tx_burst(tap_port0, 0, tx_mbufs, MAX_PKT_BURST / 2);
+	printf("Transmitted %u packets on port %d\n", nb_tx, tap_port0);
+
+	/* Free any unsent packets */
+	for (i = nb_tx; i < MAX_PKT_BURST / 2; i++)
+		rte_pktmbuf_free(tx_mbufs[i]);
+
+	if (nb_tx == 0) {
+		printf("Warning: No packets transmitted (this may be expected if interface is not up)\n");
+		return TEST_SUCCESS;
+	}
+
+	/* Small delay to allow packets to be processed */
+	usleep(10000);
+
+	/* Try to receive packets (note: TAP loopback depends on kernel config) */
+	nb_rx = rte_eth_rx_burst(tap_port0, 0, rx_mbufs, MAX_PKT_BURST);
+	printf("Received %u packets on port %d\n", nb_rx, tap_port0);
+
+	/* Free received packets */
+	for (i = 0; i < nb_rx; i++)
+		rte_pktmbuf_free(rx_mbufs[i]);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_stats_get(int port)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	printf("Testing TAP PMD stats_get for port %d\n", port);
+
+	ret = rte_eth_stats_get(port, &stats);
+	if (ret != 0) {
+		printf("Error: failed to get stats for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d stats:\n", port);
+	printf("  ipackets: %"PRIu64"\n", stats.ipackets);
+	printf("  opackets: %"PRIu64"\n", stats.opackets);
+	printf("  ibytes:   %"PRIu64"\n", stats.ibytes);
+	printf("  obytes:   %"PRIu64"\n", stats.obytes);
+	printf("  ierrors:  %"PRIu64"\n", stats.ierrors);
+	printf("  oerrors:  %"PRIu64"\n", stats.oerrors);
+
+	return 0;
+}
+
+static int
+test_tap_stats_reset(int port)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	printf("Testing TAP PMD stats_reset for port %d\n", port);
+
+	ret = rte_eth_stats_reset(port);
+	if (ret != 0) {
+		printf("Error: failed to reset stats for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_stats_get(port, &stats);
+	if (ret != 0) {
+		printf("Error: failed to get stats after reset for port %d\n",
+		       port);
+		return -1;
+	}
+
+	/* After reset, all stats should be zero */
+	if (stats.ipackets != 0 || stats.opackets != 0 ||
+	    stats.ibytes != 0 || stats.obytes != 0 ||
+	    stats.ierrors != 0 || stats.oerrors != 0) {
+		printf("Error: port %d stats are not zero after reset\n", port);
+		return -1;
+	}
+
+	printf("Stats reset successful for port %d\n", port);
+	return 0;
+}
+
+static int
+test_tap_link_status(int port)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	printf("Testing TAP PMD link status for port %d\n", port);
+
+	ret = rte_eth_link_get_nowait(port, &link);
+	if (ret < 0) {
+		printf("Error: failed to get link status for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d link: status=%s speed=%u duplex=%s\n",
+	       port,
+	       link.link_status ? "up" : "down",
+	       link.link_speed,
+	       link.link_duplex ? "full" : "half");
+
+	return 0;
+}
+
+static int
+test_tap_dev_info(int port)
+{
+	struct rte_eth_dev_info dev_info;
+	int ret;
+
+	printf("Testing TAP PMD dev_info for port %d\n", port);
+
+	ret = rte_eth_dev_info_get(port, &dev_info);
+	if (ret != 0) {
+		printf("Error: failed to get dev info for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d device info:\n", port);
+	printf("  driver_name: %s\n", dev_info.driver_name);
+	printf("  if_index: %u\n", dev_info.if_index);
+	printf("  max_rx_queues: %u\n", dev_info.max_rx_queues);
+	printf("  max_tx_queues: %u\n", dev_info.max_tx_queues);
+	printf("  max_rx_pktlen: %u\n", dev_info.max_rx_pktlen);
+
+	/* Verify this is indeed a TAP device */
+	if (strcmp(dev_info.driver_name, "net_tap") != 0 &&
+	    strcmp(dev_info.driver_name, "net_tun") != 0) {
+		printf("Warning: unexpected driver name: %s\n",
+		       dev_info.driver_name);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_mtu(int port)
+{
+	uint16_t mtu;
+	int ret;
+
+	printf("Testing TAP PMD MTU operations for port %d\n", port);
+
+	/* Get current MTU */
+	ret = rte_eth_dev_get_mtu(port, &mtu);
+	if (ret != 0) {
+		printf("Error: failed to get MTU for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Current MTU for port %d: %u\n", port, mtu);
+
+	/* Try to set a new MTU */
+	ret = rte_eth_dev_set_mtu(port, 1400);
+	if (ret != 0) {
+		printf("Warning: failed to set MTU to 1400 for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		/* Not a fatal error - may require privileges */
+	} else {
+		printf("MTU set to 1400 for port %d\n", port);
+
+		/* Restore original MTU */
+		ret = rte_eth_dev_set_mtu(port, mtu);
+		if (ret != 0)
+			printf("Warning: failed to restore MTU for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_mac_addr(int port)
+{
+	struct rte_ether_addr mac_addr;
+	int ret;
+
+	printf("Testing TAP PMD MAC address for port %d\n", port);
+
+	ret = rte_eth_macaddr_get(port, &mac_addr);
+	if (ret != 0) {
+		printf("Error: failed to get MAC address for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d MAC address: " RTE_ETHER_ADDR_PRT_FMT "\n",
+	       port, RTE_ETHER_ADDR_BYTES(&mac_addr));
+
+	return 0;
+}
+
+static int
+test_tap_promiscuous(int port)
+{
+	int ret;
+	int promisc_enabled;
+
+	printf("Testing TAP PMD promiscuous mode for port %d\n", port);
+
+	/* Get current promiscuous state */
+	promisc_enabled = rte_eth_promiscuous_get(port);
+	printf("Promiscuous mode initially %s for port %d\n",
+	       promisc_enabled ? "enabled" : "disabled", port);
+
+	/* Enable promiscuous mode */
+	ret = rte_eth_promiscuous_enable(port);
+	if (ret != 0) {
+		printf("Warning: failed to enable promiscuous mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_promiscuous_get(port) != 1) {
+			printf("Error: promiscuous mode not enabled after enable call\n");
+			return -1;
+		}
+		printf("Promiscuous mode enabled for port %d\n", port);
+	}
+
+	/* Disable promiscuous mode */
+	ret = rte_eth_promiscuous_disable(port);
+	if (ret != 0) {
+		printf("Warning: failed to disable promiscuous mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_promiscuous_get(port) != 0) {
+			printf("Error: promiscuous mode not disabled after disable call\n");
+			return -1;
+		}
+		printf("Promiscuous mode disabled for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_allmulti(int port)
+{
+	int ret;
+	int allmulti_enabled;
+
+	printf("Testing TAP PMD allmulticast mode for port %d\n", port);
+
+	/* Get current allmulticast state */
+	allmulti_enabled = rte_eth_allmulticast_get(port);
+	printf("Allmulticast mode initially %s for port %d\n",
+	       allmulti_enabled ? "enabled" : "disabled", port);
+
+	/* Enable allmulticast mode */
+	ret = rte_eth_allmulticast_enable(port);
+	if (ret != 0) {
+		printf("Warning: failed to enable allmulticast mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_allmulticast_get(port) != 1) {
+			printf("Error: allmulticast mode not enabled after enable call\n");
+			return -1;
+		}
+		printf("Allmulticast mode enabled for port %d\n", port);
+	}
+
+	/* Disable allmulticast mode */
+	ret = rte_eth_allmulticast_disable(port);
+	if (ret != 0) {
+		printf("Warning: failed to disable allmulticast mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_allmulticast_get(port) != 0) {
+			printf("Error: allmulticast mode not disabled after disable call\n");
+			return -1;
+		}
+		printf("Allmulticast mode disabled for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_queue_start_stop(int port)
+{
+	int ret;
+
+	printf("Testing TAP PMD queue start/stop for port %d\n", port);
+
+	/* Stop RX queue */
+	ret = rte_eth_dev_rx_queue_stop(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to stop RX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("RX queue stopped for port %d\n", port);
+
+	/* Stop TX queue */
+	ret = rte_eth_dev_tx_queue_stop(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to stop TX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("TX queue stopped for port %d\n", port);
+
+	/* Start RX queue */
+	ret = rte_eth_dev_rx_queue_start(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to start RX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("RX queue started for port %d\n", port);
+
+	/* Start TX queue */
+	ret = rte_eth_dev_tx_queue_start(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to start TX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("TX queue started for port %d\n", port);
+
+	return 0;
+}
+
+static int
+test_tap_link_up_down(int port)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	printf("Testing TAP PMD link up/down for port %d\n", port);
+
+	/* Set link down */
+	ret = rte_eth_dev_set_link_down(port);
+	if (ret != 0) {
+		printf("Warning: failed to set link down for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		ret = rte_eth_link_get_nowait(port, &link);
+		if (ret == 0)
+			printf("Link status after set_link_down: %s\n",
+			       link.link_status ? "up" : "down");
+	}
+
+	/* Set link up */
+	ret = rte_eth_dev_set_link_up(port);
+	if (ret != 0) {
+		printf("Warning: failed to set link up for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		ret = rte_eth_link_get_nowait(port, &link);
+		if (ret == 0)
+			printf("Link status after set_link_up: %s\n",
+			       link.link_status ? "up" : "down");
+	}
+
+	return 0;
+}
+
+static int
+test_tap_dev_stop_start(int port)
+{
+	int ret;
+
+	printf("Testing TAP PMD device stop/start for port %d\n", port);
+
+	/* Stop the device */
+	ret = rte_eth_dev_stop(port);
+	if (ret != 0) {
+		printf("Error: failed to stop port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Device stopped for port %d\n", port);
+
+	/* Start the device again */
+	ret = rte_eth_dev_start(port);
+	if (ret != 0) {
+		printf("Error: failed to start port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Device started for port %d\n", port);
+
+	return 0;
+}
+
+static int
+test_tap_multi_queue(void)
+{
+	struct rte_eth_conf port_conf;
+	struct rte_mempool *mq_mp;
+	const uint16_t nb_queues = 2;
+	int ret;
+	int port = -1;
+
+	printf("Testing TAP PMD multi-queue configuration\n");
+
+	/* Create a separate mempool for multi-queue test */
+	mq_mp = rte_pktmbuf_pool_create("tap_mq_pool", NB_MBUF, 32, 0,
+					RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+	if (mq_mp == NULL) {
+		printf("Warning: failed to create mempool for multi-queue test: %s\n",
+		       rte_strerror(rte_errno));
+		return TEST_SKIPPED;
+	}
+
+	/* Create a new TAP device for multi-queue test */
+	ret = rte_vdev_init("net_tap_mq", "iface=dtap_mq");
+	if (ret < 0) {
+		printf("Warning: failed to create multi-queue TAP device: %s\n",
+		       rte_strerror(-ret));
+		rte_mempool_free(mq_mp);
+		return TEST_SKIPPED;
+	}
+
+	/* Find the port */
+	RTE_ETH_FOREACH_DEV(port) {
+		char name[RTE_ETH_NAME_MAX_LEN];
+		if (rte_eth_dev_get_name_by_port(port, name) == 0 &&
+		    strstr(name, "net_tap_mq"))
+			break;
+	}
+
+	if (port < 0 || port >= RTE_MAX_ETHPORTS) {
+		printf("Error: could not find multi-queue TAP device\n");
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_FAILED;
+	}
+
+	memset(&port_conf, 0, sizeof(struct rte_eth_conf));
+
+	/* Configure with multiple queues */
+	ret = rte_eth_dev_configure(port, nb_queues, nb_queues, &port_conf);
+	if (ret < 0) {
+		printf("Warning: multi-queue configure failed: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_SKIPPED;
+	}
+
+	/* Setup TX queues */
+	for (uint16_t q = 0; q < nb_queues; q++) {
+		ret = rte_eth_tx_queue_setup(port, q, RING_SIZE, SOCKET0, NULL);
+		if (ret < 0) {
+			printf("Error: TX queue %u setup failed: %s\n",
+			       q, rte_strerror(-ret));
+			rte_vdev_uninit("net_tap_mq");
+			rte_mempool_free(mq_mp);
+			return TEST_FAILED;
+		}
+	}
+
+	/* Setup RX queues */
+	for (uint16_t q = 0; q < nb_queues; q++) {
+		ret = rte_eth_rx_queue_setup(port, q, RING_SIZE, SOCKET0, NULL, mq_mp);
+		if (ret < 0) {
+			printf("Error: RX queue %u setup failed: %s\n",
+			       q, rte_strerror(-ret));
+			rte_vdev_uninit("net_tap_mq");
+			rte_mempool_free(mq_mp);
+			return TEST_FAILED;
+		}
+	}
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0) {
+		printf("Error: failed to start multi-queue port: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_FAILED;
+	}
+
+	printf("Multi-queue TAP device configured with %u queues\n", nb_queues);
+
+	/* Cleanup */
+	rte_eth_dev_stop(port);
+	rte_eth_dev_close(port);
+	rte_vdev_uninit("net_tap_mq");
+	rte_mempool_free(mq_mp);
+
+	return TEST_SUCCESS;
+}
+
+static void
+test_tap_cleanup(void)
+{
+	int ret;
+
+	printf("Cleaning up TAP PMD test resources\n");
+
+	if (tap_port0 >= 0) {
+		ret = rte_eth_dev_stop(tap_port0);
+		if (ret != 0)
+			printf("Warning: failed to stop port %d: %s\n",
+			       tap_port0, rte_strerror(-ret));
+		rte_eth_dev_close(tap_port0);
+	}
+
+	if (tap_port1 >= 0) {
+		ret = rte_eth_dev_stop(tap_port1);
+		if (ret != 0)
+			printf("Warning: failed to stop port %d: %s\n",
+			       tap_port1, rte_strerror(-ret));
+		rte_eth_dev_close(tap_port1);
+	}
+
+	rte_vdev_uninit("net_tap0");
+	rte_vdev_uninit("net_tap1");
+
+	if (mp != NULL) {
+		rte_mempool_free(mp);
+		mp = NULL;
+	}
+
+	tap_port0 = -1;
+	tap_port1 = -1;
+}
+
+static int
+test_tap_setup(void)
+{
+	int ret;
+	uint16_t port_id;
+
+	printf("Setting up TAP PMD test\n");
+
+	/* Create mempool */
+	mp = rte_pktmbuf_pool_create("tap_test_pool", NB_MBUF, 32, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+	if (mp == NULL) {
+		printf("Error: failed to create mempool: %s\n",
+		       rte_strerror(rte_errno));
+		return -1;
+	}
+
+	/* Create first TAP device */
+	ret = rte_vdev_init("net_tap0", "iface=dtap_test0");
+	if (ret < 0) {
+		printf("Error: failed to create TAP device net_tap0: %s\n",
+		       rte_strerror(-ret));
+		rte_mempool_free(mp);
+		mp = NULL;
+		return -1;
+	}
+
+	/* Create second TAP device */
+	ret = rte_vdev_init("net_tap1", "iface=dtap_test1");
+	if (ret < 0) {
+		printf("Error: failed to create TAP device net_tap1: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap0");
+		rte_mempool_free(mp);
+		mp = NULL;
+		return -1;
+	}
+
+	/* Find the port IDs */
+	RTE_ETH_FOREACH_DEV(port_id) {
+		char name[RTE_ETH_NAME_MAX_LEN];
+		if (rte_eth_dev_get_name_by_port(port_id, name) != 0)
+			continue;
+
+		if (strstr(name, "net_tap0"))
+			tap_port0 = port_id;
+		else if (strstr(name, "net_tap1"))
+			tap_port1 = port_id;
+	}
+
+	if (tap_port0 < 0 || tap_port1 < 0) {
+		printf("Error: failed to find TAP port IDs\n");
+		test_tap_cleanup();
+		return -1;
+	}
+
+	printf("Created TAP devices: tap_port0=%d, tap_port1=%d\n",
+	       tap_port0, tap_port1);
+
+	return 0;
+}
+
+/* Individual test case wrappers */
+
+static int
+test_tap_configure_port0(void)
+{
+	return test_tap_ethdev_configure(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_configure_port1(void)
+{
+	return test_tap_ethdev_configure(tap_port1) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_packet_send_receive(void)
+{
+	return test_tap_send_receive();
+}
+
+static int
+test_tap_get_stats(void)
+{
+	if (test_tap_stats_get(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_stats_get(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_reset_stats(void)
+{
+	if (test_tap_stats_reset(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_stats_reset(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_get_link_status(void)
+{
+	if (test_tap_link_status(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_link_status(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_get_dev_info(void)
+{
+	if (test_tap_dev_info(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_dev_info(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_mtu_ops(void)
+{
+	return test_tap_mtu(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_mac_addr_get(void)
+{
+	if (test_tap_mac_addr(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_mac_addr(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_promisc_mode(void)
+{
+	return test_tap_promiscuous(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_allmulti_mode(void)
+{
+	return test_tap_allmulti(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_queue_ops(void)
+{
+	return test_tap_queue_start_stop(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_link_ops(void)
+{
+	return test_tap_link_up_down(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_stop_start(void)
+{
+	return test_tap_dev_stop_start(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_multiqueue(void)
+{
+	return test_tap_multi_queue();
+}
+
+static struct unit_test_suite test_pmd_tap_suite = {
+	.setup = test_tap_setup,
+	.teardown = test_tap_cleanup,
+	.suite_name = "TAP PMD Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_tap_configure_port0),
+		TEST_CASE(test_tap_configure_port1),
+		TEST_CASE(test_tap_get_dev_info),
+		TEST_CASE(test_tap_get_link_status),
+		TEST_CASE(test_tap_mac_addr_get),
+		TEST_CASE(test_tap_get_stats),
+		TEST_CASE(test_tap_reset_stats),
+		TEST_CASE(test_tap_packet_send_receive),
+		TEST_CASE(test_tap_promisc_mode),
+		TEST_CASE(test_tap_allmulti_mode),
+		TEST_CASE(test_tap_mtu_ops),
+		TEST_CASE(test_tap_queue_ops),
+		TEST_CASE(test_tap_link_ops),
+		TEST_CASE(test_tap_stop_start),
+		TEST_CASE(test_tap_multiqueue),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_pmd_tap(void)
+{
+	return unit_test_suite_runner(&test_pmd_tap_suite);
+}
+
+#endif /* RTE_EXEC_ENV_LINUX */
+
+REGISTER_FAST_TEST(tap_pmd_autotest, NOHUGE_OK, ASAN_OK, test_pmd_tap);
-- 
2.51.0


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

* [PATCH v2 02/11] net/tap: replace runtime speed capability with constant
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 01/11] test: add unit tests for TAP PMD Stephen Hemminger
@ 2026-02-16 23:02   ` Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 03/11] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
                     ` (8 subsequent siblings)
  10 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The TAP/TUN virtual device always operates at 10G. The link speed is
set in the static initializer for pmd_link, so the runtime assignments
in rte_pmd_tap_probe() and rte_pmd_tun_probe() are redundant.

Replace tap_dev_speed_capa(), which dynamically computed speed
capabilities against pmd_link.link_speed, with a compile-time
constant TAP_SPEED_CAPA. Remove the unused 'speed' variable and
redundant assignments.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 7a8a98cddb..aa236cf967 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -891,39 +891,13 @@ tap_dev_configure(struct rte_eth_dev *dev)
 	return 0;
 }
 
-static uint32_t
-tap_dev_speed_capa(void)
-{
-	uint32_t speed = pmd_link.link_speed;
-	uint32_t capa = 0;
-
-	if (speed >= RTE_ETH_SPEED_NUM_10M)
-		capa |= RTE_ETH_LINK_SPEED_10M;
-	if (speed >= RTE_ETH_SPEED_NUM_100M)
-		capa |= RTE_ETH_LINK_SPEED_100M;
-	if (speed >= RTE_ETH_SPEED_NUM_1G)
-		capa |= RTE_ETH_LINK_SPEED_1G;
-	if (speed >= RTE_ETH_SPEED_NUM_5G)
-		capa |= RTE_ETH_LINK_SPEED_2_5G;
-	if (speed >= RTE_ETH_SPEED_NUM_5G)
-		capa |= RTE_ETH_LINK_SPEED_5G;
-	if (speed >= RTE_ETH_SPEED_NUM_10G)
-		capa |= RTE_ETH_LINK_SPEED_10G;
-	if (speed >= RTE_ETH_SPEED_NUM_20G)
-		capa |= RTE_ETH_LINK_SPEED_20G;
-	if (speed >= RTE_ETH_SPEED_NUM_25G)
-		capa |= RTE_ETH_LINK_SPEED_25G;
-	if (speed >= RTE_ETH_SPEED_NUM_40G)
-		capa |= RTE_ETH_LINK_SPEED_40G;
-	if (speed >= RTE_ETH_SPEED_NUM_50G)
-		capa |= RTE_ETH_LINK_SPEED_50G;
-	if (speed >= RTE_ETH_SPEED_NUM_56G)
-		capa |= RTE_ETH_LINK_SPEED_56G;
-	if (speed >= RTE_ETH_SPEED_NUM_100G)
-		capa |= RTE_ETH_LINK_SPEED_100G;
-
-	return capa;
-}
+/* Speed capabilities for virtual TAP/TUN device (always 10G) */
+#define TAP_SPEED_CAPA (RTE_ETH_LINK_SPEED_10M | \
+			RTE_ETH_LINK_SPEED_100M | \
+			RTE_ETH_LINK_SPEED_1G | \
+			RTE_ETH_LINK_SPEED_2_5G | \
+			RTE_ETH_LINK_SPEED_5G | \
+			RTE_ETH_LINK_SPEED_10G)
 
 static int
 tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
@@ -936,7 +910,7 @@ tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
 	dev_info->max_rx_queues = RTE_PMD_TAP_MAX_QUEUES;
 	dev_info->max_tx_queues = RTE_PMD_TAP_MAX_QUEUES;
 	dev_info->min_rx_bufsize = 0;
-	dev_info->speed_capa = tap_dev_speed_capa();
+	dev_info->speed_capa = TAP_SPEED_CAPA;
 	dev_info->rx_queue_offload_capa = TAP_RX_OFFLOAD;
 	dev_info->rx_offload_capa = dev_info->rx_queue_offload_capa;
 	dev_info->tx_queue_offload_capa = TAP_TX_OFFLOAD;
@@ -2325,7 +2299,6 @@ set_mac_type(const char *key __rte_unused,
  * Open a TUN interface device. TUN PMD
  * 1) sets tap_type as false
  * 2) intakes iface as argument.
- * 3) as interface is virtual set speed to 10G
  */
 static int
 rte_pmd_tun_probe(struct rte_vdev_device *dev)
@@ -2373,7 +2346,6 @@ rte_pmd_tun_probe(struct rte_vdev_device *dev)
 			}
 		}
 	}
-	pmd_link.link_speed = RTE_ETH_SPEED_NUM_10G;
 
 	TAP_LOG(DEBUG, "Initializing pmd_tun for %s", name);
 
@@ -2495,7 +2467,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 	const char *name, *params;
 	int ret;
 	struct rte_kvargs *kvlist = NULL;
-	int speed;
 	char tap_name[RTE_ETH_NAME_MAX_LEN];
 	char remote_iface[RTE_ETH_NAME_MAX_LEN];
 	struct rte_ether_addr user_mac = { .addr_bytes = {0} };
@@ -2545,8 +2516,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 		return 0;
 	}
 
-	speed = RTE_ETH_SPEED_NUM_10G;
-
 	/* use tap%d which causes kernel to choose next available */
 	strlcpy(tap_name, DEFAULT_TAP_NAME "%d", RTE_ETH_NAME_MAX_LEN);
 	memset(remote_iface, 0, RTE_ETH_NAME_MAX_LEN);
@@ -2587,7 +2556,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 				persist = 1;
 		}
 	}
-	pmd_link.link_speed = speed;
 
 	TAP_LOG(DEBUG, "Initializing pmd_tap for %s", name);
 
-- 
2.51.0


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

* [PATCH v2 03/11] net/tap: clarify TUN/TAP flag assignment
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 01/11] test: add unit tests for TAP PMD Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 02/11] net/tap: replace runtime speed capability with constant Stephen Hemminger
@ 2026-02-16 23:02   ` Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 04/11] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
                     ` (7 subsequent siblings)
  10 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add parentheses in the ternary expression that relied on operator
precedence between '?:' and '|'. No functional change.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index aa236cf967..e06e1ca079 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -155,7 +155,8 @@ tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
 	 * to check if a received packet has been truncated.
 	 */
 	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
-		IFF_TAP : IFF_TUN | IFF_POINTOPOINT;
+		IFF_TAP : (IFF_TUN | IFF_POINTOPOINT);
+
 	strlcpy(ifr.ifr_name, pmd->name, IFNAMSIZ);
 
 	fd = open(TUN_TAP_DEV_PATH, O_RDWR);
-- 
2.51.0


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

* [PATCH v2 04/11] net/tap: extend fixed MAC range to 16 bits
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
                     ` (2 preceding siblings ...)
  2026-02-16 23:02   ` [PATCH v2 03/11] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
@ 2026-02-16 23:02   ` Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 05/11] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
                     ` (6 subsequent siblings)
  10 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The generated fixed MAC address stored the interface index in a single
byte, which wraps after 256 devices and produces duplicate MACs under
repeated hot-plug. Spread the index across the last two bytes of the
address, extending the unique range to 65536.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index e06e1ca079..8b6d5db37e 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2274,13 +2274,15 @@ set_mac_type(const char *key __rte_unused,
 		return 0;
 
 	if (!strncasecmp(ETH_TAP_MAC_FIXED, value, strlen(ETH_TAP_MAC_FIXED))) {
-		static int iface_idx;
-
-		/* fixed mac = 02:64:74:61:70:<iface_idx> */
-		memcpy((char *)user_mac->addr_bytes, "\002dtap",
-			RTE_ETHER_ADDR_LEN);
-		user_mac->addr_bytes[RTE_ETHER_ADDR_LEN - 1] =
-			iface_idx++ + '0';
+		static uint16_t iface_idx;
+
+		/* fixed mac that is locally assigned based off of iface_idx. */
+		user_mac->addr_bytes[0] = RTE_ETHER_LOCAL_ADMIN_ADDR; /* 0x2 */
+		user_mac->addr_bytes[1] = 'd';
+		user_mac->addr_bytes[2] = 't';
+		user_mac->addr_bytes[3] = 'a';
+		user_mac->addr_bytes[4] = 'p' + (iface_idx >> 8);
+		user_mac->addr_bytes[5] = '0' + (uint8_t)iface_idx++;
 		goto success;
 	}
 
-- 
2.51.0


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

* [PATCH v2 05/11] net/tap: skip checksum on truncated L4 headers
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
                     ` (3 preceding siblings ...)
  2026-02-16 23:02   ` [PATCH v2 04/11] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
@ 2026-02-16 23:02   ` Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 06/11] net/tap: fix resource leaks in tap create error path Stephen Hemminger
                     ` (5 subsequent siblings)
  10 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a bounds check before accessing the UDP or TCP header in
tap_verify_csum(). A single-segment packet whose L4 header extends
past rte_pktmbuf_data_len() would cause an out-of-bounds read.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 8b6d5db37e..45ca32cfb8 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -365,8 +365,15 @@ tap_verify_csum(struct rte_mbuf *mbuf)
 		 */
 		return;
 	}
+
 	if (l4 == RTE_PTYPE_L4_UDP || l4 == RTE_PTYPE_L4_TCP) {
 		int cksum_ok;
+		const unsigned int l4_min_len = (l4 == RTE_PTYPE_L4_UDP)
+			? sizeof(struct rte_udp_hdr) : sizeof(struct rte_tcp_hdr);
+
+		/* Don't verify checksum if L4 header is truncated */
+		if (l2_len + l3_len + l4_min_len > rte_pktmbuf_data_len(mbuf))
+			return;
 
 		l4_hdr = rte_pktmbuf_mtod_offset(mbuf, void *, l2_len + l3_len);
 		/* Don't verify checksum for multi-segment packets. */
-- 
2.51.0


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

* [PATCH v2 06/11] net/tap: fix resource leaks in tap create error path
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
                     ` (4 preceding siblings ...)
  2026-02-16 23:02   ` [PATCH v2 05/11] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
@ 2026-02-16 23:02   ` Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 07/11] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
                     ` (4 subsequent siblings)
  10 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

Correct two leaks in eth_dev_tap_create():

- process_private was never freed on error_exit. Add free() before
  releasing the port.
- If the process_private malloc failed, the function returned -1
  directly without releasing the ethdev port allocated by
  rte_eth_vdev_allocate(). Jump to a new error_exit_nodev_release
  label instead.

Bugzilla ID: 1881
Fixes: ed8132e7c912 ("net/tap: move fds of queues to be in process private")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 45ca32cfb8..9b38d1f50b 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2010,7 +2010,7 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	process_private = malloc(sizeof(struct pmd_process_private));
 	if (process_private == NULL) {
 		TAP_LOG(ERR, "Failed to alloc memory for process private");
-		return -1;
+		goto error_exit_nodev_release;
 	}
 	memset(process_private, 0, sizeof(struct pmd_process_private));
 
@@ -2200,9 +2200,12 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 		close(pmd->nlsk_fd);
 	if (pmd->ka_fd != -1)
 		close(pmd->ka_fd);
+	rte_intr_instance_free(pmd->intr_handle);
+	free(dev->process_private);
+
+error_exit_nodev_release:
 	/* mac_addrs must not be freed alone because part of dev_private */
 	dev->data->mac_addrs = NULL;
-	rte_intr_instance_free(pmd->intr_handle);
 	rte_eth_dev_release_port(dev);
 
 error_exit_nodev:
-- 
2.51.0


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

* [PATCH v2 07/11] net/tap: fix resource leaks in secondary process probe
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
                     ` (5 preceding siblings ...)
  2026-02-16 23:02   ` [PATCH v2 06/11] net/tap: fix resource leaks in tap create error path Stephen Hemminger
@ 2026-02-16 23:02   ` Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 08/11] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
                     ` (3 subsequent siblings)
  10 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

Four error paths in the secondary-process branch of
rte_pmd_tap_probe() returned -1 without cleanup:

- primary process not alive: leaked eth_dev
- process_private malloc failure: leaked eth_dev
- tap_mp_attach_queues failure: leaked eth_dev and process_private
- rte_mp_action_register failure: leaked eth_dev and process_private

Add secondary_fail and secondary_fail_pp labels to free
process_private and release the ethdev port.

Bugzilla ID: 1881
Fixes: c9aa56edec8e ("net/tap: access primary process queues from secondary")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 9b38d1f50b..974b45ecad 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2502,31 +2502,38 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 		eth_dev->tx_pkt_burst = pmd_tx_burst;
 		if (!rte_eal_primary_proc_alive(NULL)) {
 			TAP_LOG(ERR, "Primary process is missing");
-			return -1;
+			goto secondary_fail;
 		}
 		eth_dev->process_private = malloc(sizeof(struct pmd_process_private));
 		if (eth_dev->process_private == NULL) {
 			TAP_LOG(ERR,
 				"Failed to alloc memory for process private");
-			return -1;
+			goto secondary_fail;
 		}
 		memset(eth_dev->process_private, 0, sizeof(struct pmd_process_private));
 
 		ret = tap_mp_attach_queues(name, eth_dev);
 		if (ret != 0)
-			return -1;
+			goto secondary_fail_pp;
 
 		if (!tap_devices_count) {
 			ret = rte_mp_action_register(TAP_MP_REQ_START_RXTX, tap_mp_req_start_rxtx);
 			if (ret < 0 && rte_errno != ENOTSUP) {
 				TAP_LOG(ERR, "tap: Failed to register IPC callback: %s",
 					strerror(rte_errno));
-				return -1;
+				goto secondary_fail_pp;
 			}
 		}
 		tap_devices_count++;
 		rte_eth_dev_probing_finish(eth_dev);
 		return 0;
+
+secondary_fail_pp:
+		free(eth_dev->process_private);
+		eth_dev->process_private = NULL;
+secondary_fail:
+		rte_eth_dev_release_port(eth_dev);
+		return -1;
 	}
 
 	/* use tap%d which causes kernel to choose next available */
-- 
2.51.0


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

* [PATCH v2 08/11] net/tap: free IPC reply buffer on queue count mismatch
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
                     ` (6 preceding siblings ...)
  2026-02-16 23:02   ` [PATCH v2 07/11] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
@ 2026-02-16 23:02   ` Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 09/11] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
                     ` (2 subsequent siblings)
  10 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

In tap_mp_attach_queues(), if the reply queue count does not match
the number of received file descriptors, the function returns -1
without freeing the reply buffer allocated by rte_mp_request_sync().
Add the missing free().

Bugzilla ID: 1881
Fixes: 9ad43ad8fbee ("net/tap: fix potential IPC buffer overrun")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 974b45ecad..deb1d72382 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2407,6 +2407,7 @@ tap_mp_attach_queues(const char *port_name, struct rte_eth_dev *dev)
 	/* Attach the queues from received file descriptors */
 	if (reply_param->q_count != reply->num_fds) {
 		TAP_LOG(ERR, "Unexpected number of fds received");
+		free(reply);
 		return -1;
 	}
 
-- 
2.51.0


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

* [PATCH v2 09/11] net/tap: fix use-after-free on remote flow creation failure
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
                     ` (7 preceding siblings ...)
  2026-02-16 23:02   ` [PATCH v2 08/11] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
@ 2026-02-16 23:02   ` Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 10/11] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 11/11] net/tap: track device by ifindex instead of name Stephen Hemminger
  10 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

After a local TC filter rule is installed and the flow is inserted
into pmd->flows, failure during remote flow creation jumps to the
fail label which frees the flow without removing it from the list
and without deleting the kernel-side TC rule.

Send RTM_DELTFILTER to clean up the local rule and call
LIST_REMOVE before freeing.

Bugzilla ID: 1881
Fixes: 2bc06869cd94 ("net/tap: add remote netdevice traffic capture")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 9d4ef27a8a..427faf75d5 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -1293,7 +1293,7 @@ tap_flow_create(struct rte_eth_dev *dev,
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
 				"cannot allocate memory for rte_flow");
-			goto fail;
+			goto fail_remove;
 		}
 		msg = &remote_flow->msg;
 		/* set the rule if_index for the remote netdevice */
@@ -1307,14 +1307,14 @@ tap_flow_create(struct rte_eth_dev *dev,
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "rte flow rule validation failed");
-			goto fail;
+			goto fail_remove;
 		}
 		err = tap_nl_send(pmd->nlsk_fd, &msg->nh);
 		if (err < 0) {
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "Failure sending nl request");
-			goto fail;
+			goto fail_remove;
 		}
 		err = tap_nl_recv_ack(pmd->nlsk_fd);
 		if (err < 0) {
@@ -1325,15 +1325,22 @@ tap_flow_create(struct rte_eth_dev *dev,
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL,
 				"overlapping rules or Kernel too old for flower support");
-			goto fail;
+			goto fail_remove;
 		}
 		flow->remote_flow = remote_flow;
 	}
 	return flow;
+
+fail_remove:
+	/* Delete the local TC rule that was already installed */
+	flow->msg.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	flow->msg.nh.nlmsg_type = RTM_DELTFILTER;
+	if (tap_nl_send(pmd->nlsk_fd, &flow->msg.nh) >= 0)
+		tap_nl_recv_ack(pmd->nlsk_fd);
+	LIST_REMOVE(flow, next);
 fail:
 	rte_free(remote_flow);
-	if (flow)
-		tap_flow_free(pmd, flow);
+	tap_flow_free(pmd, flow);
 	return NULL;
 }
 
-- 
2.51.0


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

* [PATCH v2 10/11] net/tap: free remote flow when implicit rule already exists
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
                     ` (8 preceding siblings ...)
  2026-02-16 23:02   ` [PATCH v2 09/11] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
@ 2026-02-16 23:02   ` Stephen Hemminger
  2026-02-16 23:02   ` [PATCH v2 11/11] net/tap: track device by ifindex instead of name Stephen Hemminger
  10 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

When tap_flow_implicit_create() gets EEXIST from the kernel, the
allocated remote_flow is never freed. Add rte_free() on that path.

Bugzilla ID: 1880
Fixes: 2ef1c0da894a ("net/tap: fix isolation mode toggling")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/tap_flow.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 427faf75d5..da1e70019a 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -1625,8 +1625,10 @@ int tap_flow_implicit_create(struct pmd_internals *pmd,
 	err = tap_nl_recv_ack(pmd->nlsk_fd);
 	if (err < 0) {
 		/* Silently ignore re-entering existing rule */
-		if (errno == EEXIST)
+		if (errno == EEXIST) {
+			rte_free(remote_flow);
 			goto success;
+		}
 		TAP_LOG(ERR,
 			"Kernel refused TC filter rule creation (%d): %s",
 			errno, strerror(errno));
-- 
2.51.0


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

* [PATCH v2 11/11] net/tap: track device by ifindex instead of name
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
                     ` (9 preceding siblings ...)
  2026-02-16 23:02   ` [PATCH v2 10/11] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
@ 2026-02-16 23:02   ` Stephen Hemminger
  2026-02-17  1:28     ` Stephen Hemminger
  10 siblings, 1 reply; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-16 23:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Remove name and remote_iface strings from pmd_internals.
The interface name can change at any time due to udev or admin
action, making cached names unreliable.

Resolve ifindex once at creation via TUNGETIFF on the keep-alive
fd. For per-queue opens, recover the name on demand with
if_indextoname(). In tap_netns_change(), use TUNGETIFF to get
the current kernel name before resolving the new ifindex,
fixing a bug where a rename during namespace move would lose
the device.

Log messages now use dev->device->name (DPDK vdev name) or
the ifindex, following the pattern established by the rtap PMD.

Bugzilla ID: 1880

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 194 +++++++++++++++++++---------------
 drivers/net/tap/rte_eth_tap.h |   2 -
 2 files changed, 108 insertions(+), 88 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index deb1d72382..f5712055ee 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -130,6 +130,9 @@ static int tap_intr_handle_set(struct rte_eth_dev *dev, int set);
  * @param[in] pmd
  *   Pointer to private structure.
  *
+ * @param[in] ifname
+ *   Name of the TUN/TAP interface to open or create.
+ *
  * @param[in] is_keepalive
  *   Keepalive flag
  *
@@ -140,7 +143,8 @@ static int tap_intr_handle_set(struct rte_eth_dev *dev, int set);
  *   -1 on failure, fd on success
  */
 static int
-tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
+tun_alloc(struct pmd_internals *pmd, const char *ifname,
+	  int is_keepalive, int persistent)
 {
 	struct ifreq ifr;
 #ifdef IFF_MULTI_QUEUE
@@ -157,7 +161,7 @@ tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
 	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
 		IFF_TAP : (IFF_TUN | IFF_POINTOPOINT);
 
-	strlcpy(ifr.ifr_name, pmd->name, IFNAMSIZ);
+	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
 
 	fd = open(TUN_TAP_DEV_PATH, O_RDWR);
 	if (fd < 0) {
@@ -199,12 +203,7 @@ tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
 		goto error;
 	}
 
-	/*
-	 * Name passed to kernel might be wildcard like dtun%d
-	 * and need to find the resulting device.
-	 */
-	TAP_LOG(DEBUG, "Device name is '%s'", ifr.ifr_name);
-	strlcpy(pmd->name, ifr.ifr_name, RTE_ETH_NAME_MAX_LEN);
+	TAP_LOG(DEBUG, "Opened TUN/TAP '%s'", ifr.ifr_name);
 
 	if (is_keepalive) {
 		/*
@@ -879,8 +878,6 @@ tap_dev_stop(struct rte_eth_dev *dev)
 static int
 tap_dev_configure(struct rte_eth_dev *dev)
 {
-	struct pmd_internals *pmd = dev->data->dev_private;
-
 	if (dev->data->nb_rx_queues != dev->data->nb_tx_queues) {
 		TAP_LOG(ERR,
 			"%s: number of rx queues %d must be equal to number of tx queues %d",
@@ -890,12 +887,6 @@ tap_dev_configure(struct rte_eth_dev *dev)
 		return -1;
 	}
 
-	TAP_LOG(INFO, "%s: %s: TX configured queues number: %u",
-		dev->device->name, pmd->name, dev->data->nb_tx_queues);
-
-	TAP_LOG(INFO, "%s: %s: RX configured queues number: %u",
-		dev->device->name, pmd->name, dev->data->nb_rx_queues);
-
 	return 0;
 }
 
@@ -1371,17 +1362,18 @@ tap_mac_set(struct rte_eth_dev *dev, struct rte_ether_addr *mac_addr)
 	return 0;
 }
 
-static int tap_carrier_set(struct pmd_internals *pmd, int carrier)
+static int tap_carrier_set(struct rte_eth_dev *dev, int carrier)
 {
+	struct pmd_internals *pmd = dev->data->dev_private;
 #ifdef TUNSETCARRIER
 	int ret = ioctl(pmd->ka_fd, TUNSETCARRIER, &carrier);
 	if (ret < 0) {
 		TAP_LOG(ERR, "%s: ioctl(TUNSETCARRIER) failed: %s",
-			pmd->name, strerror(errno));
+			dev->device->name, strerror(errno));
 		return ret;
 	}
 #else
-	(void)pmd;
+	(void)dev;
 	(void)carrier;
 #endif
 	return 0;
@@ -1407,9 +1399,9 @@ tap_gso_ctx_setup(struct rte_gso_ctx *gso_ctx, struct rte_eth_dev *dev)
 				dev->device->name);
 		if (ret < 0 || ret >= (int)sizeof(pool_name)) {
 			TAP_LOG(ERR,
-				"%s: failed to create mbuf pool name for device %s,"
+				"%s: failed to create mbuf pool name,"
 				"device name too long or output error, ret: %d",
-				pmd->name, dev->device->name, ret);
+				dev->device->name, ret);
 			return -ENAMETOOLONG;
 		}
 		pmd->gso_ctx_mp = rte_pktmbuf_pool_create(pool_name,
@@ -1418,8 +1410,8 @@ tap_gso_ctx_setup(struct rte_gso_ctx *gso_ctx, struct rte_eth_dev *dev)
 			SOCKET_ID_ANY);
 		if (!pmd->gso_ctx_mp) {
 			TAP_LOG(ERR,
-				"%s: failed to create mbuf pool for device %s",
-				pmd->name, dev->device->name);
+				"%s: failed to create mbuf pool",
+				dev->device->name);
 			return -1;
 		}
 	}
@@ -1450,18 +1442,27 @@ tap_setup_queue(struct rte_eth_dev *dev,
 	fd = process_private->fds[qid];
 	if (fd != -1) {
 		/* fd for this queue already exists */
-		TAP_LOG(DEBUG, "%s: fd %d for %s queue qid %d exists",
-			pmd->name, fd, dir, qid);
+		TAP_LOG(DEBUG, "%s: fd %d for %s queue %d exists",
+			dev->device->name, fd, dir, qid);
 		gso_ctx = NULL;
 	} else {
-		fd = tun_alloc(pmd, 0, 0);
+		char ifname[IFNAMSIZ];
+
+		if (if_indextoname(pmd->if_index, ifname) == NULL) {
+			TAP_LOG(ERR, "%s: ifindex %d not found",
+				dev->device->name, pmd->if_index);
+			return -1;
+		}
+
+		fd = tun_alloc(pmd, ifname, 0, 0);
 		if (fd < 0) {
-			TAP_LOG(ERR, "%s: tun_alloc() failed.", pmd->name);
+			TAP_LOG(ERR, "%s: tun_alloc() failed",
+				dev->device->name);
 			return -1;
 		}
 
-		TAP_LOG(DEBUG, "%s: add %s queue for qid %d fd %d",
-			pmd->name, dir, qid, fd);
+		TAP_LOG(DEBUG, "%s: add %s queue %d fd %d",
+			dev->device->name, dir, qid, fd);
 
 		process_private->fds[qid] = fd;
 	}
@@ -1555,12 +1556,12 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	/* set carrier after creating at least one rxq */
-	ret = tap_carrier_set(internals, 1);
+	ret = tap_carrier_set(dev, 1);
 	if (ret < 0)
 		goto error;
 
-	TAP_LOG(DEBUG, "  RX TUNTAP device name %s, qid %d on fd %d",
-		internals->name, rx_queue_id,
+	TAP_LOG(DEBUG, "  RX %s qid %d on fd %d",
+		dev->device->name, rx_queue_id,
 		process_private->fds[rx_queue_id]);
 
 	return 0;
@@ -1603,8 +1604,8 @@ tap_tx_queue_setup(struct rte_eth_dev *dev,
 	if (ret == -1)
 		return -1;
 	TAP_LOG(DEBUG,
-		"  TX TUNTAP device name %s, qid %d on fd %d csum %s",
-		internals->name, tx_queue_id,
+		"  TX %s qid %d on fd %d csum %s",
+		dev->device->name, tx_queue_id,
 		process_private->fds[tx_queue_id],
 		txq->csum ? "on" : "off");
 
@@ -1647,40 +1648,46 @@ tap_netns_change(struct rte_eth_dev *dev)
 {
 	struct pmd_internals *pmd = dev->data->dev_private;
 #ifdef TUNGETDEVNETNS
-	int netns_fd, orig_netns_fd, new_nlsk_fd;
+	struct ifreq ifr = { 0 };
+	int netns_fd, orig_netns_fd, new_nlsk_fd, new_ifindex;
 
 	netns_fd = ioctl(pmd->ka_fd, TUNGETDEVNETNS);
 	if (netns_fd < 0) {
-		TAP_LOG(INFO, "%s: interface deleted", pmd->name);
+		TAP_LOG(INFO, "ifindex %d: interface deleted",
+			pmd->if_index);
 		return 0;
 	}
 
-	/* Interface was moved to another namespace */
-	pmd->if_index = 0;
-
 	/* Save current namespace */
 	orig_netns_fd = open("/proc/self/ns/net", O_RDONLY);
 	if (orig_netns_fd < 0) {
-		TAP_LOG(ERR, "%s: failed to open original netns: %s",
-			pmd->name, strerror(errno));
+		TAP_LOG(ERR, "ifindex %d: failed to open original netns: %s",
+			pmd->if_index, strerror(errno));
 		close(netns_fd);
 		return -1;
 	}
 
 	/* Switch to new namespace */
 	if (setns(netns_fd, CLONE_NEWNET) < 0) {
-		TAP_LOG(ERR, "%s: failed to enter new netns: %s",
-			pmd->name, strerror(errno));
+		TAP_LOG(ERR, "ifindex %d: failed to enter new netns: %s",
+			pmd->if_index, strerror(errno));
 		close(netns_fd);
 		close(orig_netns_fd);
 		return -1;
 	}
 
 	/*
-	 * Update ifindex by querying interface name.
-	 * The interface now has a new ifindex in the new namespace.
+	 * Get the current name from the TUN fd and resolve to the new
+	 * ifindex. TUNGETIFF always returns the current kernel name
+	 * regardless of any renames.
 	 */
-	pmd->if_index = if_nametoindex(pmd->name);
+	if (ioctl(pmd->ka_fd, TUNGETIFF, &ifr) < 0) {
+		TAP_LOG(ERR, "ifindex %d: TUNGETIFF failed: %s",
+			pmd->if_index, strerror(errno));
+		new_ifindex = 0;
+	} else {
+		new_ifindex = if_nametoindex(ifr.ifr_name);
+	}
 
 	/* Recreate netlink socket in new namespace */
 	new_nlsk_fd = tap_nl_init(0);
@@ -1688,32 +1695,31 @@ tap_netns_change(struct rte_eth_dev *dev)
 	/* Recreate LSC interrupt netlink socket in new namespace */
 	rte_intr_callback_unregister_pending(pmd->intr_handle, tap_dev_intr_handler, dev, NULL);
 	if (tap_lsc_intr_handle_set(dev, 1) < 0)
-		TAP_LOG(WARNING, "%s: failed to recreate LSC interrupt socket",
-			pmd->name);
+		TAP_LOG(WARNING, "ifindex %d: failed to recreate LSC socket",
+			pmd->if_index);
 
 	/* Force carrier back after switching netns */
-	tap_carrier_set(pmd, 1);
+	tap_carrier_set(dev, 1);
 
 	/* Switch back to original namespace */
 	if (setns(orig_netns_fd, CLONE_NEWNET) < 0)
-		TAP_LOG(ERR, "%s: failed to return to original netns: %s",
-			pmd->name, strerror(errno));
+		TAP_LOG(ERR, "ifindex %d: failed to return to original netns: %s",
+			pmd->if_index, strerror(errno));
 
 	close(orig_netns_fd);
 	close(netns_fd);
 
-	if (pmd->if_index == 0) {
-		TAP_LOG(WARNING, "%s: interface moved to another namespace, "
-			"failed to get new ifindex",
-			pmd->name);
+	if (new_ifindex == 0) {
+		TAP_LOG(WARNING, "ifindex %d: moved to new namespace, "
+			"failed to get new ifindex", pmd->if_index);
 		if (new_nlsk_fd >= 0)
 			close(new_nlsk_fd);
 		return -1;
 	}
 
 	if (new_nlsk_fd < 0) {
-		TAP_LOG(WARNING, "%s: failed to recreate netlink socket in new namespace",
-			pmd->name);
+		TAP_LOG(WARNING, "ifindex %d: failed to recreate netlink socket",
+			pmd->if_index);
 		return -1;
 	}
 
@@ -1721,12 +1727,13 @@ tap_netns_change(struct rte_eth_dev *dev)
 	if (pmd->nlsk_fd >= 0)
 		tap_nl_final(pmd->nlsk_fd);
 	pmd->nlsk_fd = new_nlsk_fd;
+	pmd->if_index = new_ifindex;
 
-	TAP_LOG(INFO, "%s: interface moved to another namespace, new ifindex: %u",
-		pmd->name, pmd->if_index);
+	TAP_LOG(INFO, "interface moved to new namespace, new ifindex: %u",
+		pmd->if_index);
 #else
-	TAP_LOG(WARNING, "%s: interface deleted or moved to another namespace",
-		pmd->name);
+	TAP_LOG(WARNING, "ifindex %d: interface deleted or moved",
+		pmd->if_index);
 #endif
 
 	return 0;
@@ -2017,7 +2024,6 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	pmd = dev->data->dev_private;
 	dev->process_private = process_private;
 	pmd->dev = dev;
-	strlcpy(pmd->name, tap_name, sizeof(pmd->name));
 	pmd->type = type;
 	pmd->ka_fd = -1;
 	pmd->nlsk_fd = -1;
@@ -2069,12 +2075,34 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	 * This keep-alive file descriptor will guarantee that the TUN device
 	 * exists even when all of its queues are closed
 	 */
-	pmd->ka_fd = tun_alloc(pmd, 1, persist);
+	pmd->ka_fd = tun_alloc(pmd, tap_name, 1, persist);
 	if (pmd->ka_fd == -1) {
 		TAP_LOG(ERR, "Unable to create %s interface", tuntap_name);
 		goto error_exit;
 	}
-	TAP_LOG(DEBUG, "allocated %s", pmd->name);
+
+	/*
+	 * Get the kernel-assigned name from the TUN fd and resolve the
+	 * ifindex. From this point on, the device is tracked by ifindex
+	 * which is stable even if the interface is renamed.
+	 */
+	{
+		struct ifreq ifr = { 0 };
+
+		if (ioctl(pmd->ka_fd, TUNGETIFF, &ifr) < 0) {
+			TAP_LOG(ERR, "Unable to get interface name: %s",
+				strerror(errno));
+			goto error_exit;
+		}
+		pmd->if_index = if_nametoindex(ifr.ifr_name);
+		if (pmd->if_index == 0) {
+			TAP_LOG(ERR, "Unable to get ifindex for '%s'",
+				ifr.ifr_name);
+			goto error_exit;
+		}
+		TAP_LOG(DEBUG, "Created '%s' ifindex %d",
+			ifr.ifr_name, pmd->if_index);
+	}
 
 	/*
 	 * Create netlink socket for interface control.
@@ -2082,13 +2110,8 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	 */
 	pmd->nlsk_fd = tap_nl_init(0);
 	if (pmd->nlsk_fd == -1) {
-		TAP_LOG(ERR, "%s: failed to create netlink socket.", pmd->name);
-		goto error_exit;
-	}
-
-	pmd->if_index = if_nametoindex(pmd->name);
-	if (!pmd->if_index) {
-		TAP_LOG(ERR, "%s: failed to get if_index.", pmd->name);
+		TAP_LOG(ERR, "ifindex %d: failed to create netlink socket",
+			pmd->if_index);
 		goto error_exit;
 	}
 
@@ -2111,13 +2134,13 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	 * - implicit rules
 	 */
 	if (qdisc_create_multiq(pmd->nlsk_fd, pmd->if_index) < 0) {
-		TAP_LOG(ERR, "%s: failed to create multiq qdisc.",
-			pmd->name);
+		TAP_LOG(ERR, "ifindex %d: failed to create multiq qdisc",
+			pmd->if_index);
 		goto disable_rte_flow;
 	}
 	if (qdisc_create_ingress(pmd->nlsk_fd, pmd->if_index) < 0) {
-		TAP_LOG(ERR, "%s: failed to create ingress qdisc.",
-			pmd->name);
+		TAP_LOG(ERR, "ifindex %d: failed to create ingress qdisc",
+			pmd->if_index);
 		goto disable_rte_flow;
 	}
 
@@ -2126,11 +2149,10 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	if (strlen(remote_iface)) {
 		pmd->remote_if_index = if_nametoindex(remote_iface);
 		if (!pmd->remote_if_index) {
-			TAP_LOG(ERR, "%s: failed to get %s if_index.",
-				pmd->name, remote_iface);
+			TAP_LOG(ERR, "ifindex %d: failed to get ifindex for remote '%s'",
+				pmd->if_index, remote_iface);
 			goto error_remote;
 		}
-		strlcpy(pmd->remote_iface, remote_iface, RTE_ETH_NAME_MAX_LEN);
 
 		/* Save state of remote device */
 		if (tap_nl_get_flags(pmd->nlsk_fd, pmd->remote_if_index,
@@ -2139,14 +2161,14 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 
 		/* Replicate remote MAC address */
 		if (tap_nl_get_mac(pmd->nlsk_fd, pmd->remote_if_index, &pmd->eth_addr) < 0) {
-			TAP_LOG(ERR, "%s: failed to get %s MAC address.",
-				pmd->name, pmd->remote_iface);
+			TAP_LOG(ERR, "ifindex %d: failed to get remote MAC",
+				pmd->if_index);
 			goto error_remote;
 		}
 
 		if (tap_nl_set_mac(pmd->nlsk_fd, pmd->if_index, &pmd->eth_addr) < 0) {
-			TAP_LOG(ERR, "%s: failed to set %s MAC address.",
-				pmd->name, remote_iface);
+			TAP_LOG(ERR, "ifindex %d: failed to set local MAC",
+				pmd->if_index);
 			goto error_remote;
 		}
 
@@ -2158,8 +2180,8 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 		qdisc_flush(pmd->nlsk_fd, pmd->remote_if_index);
 		if (qdisc_create_ingress(pmd->nlsk_fd,
 					 pmd->remote_if_index) < 0) {
-			TAP_LOG(ERR, "%s: failed to create ingress qdisc.",
-				pmd->remote_iface);
+			TAP_LOG(ERR, "remote ifindex %d: failed to create ingress qdisc",
+				pmd->remote_if_index);
 			goto error_remote;
 		}
 		LIST_INIT(&pmd->implicit_flows);
@@ -2168,8 +2190,8 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 		    tap_flow_implicit_create(pmd, TAP_REMOTE_BROADCAST) < 0 ||
 		    tap_flow_implicit_create(pmd, TAP_REMOTE_BROADCASTV6) < 0) {
 			TAP_LOG(ERR,
-				"%s: failed to create implicit rules.",
-				pmd->name);
+				"ifindex %d: failed to create implicit rules",
+				pmd->if_index);
 			goto error_remote;
 		}
 	}
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index 218ee1b811..74ad0b9253 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -68,8 +68,6 @@ struct tx_queue {
 
 struct pmd_internals {
 	struct rte_eth_dev *dev;          /* Ethernet device. */
-	char remote_iface[RTE_ETH_NAME_MAX_LEN]; /* Remote netdevice name */
-	char name[RTE_ETH_NAME_MAX_LEN];  /* Internal Tap device name */
 	int type;                         /* Type field - TUN|TAP */
 	int persist;			  /* 1 if keep link up, else 0 */
 	struct rte_ether_addr eth_addr;   /* Mac address of the device port */
-- 
2.51.0


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

* Re: [PATCH v2 11/11] net/tap: track device by ifindex instead of name
  2026-02-16 23:02   ` [PATCH v2 11/11] net/tap: track device by ifindex instead of name Stephen Hemminger
@ 2026-02-17  1:28     ` Stephen Hemminger
  0 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-17  1:28 UTC (permalink / raw)
  To: dev

On Mon, 16 Feb 2026 15:02:35 -0800
Stephen Hemminger <stephen@networkplumber.org> wrote:

> Remove name and remote_iface strings from pmd_internals.
> The interface name can change at any time due to udev or admin
> action, making cached names unreliable.
> 
> Resolve ifindex once at creation via TUNGETIFF on the keep-alive
> fd. For per-queue opens, recover the name on demand with
> if_indextoname(). In tap_netns_change(), use TUNGETIFF to get
> the current kernel name before resolving the new ifindex,
> fixing a bug where a rename during namespace move would lose
> the device.
> 
> Log messages now use dev->device->name (DPDK vdev name) or
> the ifindex, following the pattern established by the rtap PMD.
> 
> Bugzilla ID: 1880
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---

This patch is not ready and needs to be dropped

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

* [RFT 0/4] net/mlx5: fix several correctness bugs
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (10 preceding siblings ...)
  2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
@ 2026-02-17 15:04 ` Stephen Hemminger
  2026-02-17 15:04   ` [RFT 1/4] net/mlx5: fix NULL dereference in Tx queue start Stephen Hemminger
                     ` (4 more replies)
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
                   ` (2 subsequent siblings)
  14 siblings, 5 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-17 15:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Found these during a code review of the mlx5 driver.  I don't have
ConnectX hardware to test, so sending as RFC for maintainers to
verify and test.

Summary:

  1/4  NULL pointer dereference in mlx5_txq_start() - txq_ctrl is
       dereferenced before the NULL check one line later.

  2/4  DevX queue counter leak in hairpin counter setup - the
       counter object is not freed when the subsequent modify call
       fails.

  3/4  Use-after-free in ASO age and CT management init - on queue
       init failure the management structure is freed but the
       pointer is not NULLed, so a retry dereferences freed memory.

  4/4  64-bit counter truncation - uint64_t* cast to uint32_t*
       leaves the upper 32 bits uninitialised, producing wrong
       hairpin queue statistics.

Stephen Hemminger (4):
  net/mlx5: fix NULL dereference in Tx queue start
  net/mlx5: fix counter leak in hairpin queue setup
  net/mlx5: fix use-after-free in ASO management init
  net/mlx5: fix counter truncation in queue counter read

 drivers/net/mlx5/mlx5.c         | 13 ++++++++++++-
 drivers/net/mlx5/mlx5_trigger.c |  3 ++-
 2 files changed, 14 insertions(+), 2 deletions(-)

-- 
2.51.0


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

* [RFT 1/4] net/mlx5: fix NULL dereference in Tx queue start
  2026-02-17 15:04 ` [RFT 0/4] net/mlx5: fix several correctness bugs Stephen Hemminger
@ 2026-02-17 15:04   ` Stephen Hemminger
  2026-02-26  9:55     ` Dariusz Sosnowski
  2026-02-17 15:05   ` [RFT 2/4] net/mlx5: fix counter leak in hairpin queue setup Stephen Hemminger
                     ` (3 subsequent siblings)
  4 siblings, 1 reply; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-17 15:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

mlx5_txq_get() can return NULL for an unconfigured queue index,
but the result is dereferenced to initialise txq_data before the
NULL check on the following line.  Move the txq_data assignment
after the NULL guard.

Fixes: 6f356d3840e6 ("net/mlx5: pass DevX object info in Tx queue start")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/mlx5/mlx5_trigger.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/mlx5/mlx5_trigger.c b/drivers/net/mlx5/mlx5_trigger.c
index f155b1a7a0..a070aaecfd 100644
--- a/drivers/net/mlx5/mlx5_trigger.c
+++ b/drivers/net/mlx5/mlx5_trigger.c
@@ -59,10 +59,11 @@ mlx5_txq_start(struct rte_eth_dev *dev)
 	for (cnt = log_max_wqe; cnt > 0; cnt -= 1) {
 		for (i = 0; i != priv->txqs_n; ++i) {
 			struct mlx5_txq_ctrl *txq_ctrl = mlx5_txq_get(dev, i);
-			struct mlx5_txq_data *txq_data = &txq_ctrl->txq;
+			struct mlx5_txq_data *txq_data;
 
 			if (!txq_ctrl)
 				continue;
+			txq_data = &txq_ctrl->txq;
 			if (txq_data->elts_n != cnt) {
 				mlx5_txq_release(dev, i);
 				continue;
-- 
2.51.0


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

* [RFT 2/4] net/mlx5: fix counter leak in hairpin queue setup
  2026-02-17 15:04 ` [RFT 0/4] net/mlx5: fix several correctness bugs Stephen Hemminger
  2026-02-17 15:04   ` [RFT 1/4] net/mlx5: fix NULL dereference in Tx queue start Stephen Hemminger
@ 2026-02-17 15:05   ` Stephen Hemminger
  2026-02-26  9:57     ` Dariusz Sosnowski
  2026-02-17 15:05   ` [RFT 3/4] net/mlx5: fix use-after-free in ASO management init Stephen Hemminger
                     ` (2 subsequent siblings)
  4 siblings, 1 reply; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-17 15:05 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

When rxq_obj_modify_counter_set_id() fails in
mlx5_enable_per_queue_hairpin_counter(), the freshly allocated DevX
queue counter object is not destroyed before returning.  Destroy it
on the error path.

Fixes: f0c0731b6d40 ("net/mlx5: add counters for hairpin drop")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/mlx5/mlx5.c b/drivers/net/mlx5/mlx5.c
index 4d3bfddc36..d533ce41e1 100644
--- a/drivers/net/mlx5/mlx5.c
+++ b/drivers/net/mlx5/mlx5.c
@@ -3704,6 +3704,8 @@ mlx5_enable_per_queue_hairpin_counter(struct rte_eth_dev *dev, uint64_t id)
 	if (ret) {
 		DRV_LOG(ERR, "failed to modify rq object for port %u"
 			"%s", priv->dev_data->port_id, strerror(rte_errno));
+		mlx5_devx_cmd_destroy(rxq->q_counter);
+		rxq->q_counter = NULL;
 		return ret;
 	}
 
-- 
2.51.0


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

* [RFT 3/4] net/mlx5: fix use-after-free in ASO management init
  2026-02-17 15:04 ` [RFT 0/4] net/mlx5: fix several correctness bugs Stephen Hemminger
  2026-02-17 15:04   ` [RFT 1/4] net/mlx5: fix NULL dereference in Tx queue start Stephen Hemminger
  2026-02-17 15:05   ` [RFT 2/4] net/mlx5: fix counter leak in hairpin queue setup Stephen Hemminger
@ 2026-02-17 15:05   ` Stephen Hemminger
  2026-02-26  9:57     ` Dariusz Sosnowski
  2026-02-17 15:05   ` [RFT 4/4] net/mlx5: fix counter truncation in queue counter read Stephen Hemminger
  2026-03-01 10:33   ` [RFT 0/4] net/mlx5: fix several correctness bugs Raslan Darawsheh
  4 siblings, 1 reply; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-17 15:05 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

mlx5_flow_aso_age_mng_init() and mlx5_flow_aso_ct_mng_init() each
allocate a management structure, then call mlx5_aso_queue_init().
If the queue init fails, the structure is freed but the pointer in
the shared context (sh->aso_age_mng / sh->ct_mng) is not set to
NULL.

A subsequent call to the same init function sees the non-NULL
pointer, skips re-allocation, and returns success, leaving the
caller operating on freed memory.

Set the pointer to NULL after freeing in both error paths.

Fixes: f935ed4b645a ("net/mlx5: support flow hit action for aging")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/mlx5/mlx5.c b/drivers/net/mlx5/mlx5.c
index d533ce41e1..71383f2ac7 100644
--- a/drivers/net/mlx5/mlx5.c
+++ b/drivers/net/mlx5/mlx5.c
@@ -459,6 +459,7 @@ mlx5_flow_aso_age_mng_init(struct mlx5_dev_ctx_shared *sh)
 	err = mlx5_aso_queue_init(sh, ASO_OPC_MOD_FLOW_HIT, 1);
 	if (err) {
 		mlx5_free(sh->aso_age_mng);
+		sh->aso_age_mng = NULL;
 		return -1;
 	}
 	rte_rwlock_init(&sh->aso_age_mng->resize_rwl);
@@ -823,6 +824,7 @@ mlx5_flow_aso_ct_mng_init(struct mlx5_dev_ctx_shared *sh)
 	err = mlx5_aso_queue_init(sh, ASO_OPC_MOD_CONNECTION_TRACKING, MLX5_ASO_CT_SQ_NUM);
 	if (err) {
 		mlx5_free(sh->ct_mng);
+		sh->ct_mng = NULL;
 		/* rte_errno should be extracted from the failure. */
 		rte_errno = EINVAL;
 		return -rte_errno;
-- 
2.51.0


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

* [RFT 4/4] net/mlx5: fix counter truncation in queue counter read
  2026-02-17 15:04 ` [RFT 0/4] net/mlx5: fix several correctness bugs Stephen Hemminger
                     ` (2 preceding siblings ...)
  2026-02-17 15:05   ` [RFT 3/4] net/mlx5: fix use-after-free in ASO management init Stephen Hemminger
@ 2026-02-17 15:05   ` Stephen Hemminger
  2026-02-26  9:58     ` Dariusz Sosnowski
  2026-03-01 10:33   ` [RFT 0/4] net/mlx5: fix several correctness bugs Raslan Darawsheh
  4 siblings, 1 reply; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-17 15:05 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

mlx5_read_queue_counter() casts its uint64_t *stat argument to
uint32_t * before passing it to mlx5_devx_cmd_queue_counter_query().
The query function writes a single uint32_t, so:

  - On little-endian (x86): only the low 32 bits of *stat are
    written; the upper 32 bits retain whatever value they had
    before the call (stack garbage or a stale value).
  - On big-endian (PowerPC): the write hits the wrong half of the
    64-bit word entirely.

Use a local uint32_t for the query and widen to uint64_t on
assignment.

Fixes: f0c0731b6d40 ("net/mlx5: add counters for hairpin drop")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/mlx5/mlx5.c b/drivers/net/mlx5/mlx5.c
index 71383f2ac7..4e0bc26754 100644
--- a/drivers/net/mlx5/mlx5.c
+++ b/drivers/net/mlx5/mlx5.c
@@ -3783,6 +3783,9 @@ int
 mlx5_read_queue_counter(struct mlx5_devx_obj *q_counter, const char *ctr_name,
 		      uint64_t *stat)
 {
+	uint32_t val;
+	int ret;
+
 	if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
 		DRV_LOG(WARNING,
 			"DevX %s counter is not supported in the secondary process", ctr_name);
@@ -3792,7 +3795,11 @@ mlx5_read_queue_counter(struct mlx5_devx_obj *q_counter, const char *ctr_name,
 	if (q_counter == NULL)
 		return -EINVAL;
 
-	return mlx5_devx_cmd_queue_counter_query(q_counter, 0, (uint32_t *)stat);
+	ret = mlx5_devx_cmd_queue_counter_query(q_counter, 0, &val);
+	if (ret == 0)
+		*stat = val;
+
+	return ret;
 }
 
 /**
-- 
2.51.0


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

* [PATCH v3 00/10] net/tap: bug fixes and add test
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (11 preceding siblings ...)
  2026-02-17 15:04 ` [RFT 0/4] net/mlx5: fix several correctness bugs Stephen Hemminger
@ 2026-02-20  5:02 ` Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 01/10] test: add unit tests for TAP PMD Stephen Hemminger
                     ` (9 more replies)
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
  14 siblings, 10 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20  5:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a unit test suite for the TAP PMD, clean up minor code issues,
and fix several resource leaks and a use-after-free on error paths.

v3
  - drop not ready full ifindex change
  - fix AI review feedback in test code

v2
  - fix typos in previous version
  - preserve same fixed MAC address as original

Stephen Hemminger (10):
  test: add unit tests for TAP PMD
  net/tap: replace runtime speed capability with constant
  net/tap: clarify TUN/TAP flag assignment
  net/tap: extend fixed MAC range to 16 bits
  net/tap: skip checksum on truncated L4 headers
  net/tap: fix resource leaks in tap create error path
  net/tap: fix resource leaks in secondary process probe
  net/tap: free IPC reply buffer on queue count mismatch
  net/tap: fix use-after-free on remote flow creation failure
  net/tap: free remote flow when implicit rule already exists

 app/test/meson.build          |   1 +
 app/test/test_pmd_tap.c       | 906 ++++++++++++++++++++++++++++++++++
 drivers/net/tap/rte_eth_tap.c |  97 ++--
 drivers/net/tap/tap_flow.c    |  23 +-
 4 files changed, 966 insertions(+), 61 deletions(-)
 create mode 100644 app/test/test_pmd_tap.c

-- 
2.51.0


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

* [PATCH v3 01/10] test: add unit tests for TAP PMD
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
@ 2026-02-20  5:02   ` Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 02/10] net/tap: replace runtime speed capability with constant Stephen Hemminger
                     ` (8 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20  5:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a standalone test suite for the TAP PMD, modeled on the existing
test_pmd_ring tests. Exercises device configuration, link status,
stats, MTU, MAC address, promiscuous/allmulticast modes, queue
start/stop, link up/down, device stop/start, and multi-queue setup.

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

diff --git a/app/test/meson.build b/app/test/meson.build
index 4fd8670e05..2689a23553 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -144,6 +144,7 @@ source_file_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'],
+    'test_pmd_tap.c': ['ethdev', 'net_tap', 'bus_vdev'],
     'test_pmu.c': ['pmu'],
     'test_power.c': ['power', 'power_acpi', 'power_kvm_vm', 'power_intel_pstate',
         'power_amd_pstate', 'power_cppc'],
diff --git a/app/test/test_pmd_tap.c b/app/test/test_pmd_tap.c
new file mode 100644
index 0000000000..325f19f677
--- /dev/null
+++ b/app/test/test_pmd_tap.c
@@ -0,0 +1,906 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+/*
+ * Basic test of TAP device functionality
+ * based off of PMD ring test.
+ */
+
+#include "test.h"
+
+#include <stdio.h>
+
+#ifndef RTE_EXEC_ENV_LINUX
+
+/* TAP PMD is only available on Linux */
+static int
+test_pmd_tap(void)
+{
+	printf("TAP PMD not supported on this platform, skipping test\n");
+	return TEST_SKIPPED;
+}
+
+#else /* RTE_EXEC_ENV_LINUX */
+
+#include <string.h>
+#include <unistd.h>
+
+#include <rte_ethdev.h>
+#include <rte_bus_vdev.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+
+#define SOCKET0 0
+#define RING_SIZE 256
+#define NB_MBUF 2048
+#define MAX_PKT_BURST 32
+#define PKT_LEN 64
+
+static struct rte_mempool *mp;
+static int tap_port0 = -1;
+static int tap_port1 = -1;
+
+static int
+test_tap_ethdev_configure(int port)
+{
+	struct rte_eth_conf port_conf;
+	struct rte_eth_link link;
+	int ret;
+
+	memset(&port_conf, 0, sizeof(struct rte_eth_conf));
+
+	ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+	if (ret < 0) {
+		printf("Configure failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_tx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL);
+	if (ret < 0) {
+		printf("TX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, mp);
+	if (ret < 0) {
+		printf("RX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0) {
+		printf("Error starting port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_link_get(port, &link);
+	if (ret < 0) {
+		printf("Link get failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d: link status %s, speed %u Mbps\n",
+	       port,
+	       link.link_status ? "up" : "down",
+	       link.link_speed);
+
+	return 0;
+}
+
+static struct rte_mbuf *
+create_test_packet(struct rte_mempool *pool, uint16_t pkt_len)
+{
+	struct rte_mbuf *mbuf;
+	struct rte_ether_hdr *eth_hdr;
+	struct rte_ipv4_hdr *ip_hdr;
+	uint8_t *payload;
+	uint16_t i;
+
+	mbuf = rte_pktmbuf_alloc(pool);
+	if (mbuf == NULL) {
+		printf("%s(): mbuf alloc failed\n", __func__);
+		return NULL;
+	}
+
+	/* Ensure minimum packet size for Ethernet */
+	if (pkt_len < RTE_ETHER_MIN_LEN)
+		pkt_len = RTE_ETHER_MIN_LEN;
+
+	eth_hdr = (struct rte_ether_hdr *)rte_pktmbuf_append(mbuf, pkt_len);
+	if (eth_hdr == NULL) {
+		printf("%s(): append %u bytes failed\n", __func__, pkt_len);
+		rte_pktmbuf_free(mbuf);
+		return NULL;
+	}
+
+	/* Create Ethernet header */
+	eth_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
+	memset(&eth_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN); /* broadcast */
+	memset(&eth_hdr->src_addr, 0x02, RTE_ETHER_ADDR_LEN);
+	eth_hdr->src_addr.addr_bytes[5] = 0x01;
+	eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+	/* Create simple IPv4 header */
+	ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
+	memset(ip_hdr, 0, sizeof(*ip_hdr));
+	ip_hdr->version_ihl = 0x45; /* IPv4, 20 byte header */
+	ip_hdr->total_length = rte_cpu_to_be_16(pkt_len - sizeof(*eth_hdr));
+	ip_hdr->time_to_live = 64;
+	ip_hdr->next_proto_id = IPPROTO_UDP;
+	ip_hdr->src_addr = rte_cpu_to_be_32(0x0A000001); /* 10.0.0.1 */
+	ip_hdr->dst_addr = rte_cpu_to_be_32(0x0A000002); /* 10.0.0.2 */
+
+	/* Fill payload with pattern */
+	payload = (uint8_t *)(ip_hdr + 1);
+	for (i = 0; i < pkt_len - sizeof(*eth_hdr) - sizeof(*ip_hdr); i++)
+		payload[i] = (uint8_t)(i & 0xFF);
+
+	return mbuf;
+}
+
+static int
+test_tap_send_receive(void)
+{
+	struct rte_mbuf *tx_mbufs[MAX_PKT_BURST];
+	struct rte_mbuf *rx_mbufs[MAX_PKT_BURST];
+	uint16_t nb_tx, nb_rx;
+	int i;
+
+	printf("Testing TAP packet send and receive\n");
+
+	/* Create test packets */
+	for (i = 0; i < MAX_PKT_BURST / 2; i++) {
+		tx_mbufs[i] = create_test_packet(mp, PKT_LEN);
+		if (tx_mbufs[i] == NULL) {
+			printf("Failed to create test packet %d\n", i);
+			/* Free already allocated packets */
+			while (--i >= 0)
+				rte_pktmbuf_free(tx_mbufs[i]);
+			return TEST_FAILED;
+		}
+	}
+
+	/* Send packets */
+	nb_tx = rte_eth_tx_burst(tap_port0, 0, tx_mbufs, MAX_PKT_BURST / 2);
+	printf("Transmitted %u packets on port %d\n", nb_tx, tap_port0);
+
+	/* Free any unsent packets */
+	for (i = nb_tx; i < MAX_PKT_BURST / 2; i++)
+		rte_pktmbuf_free(tx_mbufs[i]);
+
+	if (nb_tx == 0) {
+		printf("Warning: No packets transmitted (this may be expected if interface is not up)\n");
+		return TEST_SUCCESS;
+	}
+
+	/* Small delay to allow packets to be processed */
+	usleep(10000);
+
+	/* Try to receive packets (note: TAP loopback depends on kernel config) */
+	nb_rx = rte_eth_rx_burst(tap_port0, 0, rx_mbufs, MAX_PKT_BURST);
+	printf("Received %u packets on port %d\n", nb_rx, tap_port0);
+
+	/* Free received packets */
+	for (i = 0; i < nb_rx; i++)
+		rte_pktmbuf_free(rx_mbufs[i]);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_stats_get(int port)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	printf("Testing TAP PMD stats_get for port %d\n", port);
+
+	ret = rte_eth_stats_get(port, &stats);
+	if (ret != 0) {
+		printf("Error: failed to get stats for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d stats:\n", port);
+	printf("  ipackets: %"PRIu64"\n", stats.ipackets);
+	printf("  opackets: %"PRIu64"\n", stats.opackets);
+	printf("  ibytes:   %"PRIu64"\n", stats.ibytes);
+	printf("  obytes:   %"PRIu64"\n", stats.obytes);
+	printf("  ierrors:  %"PRIu64"\n", stats.ierrors);
+	printf("  oerrors:  %"PRIu64"\n", stats.oerrors);
+
+	return 0;
+}
+
+static int
+test_tap_stats_reset(int port)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	printf("Testing TAP PMD stats_reset for port %d\n", port);
+
+	ret = rte_eth_stats_reset(port);
+	if (ret != 0) {
+		printf("Error: failed to reset stats for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_stats_get(port, &stats);
+	if (ret != 0) {
+		printf("Error: failed to get stats after reset for port %d\n",
+		       port);
+		return -1;
+	}
+
+	/* After reset, all stats should be zero */
+	if (stats.ipackets != 0 || stats.opackets != 0 ||
+	    stats.ibytes != 0 || stats.obytes != 0 ||
+	    stats.ierrors != 0 || stats.oerrors != 0) {
+		printf("Error: port %d stats are not zero after reset\n", port);
+		return -1;
+	}
+
+	printf("Stats reset successful for port %d\n", port);
+	return 0;
+}
+
+static int
+test_tap_link_status(int port)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	printf("Testing TAP PMD link status for port %d\n", port);
+
+	ret = rte_eth_link_get_nowait(port, &link);
+	if (ret < 0) {
+		printf("Error: failed to get link status for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d link: status=%s speed=%u duplex=%s\n",
+	       port,
+	       link.link_status ? "up" : "down",
+	       link.link_speed,
+	       link.link_duplex ? "full" : "half");
+
+	return 0;
+}
+
+static int
+test_tap_dev_info(int port)
+{
+	struct rte_eth_dev_info dev_info;
+	int ret;
+
+	printf("Testing TAP PMD dev_info for port %d\n", port);
+
+	ret = rte_eth_dev_info_get(port, &dev_info);
+	if (ret != 0) {
+		printf("Error: failed to get dev info for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d device info:\n", port);
+	printf("  driver_name: %s\n", dev_info.driver_name);
+	printf("  if_index: %u\n", dev_info.if_index);
+	printf("  max_rx_queues: %u\n", dev_info.max_rx_queues);
+	printf("  max_tx_queues: %u\n", dev_info.max_tx_queues);
+	printf("  max_rx_pktlen: %u\n", dev_info.max_rx_pktlen);
+
+	/* Verify this is indeed a TAP device */
+	if (strcmp(dev_info.driver_name, "net_tap") != 0 &&
+	    strcmp(dev_info.driver_name, "net_tun") != 0) {
+		printf("Warning: unexpected driver name: %s\n",
+		       dev_info.driver_name);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_mtu(int port)
+{
+	uint16_t mtu;
+	int ret;
+
+	printf("Testing TAP PMD MTU operations for port %d\n", port);
+
+	/* Get current MTU */
+	ret = rte_eth_dev_get_mtu(port, &mtu);
+	if (ret != 0) {
+		printf("Error: failed to get MTU for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Current MTU for port %d: %u\n", port, mtu);
+
+	/* Try to set a new MTU */
+	ret = rte_eth_dev_set_mtu(port, 1400);
+	if (ret != 0) {
+		printf("Warning: failed to set MTU to 1400 for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		/* Not a fatal error - may require privileges */
+	} else {
+		printf("MTU set to 1400 for port %d\n", port);
+
+		/* Restore original MTU */
+		ret = rte_eth_dev_set_mtu(port, mtu);
+		if (ret != 0)
+			printf("Warning: failed to restore MTU for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_mac_addr(int port)
+{
+	struct rte_ether_addr mac_addr;
+	int ret;
+
+	printf("Testing TAP PMD MAC address for port %d\n", port);
+
+	ret = rte_eth_macaddr_get(port, &mac_addr);
+	if (ret != 0) {
+		printf("Error: failed to get MAC address for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d MAC address: " RTE_ETHER_ADDR_PRT_FMT "\n",
+	       port, RTE_ETHER_ADDR_BYTES(&mac_addr));
+
+	return 0;
+}
+
+static int
+test_tap_promiscuous(int port)
+{
+	int ret;
+	int promisc_enabled;
+
+	printf("Testing TAP PMD promiscuous mode for port %d\n", port);
+
+	/* Get current promiscuous state */
+	promisc_enabled = rte_eth_promiscuous_get(port);
+	printf("Promiscuous mode initially %s for port %d\n",
+	       promisc_enabled ? "enabled" : "disabled", port);
+
+	/* Enable promiscuous mode */
+	ret = rte_eth_promiscuous_enable(port);
+	if (ret != 0) {
+		printf("Warning: failed to enable promiscuous mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_promiscuous_get(port) != 1) {
+			printf("Error: promiscuous mode not enabled after enable call\n");
+			return -1;
+		}
+		printf("Promiscuous mode enabled for port %d\n", port);
+	}
+
+	/* Disable promiscuous mode */
+	ret = rte_eth_promiscuous_disable(port);
+	if (ret != 0) {
+		printf("Warning: failed to disable promiscuous mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_promiscuous_get(port) != 0) {
+			printf("Error: promiscuous mode not disabled after disable call\n");
+			return -1;
+		}
+		printf("Promiscuous mode disabled for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_allmulti(int port)
+{
+	int ret;
+	int allmulti_enabled;
+
+	printf("Testing TAP PMD allmulticast mode for port %d\n", port);
+
+	/* Get current allmulticast state */
+	allmulti_enabled = rte_eth_allmulticast_get(port);
+	printf("Allmulticast mode initially %s for port %d\n",
+	       allmulti_enabled ? "enabled" : "disabled", port);
+
+	/* Enable allmulticast mode */
+	ret = rte_eth_allmulticast_enable(port);
+	if (ret != 0) {
+		printf("Warning: failed to enable allmulticast mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_allmulticast_get(port) != 1) {
+			printf("Error: allmulticast mode not enabled after enable call\n");
+			return -1;
+		}
+		printf("Allmulticast mode enabled for port %d\n", port);
+	}
+
+	/* Disable allmulticast mode */
+	ret = rte_eth_allmulticast_disable(port);
+	if (ret != 0) {
+		printf("Warning: failed to disable allmulticast mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_allmulticast_get(port) != 0) {
+			printf("Error: allmulticast mode not disabled after disable call\n");
+			return -1;
+		}
+		printf("Allmulticast mode disabled for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_queue_start_stop(int port)
+{
+	int ret;
+
+	printf("Testing TAP PMD queue start/stop for port %d\n", port);
+
+	/* Stop RX queue */
+	ret = rte_eth_dev_rx_queue_stop(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to stop RX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("RX queue stopped for port %d\n", port);
+
+	/* Stop TX queue */
+	ret = rte_eth_dev_tx_queue_stop(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to stop TX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("TX queue stopped for port %d\n", port);
+
+	/* Start RX queue */
+	ret = rte_eth_dev_rx_queue_start(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to start RX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("RX queue started for port %d\n", port);
+
+	/* Start TX queue */
+	ret = rte_eth_dev_tx_queue_start(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to start TX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("TX queue started for port %d\n", port);
+
+	return 0;
+}
+
+static int
+test_tap_link_up_down(int port)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	printf("Testing TAP PMD link up/down for port %d\n", port);
+
+	/* Set link down */
+	ret = rte_eth_dev_set_link_down(port);
+	if (ret != 0) {
+		printf("Warning: failed to set link down for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		ret = rte_eth_link_get_nowait(port, &link);
+		if (ret == 0)
+			printf("Link status after set_link_down: %s\n",
+			       link.link_status ? "up" : "down");
+	}
+
+	/* Set link up */
+	ret = rte_eth_dev_set_link_up(port);
+	if (ret != 0) {
+		printf("Warning: failed to set link up for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		ret = rte_eth_link_get_nowait(port, &link);
+		if (ret == 0)
+			printf("Link status after set_link_up: %s\n",
+			       link.link_status ? "up" : "down");
+	}
+
+	return 0;
+}
+
+static int
+test_tap_dev_stop_start(int port)
+{
+	int ret;
+
+	printf("Testing TAP PMD device stop/start for port %d\n", port);
+
+	/* Stop the device */
+	ret = rte_eth_dev_stop(port);
+	if (ret != 0) {
+		printf("Error: failed to stop port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Device stopped for port %d\n", port);
+
+	/* Start the device again */
+	ret = rte_eth_dev_start(port);
+	if (ret != 0) {
+		printf("Error: failed to start port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Device started for port %d\n", port);
+
+	return 0;
+}
+
+static int
+test_tap_multi_queue(void)
+{
+	printf("Testing TAP PMD multi-queue configuration\n");
+
+	/* Create a separate mempool for multi-queue test */
+	struct rte_mempool *mq_mp
+		= rte_pktmbuf_pool_create("tap_mq_pool", NB_MBUF, 32, 0,
+					  RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+	if (mq_mp == NULL) {
+		printf("Warning: failed to create mempool for multi-queue test: %s\n",
+		       rte_strerror(rte_errno));
+		return TEST_SKIPPED;
+	}
+
+	/* Create a new TAP device for multi-queue test */
+	int ret = rte_vdev_init("net_tap_mq", "iface=dtap_mq");
+	if (ret < 0) {
+		printf("Warning: failed to create multi-queue TAP device: %s\n",
+		       rte_strerror(-ret));
+		rte_mempool_free(mq_mp);
+		return TEST_SKIPPED;
+	}
+
+	/* Find the port */
+	uint16_t port;
+	RTE_ETH_FOREACH_DEV(port) {
+		char name[RTE_ETH_NAME_MAX_LEN];
+		if (rte_eth_dev_get_name_by_port(port, name) == 0 &&
+		    strstr(name, "net_tap_mq"))
+			goto found;
+	}
+
+	printf("Error: could not find multi-queue TAP device\n");
+	rte_vdev_uninit("net_tap_mq");
+	rte_mempool_free(mq_mp);
+	return TEST_FAILED;
+
+found:
+	/* Configure with multiple queues */
+	struct rte_eth_conf port_conf = { 0 };
+	const uint16_t nb_queues = 2;
+	ret = rte_eth_dev_configure(port, nb_queues, nb_queues, &port_conf);
+	if (ret < 0) {
+		printf("Warning: multi-queue configure failed: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_SKIPPED;
+	}
+
+	/* Setup TX queues */
+	for (uint16_t q = 0; q < nb_queues; q++) {
+		ret = rte_eth_tx_queue_setup(port, q, RING_SIZE, SOCKET0, NULL);
+		if (ret < 0) {
+			printf("Error: TX queue %u setup failed: %s\n",
+			       q, rte_strerror(-ret));
+			rte_vdev_uninit("net_tap_mq");
+			rte_mempool_free(mq_mp);
+			return TEST_FAILED;
+		}
+	}
+
+	/* Setup RX queues */
+	for (uint16_t q = 0; q < nb_queues; q++) {
+		ret = rte_eth_rx_queue_setup(port, q, RING_SIZE, SOCKET0, NULL, mq_mp);
+		if (ret < 0) {
+			printf("Error: RX queue %u setup failed: %s\n",
+			       q, rte_strerror(-ret));
+			rte_vdev_uninit("net_tap_mq");
+			rte_mempool_free(mq_mp);
+			return TEST_FAILED;
+		}
+	}
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0) {
+		printf("Error: failed to start multi-queue port: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_FAILED;
+	}
+
+	printf("Multi-queue TAP device configured with %u queues\n", nb_queues);
+
+	/* Cleanup */
+	ret = rte_eth_dev_stop(port);
+	if (ret != 0) {
+		printf("Error: rte_eth_dev_stop failed\n");
+		return TEST_FAILED;
+	}
+	rte_eth_dev_close(port);
+	rte_vdev_uninit("net_tap_mq");
+	rte_mempool_free(mq_mp);
+
+	return TEST_SUCCESS;
+}
+
+static void
+test_tap_cleanup(void)
+{
+	int ret;
+
+	printf("Cleaning up TAP PMD test resources\n");
+
+	if (tap_port0 >= 0) {
+		ret = rte_eth_dev_stop(tap_port0);
+		if (ret != 0)
+			printf("Warning: failed to stop port %d: %s\n",
+			       tap_port0, rte_strerror(-ret));
+		rte_eth_dev_close(tap_port0);
+	}
+
+	if (tap_port1 >= 0) {
+		ret = rte_eth_dev_stop(tap_port1);
+		if (ret != 0)
+			printf("Warning: failed to stop port %d: %s\n",
+			       tap_port1, rte_strerror(-ret));
+		rte_eth_dev_close(tap_port1);
+	}
+
+	rte_vdev_uninit("net_tap0");
+	rte_vdev_uninit("net_tap1");
+
+	if (mp != NULL) {
+		rte_mempool_free(mp);
+		mp = NULL;
+	}
+
+	tap_port0 = -1;
+	tap_port1 = -1;
+}
+
+static int
+test_tap_setup(void)
+{
+	int ret;
+	uint16_t port_id;
+
+	printf("Setting up TAP PMD test\n");
+
+	/* Create mempool */
+	mp = rte_pktmbuf_pool_create("tap_test_pool", NB_MBUF, 32, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+	if (mp == NULL) {
+		printf("Error: failed to create mempool: %s\n",
+		       rte_strerror(rte_errno));
+		return -1;
+	}
+
+	/* Create first TAP device */
+	ret = rte_vdev_init("net_tap0", "iface=dtap_test0");
+	if (ret < 0) {
+		printf("Error: failed to create TAP device net_tap0: %s\n",
+		       rte_strerror(-ret));
+		rte_mempool_free(mp);
+		mp = NULL;
+		return -1;
+	}
+
+	/* Create second TAP device */
+	ret = rte_vdev_init("net_tap1", "iface=dtap_test1");
+	if (ret < 0) {
+		printf("Error: failed to create TAP device net_tap1: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap0");
+		rte_mempool_free(mp);
+		mp = NULL;
+		return -1;
+	}
+
+	/* Find the port IDs */
+	RTE_ETH_FOREACH_DEV(port_id) {
+		char name[RTE_ETH_NAME_MAX_LEN];
+		if (rte_eth_dev_get_name_by_port(port_id, name) != 0)
+			continue;
+
+		if (strstr(name, "net_tap0"))
+			tap_port0 = port_id;
+		else if (strstr(name, "net_tap1"))
+			tap_port1 = port_id;
+	}
+
+	if (tap_port0 < 0 || tap_port1 < 0) {
+		printf("Error: failed to find TAP port IDs\n");
+		test_tap_cleanup();
+		return -1;
+	}
+
+	printf("Created TAP devices: tap_port0=%d, tap_port1=%d\n",
+	       tap_port0, tap_port1);
+
+	return 0;
+}
+
+/* Individual test case wrappers */
+
+static int
+test_tap_configure_port0(void)
+{
+	return test_tap_ethdev_configure(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_configure_port1(void)
+{
+	return test_tap_ethdev_configure(tap_port1) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_packet_send_receive(void)
+{
+	return test_tap_send_receive();
+}
+
+static int
+test_tap_get_stats(void)
+{
+	if (test_tap_stats_get(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_stats_get(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_reset_stats(void)
+{
+	if (test_tap_stats_reset(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_stats_reset(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_get_link_status(void)
+{
+	if (test_tap_link_status(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_link_status(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_get_dev_info(void)
+{
+	if (test_tap_dev_info(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_dev_info(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_mtu_ops(void)
+{
+	return test_tap_mtu(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_mac_addr_get(void)
+{
+	if (test_tap_mac_addr(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_mac_addr(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_promisc_mode(void)
+{
+	return test_tap_promiscuous(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_allmulti_mode(void)
+{
+	return test_tap_allmulti(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_queue_ops(void)
+{
+	return test_tap_queue_start_stop(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_link_ops(void)
+{
+	return test_tap_link_up_down(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_stop_start(void)
+{
+	return test_tap_dev_stop_start(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_multiqueue(void)
+{
+	return test_tap_multi_queue();
+}
+
+static struct unit_test_suite test_pmd_tap_suite = {
+	.setup = test_tap_setup,
+	.teardown = test_tap_cleanup,
+	.suite_name = "TAP PMD Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_tap_configure_port0),
+		TEST_CASE(test_tap_configure_port1),
+		TEST_CASE(test_tap_get_dev_info),
+		TEST_CASE(test_tap_get_link_status),
+		TEST_CASE(test_tap_mac_addr_get),
+		TEST_CASE(test_tap_get_stats),
+		TEST_CASE(test_tap_reset_stats),
+		TEST_CASE(test_tap_packet_send_receive),
+		TEST_CASE(test_tap_promisc_mode),
+		TEST_CASE(test_tap_allmulti_mode),
+		TEST_CASE(test_tap_mtu_ops),
+		TEST_CASE(test_tap_queue_ops),
+		TEST_CASE(test_tap_link_ops),
+		TEST_CASE(test_tap_stop_start),
+		TEST_CASE(test_tap_multiqueue),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_pmd_tap(void)
+{
+	return unit_test_suite_runner(&test_pmd_tap_suite);
+}
+
+#endif /* RTE_EXEC_ENV_LINUX */
+
+REGISTER_FAST_TEST(tap_pmd_autotest, NOHUGE_OK, ASAN_OK, test_pmd_tap);
-- 
2.51.0


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

* [PATCH v3 02/10] net/tap: replace runtime speed capability with constant
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 01/10] test: add unit tests for TAP PMD Stephen Hemminger
@ 2026-02-20  5:02   ` Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 03/10] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
                     ` (7 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20  5:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The TAP/TUN virtual device always operates at 10G. The link speed is
set in the static initializer for pmd_link, so the runtime assignments
in rte_pmd_tap_probe() and rte_pmd_tun_probe() are redundant.

Replace tap_dev_speed_capa(), which dynamically computed speed
capabilities against pmd_link.link_speed, with a compile-time
constant TAP_SPEED_CAPA. Remove the unused 'speed' variable and
redundant assignments.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 7a8a98cddb..aa236cf967 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -891,39 +891,13 @@ tap_dev_configure(struct rte_eth_dev *dev)
 	return 0;
 }
 
-static uint32_t
-tap_dev_speed_capa(void)
-{
-	uint32_t speed = pmd_link.link_speed;
-	uint32_t capa = 0;
-
-	if (speed >= RTE_ETH_SPEED_NUM_10M)
-		capa |= RTE_ETH_LINK_SPEED_10M;
-	if (speed >= RTE_ETH_SPEED_NUM_100M)
-		capa |= RTE_ETH_LINK_SPEED_100M;
-	if (speed >= RTE_ETH_SPEED_NUM_1G)
-		capa |= RTE_ETH_LINK_SPEED_1G;
-	if (speed >= RTE_ETH_SPEED_NUM_5G)
-		capa |= RTE_ETH_LINK_SPEED_2_5G;
-	if (speed >= RTE_ETH_SPEED_NUM_5G)
-		capa |= RTE_ETH_LINK_SPEED_5G;
-	if (speed >= RTE_ETH_SPEED_NUM_10G)
-		capa |= RTE_ETH_LINK_SPEED_10G;
-	if (speed >= RTE_ETH_SPEED_NUM_20G)
-		capa |= RTE_ETH_LINK_SPEED_20G;
-	if (speed >= RTE_ETH_SPEED_NUM_25G)
-		capa |= RTE_ETH_LINK_SPEED_25G;
-	if (speed >= RTE_ETH_SPEED_NUM_40G)
-		capa |= RTE_ETH_LINK_SPEED_40G;
-	if (speed >= RTE_ETH_SPEED_NUM_50G)
-		capa |= RTE_ETH_LINK_SPEED_50G;
-	if (speed >= RTE_ETH_SPEED_NUM_56G)
-		capa |= RTE_ETH_LINK_SPEED_56G;
-	if (speed >= RTE_ETH_SPEED_NUM_100G)
-		capa |= RTE_ETH_LINK_SPEED_100G;
-
-	return capa;
-}
+/* Speed capabilities for virtual TAP/TUN device (always 10G) */
+#define TAP_SPEED_CAPA (RTE_ETH_LINK_SPEED_10M | \
+			RTE_ETH_LINK_SPEED_100M | \
+			RTE_ETH_LINK_SPEED_1G | \
+			RTE_ETH_LINK_SPEED_2_5G | \
+			RTE_ETH_LINK_SPEED_5G | \
+			RTE_ETH_LINK_SPEED_10G)
 
 static int
 tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
@@ -936,7 +910,7 @@ tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
 	dev_info->max_rx_queues = RTE_PMD_TAP_MAX_QUEUES;
 	dev_info->max_tx_queues = RTE_PMD_TAP_MAX_QUEUES;
 	dev_info->min_rx_bufsize = 0;
-	dev_info->speed_capa = tap_dev_speed_capa();
+	dev_info->speed_capa = TAP_SPEED_CAPA;
 	dev_info->rx_queue_offload_capa = TAP_RX_OFFLOAD;
 	dev_info->rx_offload_capa = dev_info->rx_queue_offload_capa;
 	dev_info->tx_queue_offload_capa = TAP_TX_OFFLOAD;
@@ -2325,7 +2299,6 @@ set_mac_type(const char *key __rte_unused,
  * Open a TUN interface device. TUN PMD
  * 1) sets tap_type as false
  * 2) intakes iface as argument.
- * 3) as interface is virtual set speed to 10G
  */
 static int
 rte_pmd_tun_probe(struct rte_vdev_device *dev)
@@ -2373,7 +2346,6 @@ rte_pmd_tun_probe(struct rte_vdev_device *dev)
 			}
 		}
 	}
-	pmd_link.link_speed = RTE_ETH_SPEED_NUM_10G;
 
 	TAP_LOG(DEBUG, "Initializing pmd_tun for %s", name);
 
@@ -2495,7 +2467,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 	const char *name, *params;
 	int ret;
 	struct rte_kvargs *kvlist = NULL;
-	int speed;
 	char tap_name[RTE_ETH_NAME_MAX_LEN];
 	char remote_iface[RTE_ETH_NAME_MAX_LEN];
 	struct rte_ether_addr user_mac = { .addr_bytes = {0} };
@@ -2545,8 +2516,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 		return 0;
 	}
 
-	speed = RTE_ETH_SPEED_NUM_10G;
-
 	/* use tap%d which causes kernel to choose next available */
 	strlcpy(tap_name, DEFAULT_TAP_NAME "%d", RTE_ETH_NAME_MAX_LEN);
 	memset(remote_iface, 0, RTE_ETH_NAME_MAX_LEN);
@@ -2587,7 +2556,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 				persist = 1;
 		}
 	}
-	pmd_link.link_speed = speed;
 
 	TAP_LOG(DEBUG, "Initializing pmd_tap for %s", name);
 
-- 
2.51.0


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

* [PATCH v3 03/10] net/tap: clarify TUN/TAP flag assignment
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 01/10] test: add unit tests for TAP PMD Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 02/10] net/tap: replace runtime speed capability with constant Stephen Hemminger
@ 2026-02-20  5:02   ` Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 04/10] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
                     ` (6 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20  5:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add parentheses in the ternary expression that relied on operator
precedence between '?:' and '|'. No functional change.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index aa236cf967..e06e1ca079 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -155,7 +155,8 @@ tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
 	 * to check if a received packet has been truncated.
 	 */
 	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
-		IFF_TAP : IFF_TUN | IFF_POINTOPOINT;
+		IFF_TAP : (IFF_TUN | IFF_POINTOPOINT);
+
 	strlcpy(ifr.ifr_name, pmd->name, IFNAMSIZ);
 
 	fd = open(TUN_TAP_DEV_PATH, O_RDWR);
-- 
2.51.0


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

* [PATCH v3 04/10] net/tap: extend fixed MAC range to 16 bits
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
                     ` (2 preceding siblings ...)
  2026-02-20  5:02   ` [PATCH v3 03/10] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
@ 2026-02-20  5:02   ` Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 05/10] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
                     ` (5 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20  5:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The generated fixed MAC address stored the interface index in a single
byte, which wraps after 256 devices and produces duplicate MACs under
repeated hot-plug. Spread the index across the last two bytes of the
address, extending the unique range to 65536.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index e06e1ca079..8b6d5db37e 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2274,13 +2274,15 @@ set_mac_type(const char *key __rte_unused,
 		return 0;
 
 	if (!strncasecmp(ETH_TAP_MAC_FIXED, value, strlen(ETH_TAP_MAC_FIXED))) {
-		static int iface_idx;
-
-		/* fixed mac = 02:64:74:61:70:<iface_idx> */
-		memcpy((char *)user_mac->addr_bytes, "\002dtap",
-			RTE_ETHER_ADDR_LEN);
-		user_mac->addr_bytes[RTE_ETHER_ADDR_LEN - 1] =
-			iface_idx++ + '0';
+		static uint16_t iface_idx;
+
+		/* fixed mac that is locally assigned based off of iface_idx. */
+		user_mac->addr_bytes[0] = RTE_ETHER_LOCAL_ADMIN_ADDR; /* 0x2 */
+		user_mac->addr_bytes[1] = 'd';
+		user_mac->addr_bytes[2] = 't';
+		user_mac->addr_bytes[3] = 'a';
+		user_mac->addr_bytes[4] = 'p' + (iface_idx >> 8);
+		user_mac->addr_bytes[5] = '0' + (uint8_t)iface_idx++;
 		goto success;
 	}
 
-- 
2.51.0


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

* [PATCH v3 05/10] net/tap: skip checksum on truncated L4 headers
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
                     ` (3 preceding siblings ...)
  2026-02-20  5:02   ` [PATCH v3 04/10] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
@ 2026-02-20  5:02   ` Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 06/10] net/tap: fix resource leaks in tap create error path Stephen Hemminger
                     ` (4 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20  5:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a bounds check before accessing the UDP or TCP header in
tap_verify_csum(). A single-segment packet whose L4 header extends
past rte_pktmbuf_data_len() would cause an out-of-bounds read.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 8b6d5db37e..45ca32cfb8 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -365,8 +365,15 @@ tap_verify_csum(struct rte_mbuf *mbuf)
 		 */
 		return;
 	}
+
 	if (l4 == RTE_PTYPE_L4_UDP || l4 == RTE_PTYPE_L4_TCP) {
 		int cksum_ok;
+		const unsigned int l4_min_len = (l4 == RTE_PTYPE_L4_UDP)
+			? sizeof(struct rte_udp_hdr) : sizeof(struct rte_tcp_hdr);
+
+		/* Don't verify checksum if L4 header is truncated */
+		if (l2_len + l3_len + l4_min_len > rte_pktmbuf_data_len(mbuf))
+			return;
 
 		l4_hdr = rte_pktmbuf_mtod_offset(mbuf, void *, l2_len + l3_len);
 		/* Don't verify checksum for multi-segment packets. */
-- 
2.51.0


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

* [PATCH v3 06/10] net/tap: fix resource leaks in tap create error path
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
                     ` (4 preceding siblings ...)
  2026-02-20  5:02   ` [PATCH v3 05/10] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
@ 2026-02-20  5:02   ` Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 07/10] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
                     ` (3 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20  5:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

Correct two leaks in eth_dev_tap_create():

- process_private was never freed on error_exit. Add free() before
  releasing the port.
- If the process_private malloc failed, the function returned -1
  directly without releasing the ethdev port allocated by
  rte_eth_vdev_allocate(). Jump to a new error_exit_nodev_release
  label instead.

Bugzilla ID: 1881
Fixes: ed8132e7c912 ("net/tap: move fds of queues to be in process private")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 45ca32cfb8..9b38d1f50b 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2010,7 +2010,7 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	process_private = malloc(sizeof(struct pmd_process_private));
 	if (process_private == NULL) {
 		TAP_LOG(ERR, "Failed to alloc memory for process private");
-		return -1;
+		goto error_exit_nodev_release;
 	}
 	memset(process_private, 0, sizeof(struct pmd_process_private));
 
@@ -2200,9 +2200,12 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 		close(pmd->nlsk_fd);
 	if (pmd->ka_fd != -1)
 		close(pmd->ka_fd);
+	rte_intr_instance_free(pmd->intr_handle);
+	free(dev->process_private);
+
+error_exit_nodev_release:
 	/* mac_addrs must not be freed alone because part of dev_private */
 	dev->data->mac_addrs = NULL;
-	rte_intr_instance_free(pmd->intr_handle);
 	rte_eth_dev_release_port(dev);
 
 error_exit_nodev:
-- 
2.51.0


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

* [PATCH v3 07/10] net/tap: fix resource leaks in secondary process probe
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
                     ` (5 preceding siblings ...)
  2026-02-20  5:02   ` [PATCH v3 06/10] net/tap: fix resource leaks in tap create error path Stephen Hemminger
@ 2026-02-20  5:02   ` Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 08/10] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
                     ` (2 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20  5:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

Four error paths in the secondary-process branch of
rte_pmd_tap_probe() returned -1 without cleanup:

- primary process not alive: leaked eth_dev
- process_private malloc failure: leaked eth_dev
- tap_mp_attach_queues failure: leaked eth_dev and process_private
- rte_mp_action_register failure: leaked eth_dev and process_private

Add secondary_fail and secondary_fail_pp labels to free
process_private and release the ethdev port.

Bugzilla ID: 1881
Fixes: c9aa56edec8e ("net/tap: access primary process queues from secondary")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 9b38d1f50b..974b45ecad 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2502,31 +2502,38 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 		eth_dev->tx_pkt_burst = pmd_tx_burst;
 		if (!rte_eal_primary_proc_alive(NULL)) {
 			TAP_LOG(ERR, "Primary process is missing");
-			return -1;
+			goto secondary_fail;
 		}
 		eth_dev->process_private = malloc(sizeof(struct pmd_process_private));
 		if (eth_dev->process_private == NULL) {
 			TAP_LOG(ERR,
 				"Failed to alloc memory for process private");
-			return -1;
+			goto secondary_fail;
 		}
 		memset(eth_dev->process_private, 0, sizeof(struct pmd_process_private));
 
 		ret = tap_mp_attach_queues(name, eth_dev);
 		if (ret != 0)
-			return -1;
+			goto secondary_fail_pp;
 
 		if (!tap_devices_count) {
 			ret = rte_mp_action_register(TAP_MP_REQ_START_RXTX, tap_mp_req_start_rxtx);
 			if (ret < 0 && rte_errno != ENOTSUP) {
 				TAP_LOG(ERR, "tap: Failed to register IPC callback: %s",
 					strerror(rte_errno));
-				return -1;
+				goto secondary_fail_pp;
 			}
 		}
 		tap_devices_count++;
 		rte_eth_dev_probing_finish(eth_dev);
 		return 0;
+
+secondary_fail_pp:
+		free(eth_dev->process_private);
+		eth_dev->process_private = NULL;
+secondary_fail:
+		rte_eth_dev_release_port(eth_dev);
+		return -1;
 	}
 
 	/* use tap%d which causes kernel to choose next available */
-- 
2.51.0


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

* [PATCH v3 08/10] net/tap: free IPC reply buffer on queue count mismatch
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
                     ` (6 preceding siblings ...)
  2026-02-20  5:02   ` [PATCH v3 07/10] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
@ 2026-02-20  5:02   ` Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 09/10] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 10/10] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20  5:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

In tap_mp_attach_queues(), if the reply queue count does not match
the number of received file descriptors, the function returns -1
without freeing the reply buffer allocated by rte_mp_request_sync().
Add the missing free().

Bugzilla ID: 1881
Fixes: 9ad43ad8fbee ("net/tap: fix potential IPC buffer overrun")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 974b45ecad..deb1d72382 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2407,6 +2407,7 @@ tap_mp_attach_queues(const char *port_name, struct rte_eth_dev *dev)
 	/* Attach the queues from received file descriptors */
 	if (reply_param->q_count != reply->num_fds) {
 		TAP_LOG(ERR, "Unexpected number of fds received");
+		free(reply);
 		return -1;
 	}
 
-- 
2.51.0


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

* [PATCH v3 09/10] net/tap: fix use-after-free on remote flow creation failure
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
                     ` (7 preceding siblings ...)
  2026-02-20  5:02   ` [PATCH v3 08/10] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
@ 2026-02-20  5:02   ` Stephen Hemminger
  2026-02-20  5:02   ` [PATCH v3 10/10] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20  5:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

After a local TC filter rule is installed and the flow is inserted
into pmd->flows, failure during remote flow creation jumps to the
fail label which frees the flow without removing it from the list
and without deleting the kernel-side TC rule.

Send RTM_DELTFILTER to clean up the local rule and call
LIST_REMOVE before freeing.

Bugzilla ID: 1881
Fixes: 2bc06869cd94 ("net/tap: add remote netdevice traffic capture")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 9d4ef27a8a..427faf75d5 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -1293,7 +1293,7 @@ tap_flow_create(struct rte_eth_dev *dev,
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
 				"cannot allocate memory for rte_flow");
-			goto fail;
+			goto fail_remove;
 		}
 		msg = &remote_flow->msg;
 		/* set the rule if_index for the remote netdevice */
@@ -1307,14 +1307,14 @@ tap_flow_create(struct rte_eth_dev *dev,
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "rte flow rule validation failed");
-			goto fail;
+			goto fail_remove;
 		}
 		err = tap_nl_send(pmd->nlsk_fd, &msg->nh);
 		if (err < 0) {
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "Failure sending nl request");
-			goto fail;
+			goto fail_remove;
 		}
 		err = tap_nl_recv_ack(pmd->nlsk_fd);
 		if (err < 0) {
@@ -1325,15 +1325,22 @@ tap_flow_create(struct rte_eth_dev *dev,
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL,
 				"overlapping rules or Kernel too old for flower support");
-			goto fail;
+			goto fail_remove;
 		}
 		flow->remote_flow = remote_flow;
 	}
 	return flow;
+
+fail_remove:
+	/* Delete the local TC rule that was already installed */
+	flow->msg.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	flow->msg.nh.nlmsg_type = RTM_DELTFILTER;
+	if (tap_nl_send(pmd->nlsk_fd, &flow->msg.nh) >= 0)
+		tap_nl_recv_ack(pmd->nlsk_fd);
+	LIST_REMOVE(flow, next);
 fail:
 	rte_free(remote_flow);
-	if (flow)
-		tap_flow_free(pmd, flow);
+	tap_flow_free(pmd, flow);
 	return NULL;
 }
 
-- 
2.51.0


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

* [PATCH v3 10/10] net/tap: free remote flow when implicit rule already exists
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
                     ` (8 preceding siblings ...)
  2026-02-20  5:02   ` [PATCH v3 09/10] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
@ 2026-02-20  5:02   ` Stephen Hemminger
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20  5:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

When tap_flow_implicit_create() gets EEXIST from the kernel, the
allocated remote_flow is never freed. Add rte_free() on that path.

Bugzilla ID: 1880
Fixes: 2ef1c0da894a ("net/tap: fix isolation mode toggling")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/tap_flow.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 427faf75d5..da1e70019a 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -1625,8 +1625,10 @@ int tap_flow_implicit_create(struct pmd_internals *pmd,
 	err = tap_nl_recv_ack(pmd->nlsk_fd);
 	if (err < 0) {
 		/* Silently ignore re-entering existing rule */
-		if (errno == EEXIST)
+		if (errno == EEXIST) {
+			rte_free(remote_flow);
 			goto success;
+		}
 		TAP_LOG(ERR,
 			"Kernel refused TC filter rule creation (%d): %s",
 			errno, strerror(errno));
-- 
2.51.0


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

* [PATCH 00/10] net/tap: cleanups and bug fixes
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (12 preceding siblings ...)
  2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
@ 2026-02-20 17:02 ` Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 01/10] net/tap: replace runtime speed capability with constant Stephen Hemminger
                     ` (9 more replies)
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
  14 siblings, 10 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20 17:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Patches 1-3 are minor cleanups: replace the runtime speed capability
function with a compile-time constant (TAP is always 10G), clarify
TUN/TAP flag operator precedence with parentheses, and extend the
fixed MAC index to 16 bits to avoid duplicates after 256 hot-plug
cycles.

Patches 4-9 fix bugs tagged for stable: a bounds check to prevent
an out-of-bounds read on truncated L4 headers; resource leaks in
the primary and secondary process probe error paths; a missing free
of the IPC reply buffer on queue count mismatch; a use-after-free
with an orphaned kernel TC rule when remote flow creation fails; and
a leaked remote_flow allocation on EEXIST from an implicit rule.

Patch 10 adds a unit test suite for the TAP PMD modeled on the ring
PMD tests, covering configuration, link, stats, MTU, MAC, promisc,
allmulti, queue start/stop, link up/down, stop/start, and multi-queue.

v4 - fix build on clang

Stephen Hemminger (10):
  net/tap: replace runtime speed capability with constant
  net/tap: clarify TUN/TAP flag assignment
  net/tap: extend fixed MAC range to 16 bits
  net/tap: skip checksum on truncated L4 headers
  net/tap: fix resource leaks in tap create error path
  net/tap: fix resource leaks in secondary process probe
  net/tap: free IPC reply buffer on queue count mismatch
  net/tap: fix use-after-free on remote flow creation failure
  net/tap: free remote flow when implicit rule already exists
  test: add unit tests for TAP PMD

 app/test/meson.build          |   1 +
 app/test/test_pmd_tap.c       | 907 ++++++++++++++++++++++++++++++++++
 drivers/net/tap/rte_eth_tap.c |  97 ++--
 drivers/net/tap/tap_flow.c    |  23 +-
 4 files changed, 967 insertions(+), 61 deletions(-)
 create mode 100644 app/test/test_pmd_tap.c

-- 
2.51.0


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

* [PATCH v4 01/10] net/tap: replace runtime speed capability with constant
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
@ 2026-02-20 17:02   ` Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 02/10] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
                     ` (8 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20 17:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The TAP/TUN virtual device always operates at 10G. The link speed is
set in the static initializer for pmd_link, so the runtime assignments
in rte_pmd_tap_probe() and rte_pmd_tun_probe() are redundant.

Replace tap_dev_speed_capa(), which dynamically computed speed
capabilities against pmd_link.link_speed, with a compile-time
constant TAP_SPEED_CAPA. Remove the unused 'speed' variable and
redundant assignments.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 7a8a98cddb..aa236cf967 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -891,39 +891,13 @@ tap_dev_configure(struct rte_eth_dev *dev)
 	return 0;
 }
 
-static uint32_t
-tap_dev_speed_capa(void)
-{
-	uint32_t speed = pmd_link.link_speed;
-	uint32_t capa = 0;
-
-	if (speed >= RTE_ETH_SPEED_NUM_10M)
-		capa |= RTE_ETH_LINK_SPEED_10M;
-	if (speed >= RTE_ETH_SPEED_NUM_100M)
-		capa |= RTE_ETH_LINK_SPEED_100M;
-	if (speed >= RTE_ETH_SPEED_NUM_1G)
-		capa |= RTE_ETH_LINK_SPEED_1G;
-	if (speed >= RTE_ETH_SPEED_NUM_5G)
-		capa |= RTE_ETH_LINK_SPEED_2_5G;
-	if (speed >= RTE_ETH_SPEED_NUM_5G)
-		capa |= RTE_ETH_LINK_SPEED_5G;
-	if (speed >= RTE_ETH_SPEED_NUM_10G)
-		capa |= RTE_ETH_LINK_SPEED_10G;
-	if (speed >= RTE_ETH_SPEED_NUM_20G)
-		capa |= RTE_ETH_LINK_SPEED_20G;
-	if (speed >= RTE_ETH_SPEED_NUM_25G)
-		capa |= RTE_ETH_LINK_SPEED_25G;
-	if (speed >= RTE_ETH_SPEED_NUM_40G)
-		capa |= RTE_ETH_LINK_SPEED_40G;
-	if (speed >= RTE_ETH_SPEED_NUM_50G)
-		capa |= RTE_ETH_LINK_SPEED_50G;
-	if (speed >= RTE_ETH_SPEED_NUM_56G)
-		capa |= RTE_ETH_LINK_SPEED_56G;
-	if (speed >= RTE_ETH_SPEED_NUM_100G)
-		capa |= RTE_ETH_LINK_SPEED_100G;
-
-	return capa;
-}
+/* Speed capabilities for virtual TAP/TUN device (always 10G) */
+#define TAP_SPEED_CAPA (RTE_ETH_LINK_SPEED_10M | \
+			RTE_ETH_LINK_SPEED_100M | \
+			RTE_ETH_LINK_SPEED_1G | \
+			RTE_ETH_LINK_SPEED_2_5G | \
+			RTE_ETH_LINK_SPEED_5G | \
+			RTE_ETH_LINK_SPEED_10G)
 
 static int
 tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
@@ -936,7 +910,7 @@ tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
 	dev_info->max_rx_queues = RTE_PMD_TAP_MAX_QUEUES;
 	dev_info->max_tx_queues = RTE_PMD_TAP_MAX_QUEUES;
 	dev_info->min_rx_bufsize = 0;
-	dev_info->speed_capa = tap_dev_speed_capa();
+	dev_info->speed_capa = TAP_SPEED_CAPA;
 	dev_info->rx_queue_offload_capa = TAP_RX_OFFLOAD;
 	dev_info->rx_offload_capa = dev_info->rx_queue_offload_capa;
 	dev_info->tx_queue_offload_capa = TAP_TX_OFFLOAD;
@@ -2325,7 +2299,6 @@ set_mac_type(const char *key __rte_unused,
  * Open a TUN interface device. TUN PMD
  * 1) sets tap_type as false
  * 2) intakes iface as argument.
- * 3) as interface is virtual set speed to 10G
  */
 static int
 rte_pmd_tun_probe(struct rte_vdev_device *dev)
@@ -2373,7 +2346,6 @@ rte_pmd_tun_probe(struct rte_vdev_device *dev)
 			}
 		}
 	}
-	pmd_link.link_speed = RTE_ETH_SPEED_NUM_10G;
 
 	TAP_LOG(DEBUG, "Initializing pmd_tun for %s", name);
 
@@ -2495,7 +2467,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 	const char *name, *params;
 	int ret;
 	struct rte_kvargs *kvlist = NULL;
-	int speed;
 	char tap_name[RTE_ETH_NAME_MAX_LEN];
 	char remote_iface[RTE_ETH_NAME_MAX_LEN];
 	struct rte_ether_addr user_mac = { .addr_bytes = {0} };
@@ -2545,8 +2516,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 		return 0;
 	}
 
-	speed = RTE_ETH_SPEED_NUM_10G;
-
 	/* use tap%d which causes kernel to choose next available */
 	strlcpy(tap_name, DEFAULT_TAP_NAME "%d", RTE_ETH_NAME_MAX_LEN);
 	memset(remote_iface, 0, RTE_ETH_NAME_MAX_LEN);
@@ -2587,7 +2556,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 				persist = 1;
 		}
 	}
-	pmd_link.link_speed = speed;
 
 	TAP_LOG(DEBUG, "Initializing pmd_tap for %s", name);
 
-- 
2.51.0


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

* [PATCH v4 02/10] net/tap: clarify TUN/TAP flag assignment
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 01/10] net/tap: replace runtime speed capability with constant Stephen Hemminger
@ 2026-02-20 17:02   ` Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 03/10] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
                     ` (7 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20 17:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add parentheses in the ternary expression that relied on operator
precedence between '?:' and '|'. No functional change.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index aa236cf967..e06e1ca079 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -155,7 +155,8 @@ tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
 	 * to check if a received packet has been truncated.
 	 */
 	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
-		IFF_TAP : IFF_TUN | IFF_POINTOPOINT;
+		IFF_TAP : (IFF_TUN | IFF_POINTOPOINT);
+
 	strlcpy(ifr.ifr_name, pmd->name, IFNAMSIZ);
 
 	fd = open(TUN_TAP_DEV_PATH, O_RDWR);
-- 
2.51.0


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

* [PATCH v4 03/10] net/tap: extend fixed MAC range to 16 bits
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 01/10] net/tap: replace runtime speed capability with constant Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 02/10] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
@ 2026-02-20 17:02   ` Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 04/10] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
                     ` (6 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20 17:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The generated fixed MAC address stored the interface index in a single
byte, which wraps after 256 devices and produces duplicate MACs under
repeated hot-plug. Spread the index across the last two bytes of the
address, extending the unique range to 65536.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index e06e1ca079..8b6d5db37e 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2274,13 +2274,15 @@ set_mac_type(const char *key __rte_unused,
 		return 0;
 
 	if (!strncasecmp(ETH_TAP_MAC_FIXED, value, strlen(ETH_TAP_MAC_FIXED))) {
-		static int iface_idx;
-
-		/* fixed mac = 02:64:74:61:70:<iface_idx> */
-		memcpy((char *)user_mac->addr_bytes, "\002dtap",
-			RTE_ETHER_ADDR_LEN);
-		user_mac->addr_bytes[RTE_ETHER_ADDR_LEN - 1] =
-			iface_idx++ + '0';
+		static uint16_t iface_idx;
+
+		/* fixed mac that is locally assigned based off of iface_idx. */
+		user_mac->addr_bytes[0] = RTE_ETHER_LOCAL_ADMIN_ADDR; /* 0x2 */
+		user_mac->addr_bytes[1] = 'd';
+		user_mac->addr_bytes[2] = 't';
+		user_mac->addr_bytes[3] = 'a';
+		user_mac->addr_bytes[4] = 'p' + (iface_idx >> 8);
+		user_mac->addr_bytes[5] = '0' + (uint8_t)iface_idx++;
 		goto success;
 	}
 
-- 
2.51.0


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

* [PATCH v4 04/10] net/tap: skip checksum on truncated L4 headers
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
                     ` (2 preceding siblings ...)
  2026-02-20 17:02   ` [PATCH v4 03/10] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
@ 2026-02-20 17:02   ` Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 05/10] net/tap: fix resource leaks in tap create error path Stephen Hemminger
                     ` (5 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20 17:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a bounds check before accessing the UDP or TCP header in
tap_verify_csum(). A single-segment packet whose L4 header extends
past rte_pktmbuf_data_len() would cause an out-of-bounds read.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 8b6d5db37e..45ca32cfb8 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -365,8 +365,15 @@ tap_verify_csum(struct rte_mbuf *mbuf)
 		 */
 		return;
 	}
+
 	if (l4 == RTE_PTYPE_L4_UDP || l4 == RTE_PTYPE_L4_TCP) {
 		int cksum_ok;
+		const unsigned int l4_min_len = (l4 == RTE_PTYPE_L4_UDP)
+			? sizeof(struct rte_udp_hdr) : sizeof(struct rte_tcp_hdr);
+
+		/* Don't verify checksum if L4 header is truncated */
+		if (l2_len + l3_len + l4_min_len > rte_pktmbuf_data_len(mbuf))
+			return;
 
 		l4_hdr = rte_pktmbuf_mtod_offset(mbuf, void *, l2_len + l3_len);
 		/* Don't verify checksum for multi-segment packets. */
-- 
2.51.0


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

* [PATCH v4 05/10] net/tap: fix resource leaks in tap create error path
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
                     ` (3 preceding siblings ...)
  2026-02-20 17:02   ` [PATCH v4 04/10] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
@ 2026-02-20 17:02   ` Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 06/10] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
                     ` (4 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20 17:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

Correct two leaks in eth_dev_tap_create():

- process_private was never freed on error_exit. Add free() before
  releasing the port.
- If the process_private malloc failed, the function returned -1
  directly without releasing the ethdev port allocated by
  rte_eth_vdev_allocate(). Jump to a new error_exit_nodev_release
  label instead.

Bugzilla ID: 1881
Fixes: ed8132e7c912 ("net/tap: move fds of queues to be in process private")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 45ca32cfb8..9b38d1f50b 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2010,7 +2010,7 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	process_private = malloc(sizeof(struct pmd_process_private));
 	if (process_private == NULL) {
 		TAP_LOG(ERR, "Failed to alloc memory for process private");
-		return -1;
+		goto error_exit_nodev_release;
 	}
 	memset(process_private, 0, sizeof(struct pmd_process_private));
 
@@ -2200,9 +2200,12 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 		close(pmd->nlsk_fd);
 	if (pmd->ka_fd != -1)
 		close(pmd->ka_fd);
+	rte_intr_instance_free(pmd->intr_handle);
+	free(dev->process_private);
+
+error_exit_nodev_release:
 	/* mac_addrs must not be freed alone because part of dev_private */
 	dev->data->mac_addrs = NULL;
-	rte_intr_instance_free(pmd->intr_handle);
 	rte_eth_dev_release_port(dev);
 
 error_exit_nodev:
-- 
2.51.0


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

* [PATCH v4 06/10] net/tap: fix resource leaks in secondary process probe
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
                     ` (4 preceding siblings ...)
  2026-02-20 17:02   ` [PATCH v4 05/10] net/tap: fix resource leaks in tap create error path Stephen Hemminger
@ 2026-02-20 17:02   ` Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 07/10] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
                     ` (3 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20 17:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

Four error paths in the secondary-process branch of
rte_pmd_tap_probe() returned -1 without cleanup:

- primary process not alive: leaked eth_dev
- process_private malloc failure: leaked eth_dev
- tap_mp_attach_queues failure: leaked eth_dev and process_private
- rte_mp_action_register failure: leaked eth_dev and process_private

Add secondary_fail and secondary_fail_pp labels to free
process_private and release the ethdev port.

Bugzilla ID: 1881
Fixes: c9aa56edec8e ("net/tap: access primary process queues from secondary")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 9b38d1f50b..974b45ecad 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2502,31 +2502,38 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 		eth_dev->tx_pkt_burst = pmd_tx_burst;
 		if (!rte_eal_primary_proc_alive(NULL)) {
 			TAP_LOG(ERR, "Primary process is missing");
-			return -1;
+			goto secondary_fail;
 		}
 		eth_dev->process_private = malloc(sizeof(struct pmd_process_private));
 		if (eth_dev->process_private == NULL) {
 			TAP_LOG(ERR,
 				"Failed to alloc memory for process private");
-			return -1;
+			goto secondary_fail;
 		}
 		memset(eth_dev->process_private, 0, sizeof(struct pmd_process_private));
 
 		ret = tap_mp_attach_queues(name, eth_dev);
 		if (ret != 0)
-			return -1;
+			goto secondary_fail_pp;
 
 		if (!tap_devices_count) {
 			ret = rte_mp_action_register(TAP_MP_REQ_START_RXTX, tap_mp_req_start_rxtx);
 			if (ret < 0 && rte_errno != ENOTSUP) {
 				TAP_LOG(ERR, "tap: Failed to register IPC callback: %s",
 					strerror(rte_errno));
-				return -1;
+				goto secondary_fail_pp;
 			}
 		}
 		tap_devices_count++;
 		rte_eth_dev_probing_finish(eth_dev);
 		return 0;
+
+secondary_fail_pp:
+		free(eth_dev->process_private);
+		eth_dev->process_private = NULL;
+secondary_fail:
+		rte_eth_dev_release_port(eth_dev);
+		return -1;
 	}
 
 	/* use tap%d which causes kernel to choose next available */
-- 
2.51.0


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

* [PATCH v4 07/10] net/tap: free IPC reply buffer on queue count mismatch
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
                     ` (5 preceding siblings ...)
  2026-02-20 17:02   ` [PATCH v4 06/10] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
@ 2026-02-20 17:02   ` Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 08/10] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
                     ` (2 subsequent siblings)
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20 17:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

In tap_mp_attach_queues(), if the reply queue count does not match
the number of received file descriptors, the function returns -1
without freeing the reply buffer allocated by rte_mp_request_sync().
Add the missing free().

Bugzilla ID: 1881
Fixes: 9ad43ad8fbee ("net/tap: fix potential IPC buffer overrun")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 974b45ecad..deb1d72382 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2407,6 +2407,7 @@ tap_mp_attach_queues(const char *port_name, struct rte_eth_dev *dev)
 	/* Attach the queues from received file descriptors */
 	if (reply_param->q_count != reply->num_fds) {
 		TAP_LOG(ERR, "Unexpected number of fds received");
+		free(reply);
 		return -1;
 	}
 
-- 
2.51.0


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

* [PATCH v4 08/10] net/tap: fix use-after-free on remote flow creation failure
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
                     ` (6 preceding siblings ...)
  2026-02-20 17:02   ` [PATCH v4 07/10] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
@ 2026-02-20 17:02   ` Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 09/10] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 10/10] test: add unit tests for TAP PMD Stephen Hemminger
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20 17:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

After a local TC filter rule is installed and the flow is inserted
into pmd->flows, failure during remote flow creation jumps to the
fail label which frees the flow without removing it from the list
and without deleting the kernel-side TC rule.

Send RTM_DELTFILTER to clean up the local rule and call
LIST_REMOVE before freeing.

Bugzilla ID: 1881
Fixes: 2bc06869cd94 ("net/tap: add remote netdevice traffic capture")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 9d4ef27a8a..427faf75d5 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -1293,7 +1293,7 @@ tap_flow_create(struct rte_eth_dev *dev,
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
 				"cannot allocate memory for rte_flow");
-			goto fail;
+			goto fail_remove;
 		}
 		msg = &remote_flow->msg;
 		/* set the rule if_index for the remote netdevice */
@@ -1307,14 +1307,14 @@ tap_flow_create(struct rte_eth_dev *dev,
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "rte flow rule validation failed");
-			goto fail;
+			goto fail_remove;
 		}
 		err = tap_nl_send(pmd->nlsk_fd, &msg->nh);
 		if (err < 0) {
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "Failure sending nl request");
-			goto fail;
+			goto fail_remove;
 		}
 		err = tap_nl_recv_ack(pmd->nlsk_fd);
 		if (err < 0) {
@@ -1325,15 +1325,22 @@ tap_flow_create(struct rte_eth_dev *dev,
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL,
 				"overlapping rules or Kernel too old for flower support");
-			goto fail;
+			goto fail_remove;
 		}
 		flow->remote_flow = remote_flow;
 	}
 	return flow;
+
+fail_remove:
+	/* Delete the local TC rule that was already installed */
+	flow->msg.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	flow->msg.nh.nlmsg_type = RTM_DELTFILTER;
+	if (tap_nl_send(pmd->nlsk_fd, &flow->msg.nh) >= 0)
+		tap_nl_recv_ack(pmd->nlsk_fd);
+	LIST_REMOVE(flow, next);
 fail:
 	rte_free(remote_flow);
-	if (flow)
-		tap_flow_free(pmd, flow);
+	tap_flow_free(pmd, flow);
 	return NULL;
 }
 
-- 
2.51.0


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

* [PATCH v4 09/10] net/tap: free remote flow when implicit rule already exists
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
                     ` (7 preceding siblings ...)
  2026-02-20 17:02   ` [PATCH v4 08/10] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
@ 2026-02-20 17:02   ` Stephen Hemminger
  2026-02-20 17:02   ` [PATCH v4 10/10] test: add unit tests for TAP PMD Stephen Hemminger
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20 17:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

When tap_flow_implicit_create() gets EEXIST from the kernel, the
allocated remote_flow is never freed. Add rte_free() on that path.

Bugzilla ID: 1880
Fixes: 2ef1c0da894a ("net/tap: fix isolation mode toggling")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/tap_flow.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 427faf75d5..da1e70019a 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -1625,8 +1625,10 @@ int tap_flow_implicit_create(struct pmd_internals *pmd,
 	err = tap_nl_recv_ack(pmd->nlsk_fd);
 	if (err < 0) {
 		/* Silently ignore re-entering existing rule */
-		if (errno == EEXIST)
+		if (errno == EEXIST) {
+			rte_free(remote_flow);
 			goto success;
+		}
 		TAP_LOG(ERR,
 			"Kernel refused TC filter rule creation (%d): %s",
 			errno, strerror(errno));
-- 
2.51.0


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

* [PATCH v4 10/10] test: add unit tests for TAP PMD
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
                     ` (8 preceding siblings ...)
  2026-02-20 17:02   ` [PATCH v4 09/10] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
@ 2026-02-20 17:02   ` Stephen Hemminger
  9 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-20 17:02 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a standalone test suite for the TAP PMD, modeled on the existing
test_pmd_ring tests. Exercises device configuration, link status,
stats, MTU, MAC address, promiscuous/allmulticast modes, queue
start/stop, link up/down, device stop/start, and multi-queue setup.

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

diff --git a/app/test/meson.build b/app/test/meson.build
index 4fd8670e05..2689a23553 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -144,6 +144,7 @@ source_file_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'],
+    'test_pmd_tap.c': ['ethdev', 'net_tap', 'bus_vdev'],
     'test_pmu.c': ['pmu'],
     'test_power.c': ['power', 'power_acpi', 'power_kvm_vm', 'power_intel_pstate',
         'power_amd_pstate', 'power_cppc'],
diff --git a/app/test/test_pmd_tap.c b/app/test/test_pmd_tap.c
new file mode 100644
index 0000000000..4de28a77af
--- /dev/null
+++ b/app/test/test_pmd_tap.c
@@ -0,0 +1,907 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+/*
+ * Basic test of TAP device functionality
+ * based off of PMD ring test.
+ */
+
+#include "test.h"
+
+#include <stdio.h>
+
+#ifndef RTE_EXEC_ENV_LINUX
+
+/* TAP PMD is only available on Linux */
+static int
+test_pmd_tap(void)
+{
+	printf("TAP PMD not supported on this platform, skipping test\n");
+	return TEST_SKIPPED;
+}
+
+#else /* RTE_EXEC_ENV_LINUX */
+
+#include <string.h>
+#include <unistd.h>
+
+#include <rte_ethdev.h>
+#include <rte_bus_vdev.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+
+#define SOCKET0 0
+#define RING_SIZE 256
+#define NB_MBUF 2048
+#define MAX_PKT_BURST 32
+#define PKT_LEN 64
+
+static struct rte_mempool *mp;
+static int tap_port0 = -1;
+static int tap_port1 = -1;
+
+static int
+test_tap_ethdev_configure(int port)
+{
+	struct rte_eth_conf port_conf;
+	struct rte_eth_link link;
+	int ret;
+
+	memset(&port_conf, 0, sizeof(struct rte_eth_conf));
+
+	ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+	if (ret < 0) {
+		printf("Configure failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_tx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL);
+	if (ret < 0) {
+		printf("TX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, mp);
+	if (ret < 0) {
+		printf("RX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0) {
+		printf("Error starting port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_link_get(port, &link);
+	if (ret < 0) {
+		printf("Link get failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d: link status %s, speed %u Mbps\n",
+	       port,
+	       link.link_status ? "up" : "down",
+	       link.link_speed);
+
+	return 0;
+}
+
+static struct rte_mbuf *
+create_test_packet(struct rte_mempool *pool, uint16_t pkt_len)
+{
+	struct rte_mbuf *mbuf;
+	struct rte_ether_hdr *eth_hdr;
+	struct rte_ipv4_hdr *ip_hdr;
+	uint8_t *payload;
+	uint16_t i;
+
+	mbuf = rte_pktmbuf_alloc(pool);
+	if (mbuf == NULL) {
+		printf("%s(): mbuf alloc failed\n", __func__);
+		return NULL;
+	}
+
+	/* Ensure minimum packet size for Ethernet */
+	if (pkt_len < RTE_ETHER_MIN_LEN)
+		pkt_len = RTE_ETHER_MIN_LEN;
+
+	eth_hdr = (struct rte_ether_hdr *)rte_pktmbuf_append(mbuf, pkt_len);
+	if (eth_hdr == NULL) {
+		printf("%s(): append %u bytes failed\n", __func__, pkt_len);
+		rte_pktmbuf_free(mbuf);
+		return NULL;
+	}
+
+	/* Create Ethernet header */
+	eth_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
+	memset(&eth_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN); /* broadcast */
+	memset(&eth_hdr->src_addr, 0x02, RTE_ETHER_ADDR_LEN);
+	eth_hdr->src_addr.addr_bytes[5] = 0x01;
+	eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+	/* Create simple IPv4 header */
+	ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
+	memset(ip_hdr, 0, sizeof(*ip_hdr));
+	ip_hdr->version_ihl = 0x45; /* IPv4, 20 byte header */
+	ip_hdr->total_length = rte_cpu_to_be_16(pkt_len - sizeof(*eth_hdr));
+	ip_hdr->time_to_live = 64;
+	ip_hdr->next_proto_id = IPPROTO_UDP;
+	ip_hdr->src_addr = rte_cpu_to_be_32(0x0A000001); /* 10.0.0.1 */
+	ip_hdr->dst_addr = rte_cpu_to_be_32(0x0A000002); /* 10.0.0.2 */
+
+	/* Fill payload with pattern */
+	payload = (uint8_t *)(ip_hdr + 1);
+	for (i = 0; i < pkt_len - sizeof(*eth_hdr) - sizeof(*ip_hdr); i++)
+		payload[i] = (uint8_t)(i & 0xFF);
+
+	return mbuf;
+}
+
+static int
+test_tap_send_receive(void)
+{
+	struct rte_mbuf *tx_mbufs[MAX_PKT_BURST];
+	struct rte_mbuf *rx_mbufs[MAX_PKT_BURST];
+	uint16_t nb_tx, nb_rx;
+	int i;
+
+	printf("Testing TAP packet send and receive\n");
+
+	/* Create test packets */
+	for (i = 0; i < MAX_PKT_BURST / 2; i++) {
+		tx_mbufs[i] = create_test_packet(mp, PKT_LEN);
+		if (tx_mbufs[i] == NULL) {
+			printf("Failed to create test packet %d\n", i);
+			/* Free already allocated packets */
+			while (--i >= 0)
+				rte_pktmbuf_free(tx_mbufs[i]);
+			return TEST_FAILED;
+		}
+	}
+
+	/* Send packets */
+	nb_tx = rte_eth_tx_burst(tap_port0, 0, tx_mbufs, MAX_PKT_BURST / 2);
+	printf("Transmitted %u packets on port %d\n", nb_tx, tap_port0);
+
+	/* Free any unsent packets */
+	for (i = nb_tx; i < MAX_PKT_BURST / 2; i++)
+		rte_pktmbuf_free(tx_mbufs[i]);
+
+	if (nb_tx == 0) {
+		printf("Warning: No packets transmitted (this may be expected if interface is not up)\n");
+		return TEST_SUCCESS;
+	}
+
+	/* Small delay to allow packets to be processed */
+	usleep(10000);
+
+	/* Try to receive packets (note: TAP loopback depends on kernel config) */
+	nb_rx = rte_eth_rx_burst(tap_port0, 0, rx_mbufs, MAX_PKT_BURST);
+	printf("Received %u packets on port %d\n", nb_rx, tap_port0);
+
+	/* Free received packets */
+	for (i = 0; i < nb_rx; i++)
+		rte_pktmbuf_free(rx_mbufs[i]);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_stats_get(int port)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	printf("Testing TAP PMD stats_get for port %d\n", port);
+
+	ret = rte_eth_stats_get(port, &stats);
+	if (ret != 0) {
+		printf("Error: failed to get stats for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d stats:\n", port);
+	printf("  ipackets: %"PRIu64"\n", stats.ipackets);
+	printf("  opackets: %"PRIu64"\n", stats.opackets);
+	printf("  ibytes:   %"PRIu64"\n", stats.ibytes);
+	printf("  obytes:   %"PRIu64"\n", stats.obytes);
+	printf("  ierrors:  %"PRIu64"\n", stats.ierrors);
+	printf("  oerrors:  %"PRIu64"\n", stats.oerrors);
+
+	return 0;
+}
+
+static int
+test_tap_stats_reset(int port)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	printf("Testing TAP PMD stats_reset for port %d\n", port);
+
+	ret = rte_eth_stats_reset(port);
+	if (ret != 0) {
+		printf("Error: failed to reset stats for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_stats_get(port, &stats);
+	if (ret != 0) {
+		printf("Error: failed to get stats after reset for port %d\n",
+		       port);
+		return -1;
+	}
+
+	/* After reset, all stats should be zero */
+	if (stats.ipackets != 0 || stats.opackets != 0 ||
+	    stats.ibytes != 0 || stats.obytes != 0 ||
+	    stats.ierrors != 0 || stats.oerrors != 0) {
+		printf("Error: port %d stats are not zero after reset\n", port);
+		return -1;
+	}
+
+	printf("Stats reset successful for port %d\n", port);
+	return 0;
+}
+
+static int
+test_tap_link_status(int port)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	printf("Testing TAP PMD link status for port %d\n", port);
+
+	ret = rte_eth_link_get_nowait(port, &link);
+	if (ret < 0) {
+		printf("Error: failed to get link status for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d link: status=%s speed=%u duplex=%s\n",
+	       port,
+	       link.link_status ? "up" : "down",
+	       link.link_speed,
+	       link.link_duplex ? "full" : "half");
+
+	return 0;
+}
+
+static int
+test_tap_dev_info(int port)
+{
+	struct rte_eth_dev_info dev_info;
+	int ret;
+
+	printf("Testing TAP PMD dev_info for port %d\n", port);
+
+	ret = rte_eth_dev_info_get(port, &dev_info);
+	if (ret != 0) {
+		printf("Error: failed to get dev info for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d device info:\n", port);
+	printf("  driver_name: %s\n", dev_info.driver_name);
+	printf("  if_index: %u\n", dev_info.if_index);
+	printf("  max_rx_queues: %u\n", dev_info.max_rx_queues);
+	printf("  max_tx_queues: %u\n", dev_info.max_tx_queues);
+	printf("  max_rx_pktlen: %u\n", dev_info.max_rx_pktlen);
+
+	/* Verify this is indeed a TAP device */
+	if (strcmp(dev_info.driver_name, "net_tap") != 0 &&
+	    strcmp(dev_info.driver_name, "net_tun") != 0) {
+		printf("Warning: unexpected driver name: %s\n",
+		       dev_info.driver_name);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_mtu(int port)
+{
+	uint16_t mtu;
+	int ret;
+
+	printf("Testing TAP PMD MTU operations for port %d\n", port);
+
+	/* Get current MTU */
+	ret = rte_eth_dev_get_mtu(port, &mtu);
+	if (ret != 0) {
+		printf("Error: failed to get MTU for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Current MTU for port %d: %u\n", port, mtu);
+
+	/* Try to set a new MTU */
+	ret = rte_eth_dev_set_mtu(port, 1400);
+	if (ret != 0) {
+		printf("Warning: failed to set MTU to 1400 for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		/* Not a fatal error - may require privileges */
+	} else {
+		printf("MTU set to 1400 for port %d\n", port);
+
+		/* Restore original MTU */
+		ret = rte_eth_dev_set_mtu(port, mtu);
+		if (ret != 0)
+			printf("Warning: failed to restore MTU for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_mac_addr(int port)
+{
+	struct rte_ether_addr mac_addr;
+	int ret;
+
+	printf("Testing TAP PMD MAC address for port %d\n", port);
+
+	ret = rte_eth_macaddr_get(port, &mac_addr);
+	if (ret != 0) {
+		printf("Error: failed to get MAC address for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d MAC address: " RTE_ETHER_ADDR_PRT_FMT "\n",
+	       port, RTE_ETHER_ADDR_BYTES(&mac_addr));
+
+	return 0;
+}
+
+static int
+test_tap_promiscuous(int port)
+{
+	int ret;
+	int promisc_enabled;
+
+	printf("Testing TAP PMD promiscuous mode for port %d\n", port);
+
+	/* Get current promiscuous state */
+	promisc_enabled = rte_eth_promiscuous_get(port);
+	printf("Promiscuous mode initially %s for port %d\n",
+	       promisc_enabled ? "enabled" : "disabled", port);
+
+	/* Enable promiscuous mode */
+	ret = rte_eth_promiscuous_enable(port);
+	if (ret != 0) {
+		printf("Warning: failed to enable promiscuous mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_promiscuous_get(port) != 1) {
+			printf("Error: promiscuous mode not enabled after enable call\n");
+			return -1;
+		}
+		printf("Promiscuous mode enabled for port %d\n", port);
+	}
+
+	/* Disable promiscuous mode */
+	ret = rte_eth_promiscuous_disable(port);
+	if (ret != 0) {
+		printf("Warning: failed to disable promiscuous mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_promiscuous_get(port) != 0) {
+			printf("Error: promiscuous mode not disabled after disable call\n");
+			return -1;
+		}
+		printf("Promiscuous mode disabled for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_allmulti(int port)
+{
+	int ret;
+	int allmulti_enabled;
+
+	printf("Testing TAP PMD allmulticast mode for port %d\n", port);
+
+	/* Get current allmulticast state */
+	allmulti_enabled = rte_eth_allmulticast_get(port);
+	printf("Allmulticast mode initially %s for port %d\n",
+	       allmulti_enabled ? "enabled" : "disabled", port);
+
+	/* Enable allmulticast mode */
+	ret = rte_eth_allmulticast_enable(port);
+	if (ret != 0) {
+		printf("Warning: failed to enable allmulticast mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_allmulticast_get(port) != 1) {
+			printf("Error: allmulticast mode not enabled after enable call\n");
+			return -1;
+		}
+		printf("Allmulticast mode enabled for port %d\n", port);
+	}
+
+	/* Disable allmulticast mode */
+	ret = rte_eth_allmulticast_disable(port);
+	if (ret != 0) {
+		printf("Warning: failed to disable allmulticast mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_allmulticast_get(port) != 0) {
+			printf("Error: allmulticast mode not disabled after disable call\n");
+			return -1;
+		}
+		printf("Allmulticast mode disabled for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_queue_start_stop(int port)
+{
+	int ret;
+
+	printf("Testing TAP PMD queue start/stop for port %d\n", port);
+
+	/* Stop RX queue */
+	ret = rte_eth_dev_rx_queue_stop(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to stop RX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("RX queue stopped for port %d\n", port);
+
+	/* Stop TX queue */
+	ret = rte_eth_dev_tx_queue_stop(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to stop TX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("TX queue stopped for port %d\n", port);
+
+	/* Start RX queue */
+	ret = rte_eth_dev_rx_queue_start(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to start RX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("RX queue started for port %d\n", port);
+
+	/* Start TX queue */
+	ret = rte_eth_dev_tx_queue_start(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to start TX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("TX queue started for port %d\n", port);
+
+	return 0;
+}
+
+static int
+test_tap_link_up_down(int port)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	printf("Testing TAP PMD link up/down for port %d\n", port);
+
+	/* Set link down */
+	ret = rte_eth_dev_set_link_down(port);
+	if (ret != 0) {
+		printf("Warning: failed to set link down for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		ret = rte_eth_link_get_nowait(port, &link);
+		if (ret == 0)
+			printf("Link status after set_link_down: %s\n",
+			       link.link_status ? "up" : "down");
+	}
+
+	/* Set link up */
+	ret = rte_eth_dev_set_link_up(port);
+	if (ret != 0) {
+		printf("Warning: failed to set link up for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		ret = rte_eth_link_get_nowait(port, &link);
+		if (ret == 0)
+			printf("Link status after set_link_up: %s\n",
+			       link.link_status ? "up" : "down");
+	}
+
+	return 0;
+}
+
+static int
+test_tap_dev_stop_start(int port)
+{
+	int ret;
+
+	printf("Testing TAP PMD device stop/start for port %d\n", port);
+
+	/* Stop the device */
+	ret = rte_eth_dev_stop(port);
+	if (ret != 0) {
+		printf("Error: failed to stop port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Device stopped for port %d\n", port);
+
+	/* Start the device again */
+	ret = rte_eth_dev_start(port);
+	if (ret != 0) {
+		printf("Error: failed to start port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Device started for port %d\n", port);
+
+	return 0;
+}
+
+static int
+test_tap_multi_queue(void)
+{
+	struct rte_eth_conf port_conf = { 0 };
+	const uint16_t nb_queues = 2;
+
+	printf("Testing TAP PMD multi-queue configuration\n");
+
+	/* Create a separate mempool for multi-queue test */
+	struct rte_mempool *mq_mp
+		= rte_pktmbuf_pool_create("tap_mq_pool", NB_MBUF, 32, 0,
+					  RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+	if (mq_mp == NULL) {
+		printf("Warning: failed to create mempool for multi-queue test: %s\n",
+		       rte_strerror(rte_errno));
+		return TEST_SKIPPED;
+	}
+
+	/* Create a new TAP device for multi-queue test */
+	int ret = rte_vdev_init("net_tap_mq", "iface=dtap_mq");
+	if (ret < 0) {
+		printf("Warning: failed to create multi-queue TAP device: %s\n",
+		       rte_strerror(-ret));
+		rte_mempool_free(mq_mp);
+		return TEST_SKIPPED;
+	}
+
+	/* Find the port */
+	uint16_t port;
+	RTE_ETH_FOREACH_DEV(port) {
+		char name[RTE_ETH_NAME_MAX_LEN];
+		if (rte_eth_dev_get_name_by_port(port, name) == 0 &&
+		    strstr(name, "net_tap_mq"))
+			goto found;
+	}
+
+	printf("Error: could not find multi-queue TAP device\n");
+	rte_vdev_uninit("net_tap_mq");
+	rte_mempool_free(mq_mp);
+	return TEST_FAILED;
+
+found:
+	/* Configure with multiple queues */
+	ret = rte_eth_dev_configure(port, nb_queues, nb_queues, &port_conf);
+	if (ret < 0) {
+		printf("Warning: multi-queue configure failed: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_SKIPPED;
+	}
+
+	/* Setup TX queues */
+	for (uint16_t q = 0; q < nb_queues; q++) {
+		ret = rte_eth_tx_queue_setup(port, q, RING_SIZE, SOCKET0, NULL);
+		if (ret < 0) {
+			printf("Error: TX queue %u setup failed: %s\n",
+			       q, rte_strerror(-ret));
+			rte_vdev_uninit("net_tap_mq");
+			rte_mempool_free(mq_mp);
+			return TEST_FAILED;
+		}
+	}
+
+	/* Setup RX queues */
+	for (uint16_t q = 0; q < nb_queues; q++) {
+		ret = rte_eth_rx_queue_setup(port, q, RING_SIZE, SOCKET0, NULL, mq_mp);
+		if (ret < 0) {
+			printf("Error: RX queue %u setup failed: %s\n",
+			       q, rte_strerror(-ret));
+			rte_vdev_uninit("net_tap_mq");
+			rte_mempool_free(mq_mp);
+			return TEST_FAILED;
+		}
+	}
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0) {
+		printf("Error: failed to start multi-queue port: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_FAILED;
+	}
+
+	printf("Multi-queue TAP device configured with %u queues\n", nb_queues);
+
+	/* Cleanup */
+	ret = rte_eth_dev_stop(port);
+	if (ret != 0) {
+		printf("Error: rte_eth_dev_stop failed\n");
+		return TEST_FAILED;
+	}
+	rte_eth_dev_close(port);
+	rte_vdev_uninit("net_tap_mq");
+	rte_mempool_free(mq_mp);
+
+	return TEST_SUCCESS;
+}
+
+static void
+test_tap_cleanup(void)
+{
+	int ret;
+
+	printf("Cleaning up TAP PMD test resources\n");
+
+	if (tap_port0 >= 0) {
+		ret = rte_eth_dev_stop(tap_port0);
+		if (ret != 0)
+			printf("Warning: failed to stop port %d: %s\n",
+			       tap_port0, rte_strerror(-ret));
+		rte_eth_dev_close(tap_port0);
+	}
+
+	if (tap_port1 >= 0) {
+		ret = rte_eth_dev_stop(tap_port1);
+		if (ret != 0)
+			printf("Warning: failed to stop port %d: %s\n",
+			       tap_port1, rte_strerror(-ret));
+		rte_eth_dev_close(tap_port1);
+	}
+
+	rte_vdev_uninit("net_tap0");
+	rte_vdev_uninit("net_tap1");
+
+	if (mp != NULL) {
+		rte_mempool_free(mp);
+		mp = NULL;
+	}
+
+	tap_port0 = -1;
+	tap_port1 = -1;
+}
+
+static int
+test_tap_setup(void)
+{
+	int ret;
+	uint16_t port_id;
+
+	printf("Setting up TAP PMD test\n");
+
+	/* Create mempool */
+	mp = rte_pktmbuf_pool_create("tap_test_pool", NB_MBUF, 32, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+	if (mp == NULL) {
+		printf("Error: failed to create mempool: %s\n",
+		       rte_strerror(rte_errno));
+		return -1;
+	}
+
+	/* Create first TAP device */
+	ret = rte_vdev_init("net_tap0", "iface=dtap_test0");
+	if (ret < 0) {
+		printf("Error: failed to create TAP device net_tap0: %s\n",
+		       rte_strerror(-ret));
+		rte_mempool_free(mp);
+		mp = NULL;
+		return -1;
+	}
+
+	/* Create second TAP device */
+	ret = rte_vdev_init("net_tap1", "iface=dtap_test1");
+	if (ret < 0) {
+		printf("Error: failed to create TAP device net_tap1: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap0");
+		rte_mempool_free(mp);
+		mp = NULL;
+		return -1;
+	}
+
+	/* Find the port IDs */
+	RTE_ETH_FOREACH_DEV(port_id) {
+		char name[RTE_ETH_NAME_MAX_LEN];
+		if (rte_eth_dev_get_name_by_port(port_id, name) != 0)
+			continue;
+
+		if (strstr(name, "net_tap0"))
+			tap_port0 = port_id;
+		else if (strstr(name, "net_tap1"))
+			tap_port1 = port_id;
+	}
+
+	if (tap_port0 < 0 || tap_port1 < 0) {
+		printf("Error: failed to find TAP port IDs\n");
+		test_tap_cleanup();
+		return -1;
+	}
+
+	printf("Created TAP devices: tap_port0=%d, tap_port1=%d\n",
+	       tap_port0, tap_port1);
+
+	return 0;
+}
+
+/* Individual test case wrappers */
+
+static int
+test_tap_configure_port0(void)
+{
+	return test_tap_ethdev_configure(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_configure_port1(void)
+{
+	return test_tap_ethdev_configure(tap_port1) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_packet_send_receive(void)
+{
+	return test_tap_send_receive();
+}
+
+static int
+test_tap_get_stats(void)
+{
+	if (test_tap_stats_get(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_stats_get(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_reset_stats(void)
+{
+	if (test_tap_stats_reset(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_stats_reset(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_get_link_status(void)
+{
+	if (test_tap_link_status(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_link_status(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_get_dev_info(void)
+{
+	if (test_tap_dev_info(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_dev_info(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_mtu_ops(void)
+{
+	return test_tap_mtu(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_mac_addr_get(void)
+{
+	if (test_tap_mac_addr(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_mac_addr(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_promisc_mode(void)
+{
+	return test_tap_promiscuous(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_allmulti_mode(void)
+{
+	return test_tap_allmulti(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_queue_ops(void)
+{
+	return test_tap_queue_start_stop(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_link_ops(void)
+{
+	return test_tap_link_up_down(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_stop_start(void)
+{
+	return test_tap_dev_stop_start(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_multiqueue(void)
+{
+	return test_tap_multi_queue();
+}
+
+static struct unit_test_suite test_pmd_tap_suite = {
+	.setup = test_tap_setup,
+	.teardown = test_tap_cleanup,
+	.suite_name = "TAP PMD Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_tap_configure_port0),
+		TEST_CASE(test_tap_configure_port1),
+		TEST_CASE(test_tap_get_dev_info),
+		TEST_CASE(test_tap_get_link_status),
+		TEST_CASE(test_tap_mac_addr_get),
+		TEST_CASE(test_tap_get_stats),
+		TEST_CASE(test_tap_reset_stats),
+		TEST_CASE(test_tap_packet_send_receive),
+		TEST_CASE(test_tap_promisc_mode),
+		TEST_CASE(test_tap_allmulti_mode),
+		TEST_CASE(test_tap_mtu_ops),
+		TEST_CASE(test_tap_queue_ops),
+		TEST_CASE(test_tap_link_ops),
+		TEST_CASE(test_tap_stop_start),
+		TEST_CASE(test_tap_multiqueue),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_pmd_tap(void)
+{
+	return unit_test_suite_runner(&test_pmd_tap_suite);
+}
+
+#endif /* RTE_EXEC_ENV_LINUX */
+
+REGISTER_FAST_TEST(tap_pmd_autotest, NOHUGE_OK, ASAN_OK, test_pmd_tap);
-- 
2.51.0


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

* [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal
  2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
                   ` (13 preceding siblings ...)
  2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
@ 2026-02-22 17:30 ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 01/19] net/tap: fix handling of queue stats Stephen Hemminger
                     ` (19 more replies)
  14 siblings, 20 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Patches 1-2 are documentation and bug fixes that stand alone: update
the features matrix to match current driver capabilities, and fix
queue statistics to count all queues regardless of the queue stat
counter limit.

Patch 3 fixes interface name buffers to use IFNAMSIZ instead of
RTE_ETH_NAME_MAX_LEN, since these are Linux kernel interface names
not DPDK device names.

Patches 4-6 are minor cleanups: replace the runtime speed capability
function with a compile-time constant (TAP is always 10G), clarify
TUN/TAP flag operator precedence with parentheses, and extend the
fixed MAC index to 16 bits to avoid duplicates after 256 hot-plug
cycles.

Patches 7-12 fix bugs tagged for stable: a bounds check to prevent
an out-of-bounds read on truncated L4 headers; resource leaks in
the primary and secondary process probe error paths; a missing free
of the IPC reply buffer on queue count mismatch; a use-after-free
with an orphaned kernel TC rule when remote flow creation fails; and
a leaked remote_flow allocation on EEXIST from an implicit rule.

Patches 13-18 restructure the driver internals: dynamically allocate
queue structures instead of embedding fixed-size arrays in
pmd_internals; replace the pointer-to-VLA iovec with a flexible
array member sized from MTU and mbuf geometry; replace the Tx
per-packet VLA with a fixed-size stack array capped at 128 segments;
remove the VLA in flow item validation; consolidate per-queue
statistics into a common structure; and enable -Wvla warnings.

Patch 19 adds a unit test suite for the TAP PMD covering
configuration, link, stats, MTU, MAC, promisc, allmulti, queue
start/stop, link up/down, stop/start, and multi-queue.

v5 - use IFNAMSIZ for interface name buffers
   - dynamically allocate queue structures
   - compute Rx scatter segments from MTU instead of nb_rx_desc
   - use flex array for Rx iovecs, fixed stack array for Tx
   - consolidate queue statistics


Stephen Hemminger (19):
  net/tap: fix handling of queue stats
  doc: update tap features
  net/tap: use correct length for interface names
  net/tap: replace runtime speed capability with constant
  net/tap: clarify TUN/TAP flag assignment
  net/tap: extend fixed MAC range to 16 bits
  net/tap: skip checksum on truncated L4 headers
  net/tap: fix resource leaks in tap create error path
  net/tap: fix resource leaks in secondary process probe
  net/tap: free IPC reply buffer on queue count mismatch
  net/tap: fix use-after-free on remote flow creation failure
  net/tap: free remote flow when implicit rule already exists
  net/tap: dynamically allocate queue structures
  net/tap: remove VLA in flow item validation
  net/tap: fix Rx descriptor vs scatter segment confusion
  net/tap: replace use of VLA in transmit burst
  net/tap: consolidate queue statistics
  net/tap: enable VLA warnings
  test: add unit tests for TAP PMD

 app/test/meson.build             |    1 +
 app/test/test_pmd_tap.c          | 1143 ++++++++++++++++++++++++++++++
 doc/guides/nics/features/tap.ini |   14 +-
 drivers/net/tap/meson.build      |    1 -
 drivers/net/tap/rte_eth_tap.c    |  411 ++++++-----
 drivers/net/tap/rte_eth_tap.h    |   28 +-
 drivers/net/tap/tap_flow.c       |   36 +-
 7 files changed, 1424 insertions(+), 210 deletions(-)
 create mode 100644 app/test/test_pmd_tap.c

-- 
2.51.0


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

* [PATCH v5 01/19] net/tap: fix handling of queue stats
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 02/19] doc: update tap features Stephen Hemminger
                     ` (18 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

Drivers are supposed to account for packets in all queues.
If RTE_ETHDEV_QUEUE_STAT_CNTRS is configured to a small value,
the TAP PMD would skip counting some queues in the accumulated totals.

Also, use memset to clear stats since that is more future proof
if new stats are added.

Fixes: f46900d03823 ("net/tap: fix flow and port commands")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 7a8a98cddb..8b233a3143 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -956,17 +956,15 @@ static int
 tap_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *tap_stats,
 	      struct eth_queue_stats *qstats)
 {
-	unsigned int i, imax;
-	unsigned long rx_total = 0, tx_total = 0, tx_err_total = 0;
-	unsigned long rx_bytes_total = 0, tx_bytes_total = 0;
-	unsigned long rx_nombuf = 0, ierrors = 0;
+	unsigned int i;
+	uint64_t rx_total = 0, tx_total = 0, tx_err_total = 0;
+	uint64_t rx_bytes_total = 0, tx_bytes_total = 0;
+	uint64_t rx_nombuf = 0, ierrors = 0;
 	const struct pmd_internals *pmd = dev->data->dev_private;
 
 	/* rx queue statistics */
-	imax = (dev->data->nb_rx_queues < RTE_ETHDEV_QUEUE_STAT_CNTRS) ?
-		dev->data->nb_rx_queues : RTE_ETHDEV_QUEUE_STAT_CNTRS;
-	for (i = 0; i < imax; i++) {
-		if (qstats != NULL) {
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		if (qstats != NULL && i < RTE_ETHDEV_QUEUE_STAT_CNTRS) {
 			qstats->q_ipackets[i] = pmd->rxq[i].stats.ipackets;
 			qstats->q_ibytes[i] = pmd->rxq[i].stats.ibytes;
 		}
@@ -977,11 +975,8 @@ tap_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *tap_stats,
 	}
 
 	/* tx queue statistics */
-	imax = (dev->data->nb_tx_queues < RTE_ETHDEV_QUEUE_STAT_CNTRS) ?
-		dev->data->nb_tx_queues : RTE_ETHDEV_QUEUE_STAT_CNTRS;
-
-	for (i = 0; i < imax; i++) {
-		if (qstats != NULL) {
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		if (qstats != NULL && i < RTE_ETHDEV_QUEUE_STAT_CNTRS) {
 			qstats->q_opackets[i] = pmd->txq[i].stats.opackets;
 			qstats->q_obytes[i] = pmd->txq[i].stats.obytes;
 		}
@@ -1003,18 +998,12 @@ tap_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *tap_stats,
 static int
 tap_stats_reset(struct rte_eth_dev *dev)
 {
-	int i;
+	unsigned int i;
 	struct pmd_internals *pmd = dev->data->dev_private;
 
 	for (i = 0; i < RTE_PMD_TAP_MAX_QUEUES; i++) {
-		pmd->rxq[i].stats.ipackets = 0;
-		pmd->rxq[i].stats.ibytes = 0;
-		pmd->rxq[i].stats.ierrors = 0;
-		pmd->rxq[i].stats.rx_nombuf = 0;
-
-		pmd->txq[i].stats.opackets = 0;
-		pmd->txq[i].stats.errs = 0;
-		pmd->txq[i].stats.obytes = 0;
+		memset(&pmd->rxq[i].stats, 0, sizeof(struct pkt_stats));
+		memset(&pmd->txq[i].stats, 0, sizeof(struct pkt_stats));
 	}
 
 	return 0;
-- 
2.51.0


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

* [PATCH v5 02/19] doc: update tap features
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 01/19] net/tap: fix handling of queue stats Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 03/19] net/tap: use correct length for interface names Stephen Hemminger
                     ` (17 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The features matrix for TAP PMD did not match what is currently
available in the driver.

Also, reorder to match the order in default.ini

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 doc/guides/nics/features/tap.ini | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/doc/guides/nics/features/tap.ini b/doc/guides/nics/features/tap.ini
index f26355e57f..fd04e3bbd6 100644
--- a/doc/guides/nics/features/tap.ini
+++ b/doc/guides/nics/features/tap.ini
@@ -8,16 +8,20 @@ Speed capabilities   = P
 Link status          = Y
 Link status event    = Y
 Rx interrupt         = Y
+Queue start/stop     = Y
+MTU update           = Y
+Scattered Rx         = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
-Basic stats          = Y
+Unicast MAC filter   = P
+Multicast MAC filter = Y
+RSS hash             = P
+Flow control         = P
 L3 checksum offload  = Y
 L4 checksum offload  = Y
-MTU update           = Y
-Multicast MAC filter = Y
-Unicast MAC filter   = Y
 Packet type parsing  = Y
-Flow control         = Y
+Basic stats          = Y
+Multiprocess aware   = Y
 Linux                = Y
 ARMv7                = Y
 ARMv8                = Y
-- 
2.51.0


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

* [PATCH v5 03/19] net/tap: use correct length for interface names
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 01/19] net/tap: fix handling of queue stats Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 02/19] doc: update tap features Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 04/19] net/tap: replace runtime speed capability with constant Stephen Hemminger
                     ` (16 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

The interface names in the driver internals are Linux
kernel network interface names which have a maximum size of 16;
not DPDK device names which can take up to 64 characters.

Fixes: 02f96a0a82d1 ("net/tap: add TUN/TAP device PMD")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 28 ++++++++++++++--------------
 drivers/net/tap/rte_eth_tap.h |  4 ++--
 2 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 8b233a3143..08d51c3a45 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -203,7 +203,7 @@ tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
 	 * and need to find the resulting device.
 	 */
 	TAP_LOG(DEBUG, "Device name is '%s'", ifr.ifr_name);
-	strlcpy(pmd->name, ifr.ifr_name, RTE_ETH_NAME_MAX_LEN);
+	strlcpy(pmd->name, ifr.ifr_name, IFNAMSIZ);
 
 	if (is_keepalive) {
 		/*
@@ -1994,7 +1994,7 @@ static const struct eth_dev_ops ops = {
 
 static int
 eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
-		   char *remote_iface, struct rte_ether_addr *mac_addr,
+		   const char *remote_iface, struct rte_ether_addr *mac_addr,
 		   enum rte_tuntap_type type, int persist)
 {
 	int numa_node = rte_socket_id();
@@ -2137,7 +2137,7 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 				pmd->name, remote_iface);
 			goto error_remote;
 		}
-		strlcpy(pmd->remote_iface, remote_iface, RTE_ETH_NAME_MAX_LEN);
+		strlcpy(pmd->remote_iface, remote_iface, IFNAMSIZ);
 
 		/* Save state of remote device */
 		if (tap_nl_get_flags(pmd->nlsk_fd, pmd->remote_if_index,
@@ -2250,10 +2250,10 @@ set_interface_name(const char *key __rte_unused,
 				value);
 			return -1;
 		}
-		strlcpy(name, value, RTE_ETH_NAME_MAX_LEN);
+		strlcpy(name, value, IFNAMSIZ);
 	} else {
 		/* use tap%d which causes kernel to choose next available */
-		strlcpy(name, DEFAULT_TAP_NAME "%d", RTE_ETH_NAME_MAX_LEN);
+		strlcpy(name, DEFAULT_TAP_NAME "%d", IFNAMSIZ);
 	}
 	return 0;
 }
@@ -2271,7 +2271,7 @@ set_remote_iface(const char *key __rte_unused,
 				value);
 			return -1;
 		}
-		strlcpy(name, value, RTE_ETH_NAME_MAX_LEN);
+		strlcpy(name, value, IFNAMSIZ);
 	}
 
 	return 0;
@@ -2322,13 +2322,13 @@ rte_pmd_tun_probe(struct rte_vdev_device *dev)
 	const char *name, *params;
 	int ret;
 	struct rte_kvargs *kvlist = NULL;
-	char tun_name[RTE_ETH_NAME_MAX_LEN];
-	char remote_iface[RTE_ETH_NAME_MAX_LEN];
+	char tun_name[IFNAMSIZ];
+	char remote_iface[IFNAMSIZ];
 	struct rte_eth_dev *eth_dev;
 
 	name = rte_vdev_device_name(dev);
 	params = rte_vdev_device_args(dev);
-	memset(remote_iface, 0, RTE_ETH_NAME_MAX_LEN);
+	memset(remote_iface, 0, IFNAMSIZ);
 
 	if (rte_eal_process_type() == RTE_PROC_SECONDARY &&
 	    strlen(params) == 0) {
@@ -2344,7 +2344,7 @@ rte_pmd_tun_probe(struct rte_vdev_device *dev)
 	}
 
 	/* use tun%d which causes kernel to choose next available */
-	strlcpy(tun_name, DEFAULT_TUN_NAME "%d", RTE_ETH_NAME_MAX_LEN);
+	strlcpy(tun_name, DEFAULT_TUN_NAME "%d", IFNAMSIZ);
 
 	if (params && (params[0] != '\0')) {
 		TAP_LOG(DEBUG, "parameters (%s)", params);
@@ -2485,8 +2485,8 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 	int ret;
 	struct rte_kvargs *kvlist = NULL;
 	int speed;
-	char tap_name[RTE_ETH_NAME_MAX_LEN];
-	char remote_iface[RTE_ETH_NAME_MAX_LEN];
+	char tap_name[IFNAMSIZ];
+	char remote_iface[IFNAMSIZ];
 	struct rte_ether_addr user_mac = { .addr_bytes = {0} };
 	struct rte_eth_dev *eth_dev;
 	int tap_devices_count_increased = 0;
@@ -2537,8 +2537,8 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 	speed = RTE_ETH_SPEED_NUM_10G;
 
 	/* use tap%d which causes kernel to choose next available */
-	strlcpy(tap_name, DEFAULT_TAP_NAME "%d", RTE_ETH_NAME_MAX_LEN);
-	memset(remote_iface, 0, RTE_ETH_NAME_MAX_LEN);
+	strlcpy(tap_name, DEFAULT_TAP_NAME "%d", IFNAMSIZ);
+	memset(remote_iface, 0, IFNAMSIZ);
 
 	if (params && (params[0] != '\0')) {
 		TAP_LOG(DEBUG, "parameters (%s)", params);
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index 218ee1b811..7be8f4d01b 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -68,8 +68,8 @@ struct tx_queue {
 
 struct pmd_internals {
 	struct rte_eth_dev *dev;          /* Ethernet device. */
-	char remote_iface[RTE_ETH_NAME_MAX_LEN]; /* Remote netdevice name */
-	char name[RTE_ETH_NAME_MAX_LEN];  /* Internal Tap device name */
+	char remote_iface[IFNAMSIZ];	  /* Remote netdevice name */
+	char name[IFNAMSIZ];		  /* Internal Tap device name */
 	int type;                         /* Type field - TUN|TAP */
 	int persist;			  /* 1 if keep link up, else 0 */
 	struct rte_ether_addr eth_addr;   /* Mac address of the device port */
-- 
2.51.0


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

* [PATCH v5 04/19] net/tap: replace runtime speed capability with constant
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (2 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 03/19] net/tap: use correct length for interface names Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 05/19] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
                     ` (15 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The TAP/TUN virtual device always operates at 10G. The link speed is
set in the static initializer for pmd_link, so the runtime assignments
in rte_pmd_tap_probe() and rte_pmd_tun_probe() are redundant.

Replace tap_dev_speed_capa(), which dynamically computed speed
capabilities against pmd_link.link_speed, with a compile-time
constant TAP_SPEED_CAPA. Remove the unused 'speed' variable and
redundant assignments.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 08d51c3a45..9dac0eced9 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -891,39 +891,13 @@ tap_dev_configure(struct rte_eth_dev *dev)
 	return 0;
 }
 
-static uint32_t
-tap_dev_speed_capa(void)
-{
-	uint32_t speed = pmd_link.link_speed;
-	uint32_t capa = 0;
-
-	if (speed >= RTE_ETH_SPEED_NUM_10M)
-		capa |= RTE_ETH_LINK_SPEED_10M;
-	if (speed >= RTE_ETH_SPEED_NUM_100M)
-		capa |= RTE_ETH_LINK_SPEED_100M;
-	if (speed >= RTE_ETH_SPEED_NUM_1G)
-		capa |= RTE_ETH_LINK_SPEED_1G;
-	if (speed >= RTE_ETH_SPEED_NUM_5G)
-		capa |= RTE_ETH_LINK_SPEED_2_5G;
-	if (speed >= RTE_ETH_SPEED_NUM_5G)
-		capa |= RTE_ETH_LINK_SPEED_5G;
-	if (speed >= RTE_ETH_SPEED_NUM_10G)
-		capa |= RTE_ETH_LINK_SPEED_10G;
-	if (speed >= RTE_ETH_SPEED_NUM_20G)
-		capa |= RTE_ETH_LINK_SPEED_20G;
-	if (speed >= RTE_ETH_SPEED_NUM_25G)
-		capa |= RTE_ETH_LINK_SPEED_25G;
-	if (speed >= RTE_ETH_SPEED_NUM_40G)
-		capa |= RTE_ETH_LINK_SPEED_40G;
-	if (speed >= RTE_ETH_SPEED_NUM_50G)
-		capa |= RTE_ETH_LINK_SPEED_50G;
-	if (speed >= RTE_ETH_SPEED_NUM_56G)
-		capa |= RTE_ETH_LINK_SPEED_56G;
-	if (speed >= RTE_ETH_SPEED_NUM_100G)
-		capa |= RTE_ETH_LINK_SPEED_100G;
-
-	return capa;
-}
+/* Speed capabilities for virtual TAP/TUN device (always 10G) */
+#define TAP_SPEED_CAPA (RTE_ETH_LINK_SPEED_10M | \
+			RTE_ETH_LINK_SPEED_100M | \
+			RTE_ETH_LINK_SPEED_1G | \
+			RTE_ETH_LINK_SPEED_2_5G | \
+			RTE_ETH_LINK_SPEED_5G | \
+			RTE_ETH_LINK_SPEED_10G)
 
 static int
 tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
@@ -936,7 +910,7 @@ tap_dev_info(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info)
 	dev_info->max_rx_queues = RTE_PMD_TAP_MAX_QUEUES;
 	dev_info->max_tx_queues = RTE_PMD_TAP_MAX_QUEUES;
 	dev_info->min_rx_bufsize = 0;
-	dev_info->speed_capa = tap_dev_speed_capa();
+	dev_info->speed_capa = TAP_SPEED_CAPA;
 	dev_info->rx_queue_offload_capa = TAP_RX_OFFLOAD;
 	dev_info->rx_offload_capa = dev_info->rx_queue_offload_capa;
 	dev_info->tx_queue_offload_capa = TAP_TX_OFFLOAD;
@@ -2314,7 +2288,6 @@ set_mac_type(const char *key __rte_unused,
  * Open a TUN interface device. TUN PMD
  * 1) sets tap_type as false
  * 2) intakes iface as argument.
- * 3) as interface is virtual set speed to 10G
  */
 static int
 rte_pmd_tun_probe(struct rte_vdev_device *dev)
@@ -2362,7 +2335,6 @@ rte_pmd_tun_probe(struct rte_vdev_device *dev)
 			}
 		}
 	}
-	pmd_link.link_speed = RTE_ETH_SPEED_NUM_10G;
 
 	TAP_LOG(DEBUG, "Initializing pmd_tun for %s", name);
 
@@ -2484,7 +2456,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 	const char *name, *params;
 	int ret;
 	struct rte_kvargs *kvlist = NULL;
-	int speed;
 	char tap_name[IFNAMSIZ];
 	char remote_iface[IFNAMSIZ];
 	struct rte_ether_addr user_mac = { .addr_bytes = {0} };
@@ -2534,8 +2505,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 		return 0;
 	}
 
-	speed = RTE_ETH_SPEED_NUM_10G;
-
 	/* use tap%d which causes kernel to choose next available */
 	strlcpy(tap_name, DEFAULT_TAP_NAME "%d", IFNAMSIZ);
 	memset(remote_iface, 0, IFNAMSIZ);
@@ -2576,7 +2545,6 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 				persist = 1;
 		}
 	}
-	pmd_link.link_speed = speed;
 
 	TAP_LOG(DEBUG, "Initializing pmd_tap for %s", name);
 
-- 
2.51.0


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

* [PATCH v5 05/19] net/tap: clarify TUN/TAP flag assignment
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (3 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 04/19] net/tap: replace runtime speed capability with constant Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 06/19] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
                     ` (14 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add parentheses in the ternary expression that relied on operator
precedence between '?:' and '|'. No functional change.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 9dac0eced9..291c3b5a9d 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -155,7 +155,8 @@ tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
 	 * to check if a received packet has been truncated.
 	 */
 	ifr.ifr_flags = (pmd->type == ETH_TUNTAP_TYPE_TAP) ?
-		IFF_TAP : IFF_TUN | IFF_POINTOPOINT;
+		IFF_TAP : (IFF_TUN | IFF_POINTOPOINT);
+
 	strlcpy(ifr.ifr_name, pmd->name, IFNAMSIZ);
 
 	fd = open(TUN_TAP_DEV_PATH, O_RDWR);
-- 
2.51.0


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

* [PATCH v5 06/19] net/tap: extend fixed MAC range to 16 bits
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (4 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 05/19] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 07/19] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
                     ` (13 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The generated fixed MAC address stored the interface index in a single
byte, which wraps after 256 devices and produces duplicate MACs under
repeated hot-plug. Spread the index across the last two bytes of the
address, extending the unique range to 65536.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 291c3b5a9d..47dad54465 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2263,13 +2263,15 @@ set_mac_type(const char *key __rte_unused,
 		return 0;
 
 	if (!strncasecmp(ETH_TAP_MAC_FIXED, value, strlen(ETH_TAP_MAC_FIXED))) {
-		static int iface_idx;
-
-		/* fixed mac = 02:64:74:61:70:<iface_idx> */
-		memcpy((char *)user_mac->addr_bytes, "\002dtap",
-			RTE_ETHER_ADDR_LEN);
-		user_mac->addr_bytes[RTE_ETHER_ADDR_LEN - 1] =
-			iface_idx++ + '0';
+		static uint16_t iface_idx;
+
+		/* fixed mac that is locally assigned based off of iface_idx. */
+		user_mac->addr_bytes[0] = RTE_ETHER_LOCAL_ADMIN_ADDR; /* 0x2 */
+		user_mac->addr_bytes[1] = 'd';
+		user_mac->addr_bytes[2] = 't';
+		user_mac->addr_bytes[3] = 'a';
+		user_mac->addr_bytes[4] = 'p' + (iface_idx >> 8);
+		user_mac->addr_bytes[5] = '0' + (uint8_t)iface_idx++;
 		goto success;
 	}
 
-- 
2.51.0


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

* [PATCH v5 07/19] net/tap: skip checksum on truncated L4 headers
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (5 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 06/19] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 08/19] net/tap: fix resource leaks in tap create error path Stephen Hemminger
                     ` (12 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a bounds check before accessing the UDP or TCP header in
tap_verify_csum(). A single-segment packet whose L4 header extends
past rte_pktmbuf_data_len() would cause an out-of-bounds read.

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 47dad54465..c810b0df72 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -365,8 +365,15 @@ tap_verify_csum(struct rte_mbuf *mbuf)
 		 */
 		return;
 	}
+
 	if (l4 == RTE_PTYPE_L4_UDP || l4 == RTE_PTYPE_L4_TCP) {
 		int cksum_ok;
+		const unsigned int l4_min_len = (l4 == RTE_PTYPE_L4_UDP)
+			? sizeof(struct rte_udp_hdr) : sizeof(struct rte_tcp_hdr);
+
+		/* Don't verify checksum if L4 header is truncated */
+		if (l2_len + l3_len + l4_min_len > rte_pktmbuf_data_len(mbuf))
+			return;
 
 		l4_hdr = rte_pktmbuf_mtod_offset(mbuf, void *, l2_len + l3_len);
 		/* Don't verify checksum for multi-segment packets. */
-- 
2.51.0


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

* [PATCH v5 08/19] net/tap: fix resource leaks in tap create error path
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (6 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 07/19] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 09/19] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
                     ` (11 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

Correct two leaks in eth_dev_tap_create():

- process_private was never freed on error_exit. Add free() before
  releasing the port.
- If the process_private malloc failed, the function returned -1
  directly without releasing the ethdev port allocated by
  rte_eth_vdev_allocate(). Jump to a new error_exit_nodev_release
  label instead.

Bugzilla ID: 1881
Fixes: ed8132e7c912 ("net/tap: move fds of queues to be in process private")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index c810b0df72..acae3b6d76 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -1999,7 +1999,7 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	process_private = malloc(sizeof(struct pmd_process_private));
 	if (process_private == NULL) {
 		TAP_LOG(ERR, "Failed to alloc memory for process private");
-		return -1;
+		goto error_exit_nodev_release;
 	}
 	memset(process_private, 0, sizeof(struct pmd_process_private));
 
@@ -2189,9 +2189,12 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 		close(pmd->nlsk_fd);
 	if (pmd->ka_fd != -1)
 		close(pmd->ka_fd);
+	rte_intr_instance_free(pmd->intr_handle);
+	free(dev->process_private);
+
+error_exit_nodev_release:
 	/* mac_addrs must not be freed alone because part of dev_private */
 	dev->data->mac_addrs = NULL;
-	rte_intr_instance_free(pmd->intr_handle);
 	rte_eth_dev_release_port(dev);
 
 error_exit_nodev:
-- 
2.51.0


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

* [PATCH v5 09/19] net/tap: fix resource leaks in secondary process probe
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (7 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 08/19] net/tap: fix resource leaks in tap create error path Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 10/19] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
                     ` (10 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

Four error paths in the secondary-process branch of
rte_pmd_tap_probe() returned -1 without cleanup:

- primary process not alive: leaked eth_dev
- process_private malloc failure: leaked eth_dev
- tap_mp_attach_queues failure: leaked eth_dev and process_private
- rte_mp_action_register failure: leaked eth_dev and process_private

Add secondary_fail and secondary_fail_pp labels to free
process_private and release the ethdev port.

Bugzilla ID: 1881
Fixes: c9aa56edec8e ("net/tap: access primary process queues from secondary")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index acae3b6d76..2707d63f29 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2491,31 +2491,38 @@ rte_pmd_tap_probe(struct rte_vdev_device *dev)
 		eth_dev->tx_pkt_burst = pmd_tx_burst;
 		if (!rte_eal_primary_proc_alive(NULL)) {
 			TAP_LOG(ERR, "Primary process is missing");
-			return -1;
+			goto secondary_fail;
 		}
 		eth_dev->process_private = malloc(sizeof(struct pmd_process_private));
 		if (eth_dev->process_private == NULL) {
 			TAP_LOG(ERR,
 				"Failed to alloc memory for process private");
-			return -1;
+			goto secondary_fail;
 		}
 		memset(eth_dev->process_private, 0, sizeof(struct pmd_process_private));
 
 		ret = tap_mp_attach_queues(name, eth_dev);
 		if (ret != 0)
-			return -1;
+			goto secondary_fail_pp;
 
 		if (!tap_devices_count) {
 			ret = rte_mp_action_register(TAP_MP_REQ_START_RXTX, tap_mp_req_start_rxtx);
 			if (ret < 0 && rte_errno != ENOTSUP) {
 				TAP_LOG(ERR, "tap: Failed to register IPC callback: %s",
 					strerror(rte_errno));
-				return -1;
+				goto secondary_fail_pp;
 			}
 		}
 		tap_devices_count++;
 		rte_eth_dev_probing_finish(eth_dev);
 		return 0;
+
+secondary_fail_pp:
+		free(eth_dev->process_private);
+		eth_dev->process_private = NULL;
+secondary_fail:
+		rte_eth_dev_release_port(eth_dev);
+		return -1;
 	}
 
 	/* use tap%d which causes kernel to choose next available */
-- 
2.51.0


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

* [PATCH v5 10/19] net/tap: free IPC reply buffer on queue count mismatch
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (8 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 09/19] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 11/19] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
                     ` (9 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

In tap_mp_attach_queues(), if the reply queue count does not match
the number of received file descriptors, the function returns -1
without freeing the reply buffer allocated by rte_mp_request_sync().
Add the missing free().

Bugzilla ID: 1881
Fixes: 9ad43ad8fbee ("net/tap: fix potential IPC buffer overrun")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 2707d63f29..6640ac1596 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -2396,6 +2396,7 @@ tap_mp_attach_queues(const char *port_name, struct rte_eth_dev *dev)
 	/* Attach the queues from received file descriptors */
 	if (reply_param->q_count != reply->num_fds) {
 		TAP_LOG(ERR, "Unexpected number of fds received");
+		free(reply);
 		return -1;
 	}
 
-- 
2.51.0


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

* [PATCH v5 11/19] net/tap: fix use-after-free on remote flow creation failure
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (9 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 10/19] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 12/19] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
                     ` (8 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

After a local TC filter rule is installed and the flow is inserted
into pmd->flows, failure during remote flow creation jumps to the
fail label which frees the flow without removing it from the list
and without deleting the kernel-side TC rule.

Send RTM_DELTFILTER to clean up the local rule and call
LIST_REMOVE before freeing.

Bugzilla ID: 1881
Fixes: 2bc06869cd94 ("net/tap: add remote netdevice traffic capture")
Cc: stable@dpdk.org

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

diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 9d4ef27a8a..427faf75d5 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -1293,7 +1293,7 @@ tap_flow_create(struct rte_eth_dev *dev,
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
 				"cannot allocate memory for rte_flow");
-			goto fail;
+			goto fail_remove;
 		}
 		msg = &remote_flow->msg;
 		/* set the rule if_index for the remote netdevice */
@@ -1307,14 +1307,14 @@ tap_flow_create(struct rte_eth_dev *dev,
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "rte flow rule validation failed");
-			goto fail;
+			goto fail_remove;
 		}
 		err = tap_nl_send(pmd->nlsk_fd, &msg->nh);
 		if (err < 0) {
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "Failure sending nl request");
-			goto fail;
+			goto fail_remove;
 		}
 		err = tap_nl_recv_ack(pmd->nlsk_fd);
 		if (err < 0) {
@@ -1325,15 +1325,22 @@ tap_flow_create(struct rte_eth_dev *dev,
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL,
 				"overlapping rules or Kernel too old for flower support");
-			goto fail;
+			goto fail_remove;
 		}
 		flow->remote_flow = remote_flow;
 	}
 	return flow;
+
+fail_remove:
+	/* Delete the local TC rule that was already installed */
+	flow->msg.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	flow->msg.nh.nlmsg_type = RTM_DELTFILTER;
+	if (tap_nl_send(pmd->nlsk_fd, &flow->msg.nh) >= 0)
+		tap_nl_recv_ack(pmd->nlsk_fd);
+	LIST_REMOVE(flow, next);
 fail:
 	rte_free(remote_flow);
-	if (flow)
-		tap_flow_free(pmd, flow);
+	tap_flow_free(pmd, flow);
 	return NULL;
 }
 
-- 
2.51.0


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

* [PATCH v5 12/19] net/tap: free remote flow when implicit rule already exists
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (10 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 11/19] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 13/19] net/tap: dynamically allocate queue structures Stephen Hemminger
                     ` (7 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable

When tap_flow_implicit_create() gets EEXIST from the kernel, the
allocated remote_flow is never freed. Add rte_free() on that path.

Bugzilla ID: 1880
Fixes: 2ef1c0da894a ("net/tap: fix isolation mode toggling")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/tap_flow.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 427faf75d5..da1e70019a 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -1625,8 +1625,10 @@ int tap_flow_implicit_create(struct pmd_internals *pmd,
 	err = tap_nl_recv_ack(pmd->nlsk_fd);
 	if (err < 0) {
 		/* Silently ignore re-entering existing rule */
-		if (errno == EEXIST)
+		if (errno == EEXIST) {
+			rte_free(remote_flow);
 			goto success;
+		}
 		TAP_LOG(ERR,
 			"Kernel refused TC filter rule creation (%d): %s",
 			errno, strerror(errno));
-- 
2.51.0


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

* [PATCH v5 13/19] net/tap: dynamically allocate queue structures
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (11 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 12/19] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 14/19] net/tap: remove VLA in flow item validation Stephen Hemminger
                     ` (6 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Replace embedded rxq/txq arrays in pmd_internals with per-queue
allocations using rte_zmalloc_socket during queue setup. Queue
structures are freed in queue release and device close.

This reduces the size of pmd_internals and allocates queue resources.

Can safely remove some redundant checks in rx_queue_setup
since these checks are already done in the ethdev layer.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 130 +++++++++++++++++++++++-----------
 drivers/net/tap/rte_eth_tap.h |   3 +-
 2 files changed, 90 insertions(+), 43 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 6640ac1596..75fa517ae2 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -942,29 +942,36 @@ tap_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *tap_stats,
 	uint64_t rx_total = 0, tx_total = 0, tx_err_total = 0;
 	uint64_t rx_bytes_total = 0, tx_bytes_total = 0;
 	uint64_t rx_nombuf = 0, ierrors = 0;
-	const struct pmd_internals *pmd = dev->data->dev_private;
 
 	/* rx queue statistics */
 	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		struct rx_queue *rxq = dev->data->rx_queues[i];
+
+		if (rxq == NULL)
+			continue;
 		if (qstats != NULL && i < RTE_ETHDEV_QUEUE_STAT_CNTRS) {
-			qstats->q_ipackets[i] = pmd->rxq[i].stats.ipackets;
-			qstats->q_ibytes[i] = pmd->rxq[i].stats.ibytes;
+			qstats->q_ipackets[i] = rxq->stats.ipackets;
+			qstats->q_ibytes[i] = rxq->stats.ibytes;
 		}
-		rx_total += pmd->rxq[i].stats.ipackets;
-		rx_bytes_total += pmd->rxq[i].stats.ibytes;
-		rx_nombuf += pmd->rxq[i].stats.rx_nombuf;
-		ierrors += pmd->rxq[i].stats.ierrors;
+		rx_total += rxq->stats.ipackets;
+		rx_bytes_total += rxq->stats.ibytes;
+		rx_nombuf += rxq->stats.rx_nombuf;
+		ierrors += rxq->stats.ierrors;
 	}
 
 	/* tx queue statistics */
 	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		struct tx_queue *txq = dev->data->tx_queues[i];
+
+		if (txq == NULL)
+			continue;
 		if (qstats != NULL && i < RTE_ETHDEV_QUEUE_STAT_CNTRS) {
-			qstats->q_opackets[i] = pmd->txq[i].stats.opackets;
-			qstats->q_obytes[i] = pmd->txq[i].stats.obytes;
+			qstats->q_opackets[i] = txq->stats.opackets;
+			qstats->q_obytes[i] = txq->stats.obytes;
 		}
-		tx_total += pmd->txq[i].stats.opackets;
-		tx_bytes_total += pmd->txq[i].stats.obytes;
-		tx_err_total += pmd->txq[i].stats.errs;
+		tx_total += txq->stats.opackets;
+		tx_bytes_total += txq->stats.obytes;
+		tx_err_total += txq->stats.errs;
 	}
 
 	tap_stats->ipackets = rx_total;
@@ -981,11 +988,19 @@ static int
 tap_stats_reset(struct rte_eth_dev *dev)
 {
 	unsigned int i;
-	struct pmd_internals *pmd = dev->data->dev_private;
 
-	for (i = 0; i < RTE_PMD_TAP_MAX_QUEUES; i++) {
-		memset(&pmd->rxq[i].stats, 0, sizeof(struct pkt_stats));
-		memset(&pmd->txq[i].stats, 0, sizeof(struct pkt_stats));
+	for (i = 0; i < dev->data->nb_rx_queues; i++) {
+		struct rx_queue *rxq = dev->data->rx_queues[i];
+
+		if (rxq != NULL)
+			memset(&rxq->stats, 0, sizeof(struct pkt_stats));
+	}
+
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		struct tx_queue *txq = dev->data->tx_queues[i];
+
+		if (txq != NULL)
+			memset(&txq->stats, 0, sizeof(struct pkt_stats));
 	}
 
 	return 0;
@@ -1028,14 +1043,21 @@ tap_dev_close(struct rte_eth_dev *dev)
 #endif
 
 	for (i = 0; i < RTE_PMD_TAP_MAX_QUEUES; i++) {
-		struct rx_queue *rxq = &internals->rxq[i];
+		struct rx_queue *rxq = dev->data->rx_queues[i];
 
 		tap_queue_close(process_private, i);
 
-		tap_rxq_pool_free(rxq->pool);
-		rte_free(rxq->iovecs);
-		rxq->pool = NULL;
-		rxq->iovecs = NULL;
+		if (rxq != NULL) {
+			tap_rxq_pool_free(rxq->pool);
+			rte_free(rxq->iovecs);
+			rte_free(rxq);
+			dev->data->rx_queues[i] = NULL;
+		}
+
+		if (dev->data->tx_queues[i] != NULL) {
+			rte_free(dev->data->tx_queues[i]);
+			dev->data->tx_queues[i] = NULL;
+		}
 	}
 
 	if (internals->remote_if_index) {
@@ -1094,11 +1116,12 @@ tap_rx_queue_release(struct rte_eth_dev *dev, uint16_t qid)
 
 	tap_rxq_pool_free(rxq->pool);
 	rte_free(rxq->iovecs);
-	rxq->pool = NULL;
-	rxq->iovecs = NULL;
 
 	if (dev->data->tx_queues[qid] == NULL)
 		tap_queue_close(process_private, qid);
+
+	rte_free(rxq);
+	dev->data->rx_queues[qid] = NULL;
 }
 
 static void
@@ -1113,6 +1136,9 @@ tap_tx_queue_release(struct rte_eth_dev *dev, uint16_t qid)
 	process_private = rte_eth_devices[txq->out_port].process_private;
 	if (dev->data->rx_queues[qid] == NULL)
 		tap_queue_close(process_private, qid);
+
+	rte_free(txq);
+	dev->data->tx_queues[qid] = NULL;
 }
 
 static int
@@ -1424,16 +1450,14 @@ tap_gso_ctx_setup(struct rte_gso_ctx *gso_ctx, struct rte_eth_dev *dev)
 
 static int
 tap_setup_queue(struct rte_eth_dev *dev,
-		struct pmd_internals *internals,
 		uint16_t qid,
 		int is_rx)
 {
 	int fd, ret;
 	struct pmd_internals *pmd = dev->data->dev_private;
 	struct pmd_process_private *process_private = dev->process_private;
-	struct rx_queue *rx = &internals->rxq[qid];
-	struct tx_queue *tx = &internals->txq[qid];
-	struct rte_gso_ctx *gso_ctx = is_rx ? NULL : &tx->gso_ctx;
+	struct tx_queue *tx = dev->data->tx_queues[qid];
+	struct rte_gso_ctx *gso_ctx = (is_rx || tx == NULL) ? NULL : &tx->gso_ctx;
 	const char *dir = is_rx ? "rx" : "tx";
 
 	fd = process_private->fds[qid];
@@ -1455,16 +1479,16 @@ tap_setup_queue(struct rte_eth_dev *dev,
 		process_private->fds[qid] = fd;
 	}
 
-	tx->mtu = &dev->data->mtu;
-	rx->rxmode = &dev->data->dev_conf.rxmode;
+	if (tx != NULL) {
+		tx->mtu = &dev->data->mtu;
+		tx->type = pmd->type;
+	}
 	if (gso_ctx) {
 		ret = tap_gso_ctx_setup(gso_ctx, dev);
 		if (ret)
 			return -1;
 	}
 
-	tx->type = pmd->type;
-
 	return fd;
 }
 
@@ -1478,8 +1502,8 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
 {
 	struct pmd_internals *internals = dev->data->dev_private;
 	struct pmd_process_private *process_private = dev->process_private;
-	struct rx_queue *rxq = &internals->rxq[rx_queue_id];
-	struct rte_mbuf **tmp = &rxq->pool;
+	struct rx_queue *rxq;
+	struct rte_mbuf **tmp;
 	long iov_max = sysconf(_SC_IOV_MAX);
 
 	if (iov_max <= 0) {
@@ -1502,23 +1526,35 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
 		return -1;
 	}
 
+	rxq = rte_zmalloc_socket(dev->device->name, sizeof(*rxq), 0,
+				  socket_id);
+	if (!rxq) {
+		TAP_LOG(ERR,
+			"%s: Couldn't allocate rx queue structure",
+			dev->device->name);
+		return -ENOMEM;
+	}
+	tmp = &rxq->pool;
+
 	rxq->mp = mp;
 	rxq->trigger_seen = 1; /* force initial burst */
 	rxq->in_port = dev->data->port_id;
 	rxq->queue_id = rx_queue_id;
 	rxq->nb_rx_desc = nb_desc;
+	rxq->rxmode = &dev->data->dev_conf.rxmode;
 	iovecs = rte_zmalloc_socket(dev->device->name, sizeof(*iovecs), 0,
 				    socket_id);
 	if (!iovecs) {
 		TAP_LOG(WARNING,
 			"%s: Couldn't allocate %d RX descriptors",
 			dev->device->name, nb_desc);
+		rte_free(rxq);
 		return -ENOMEM;
 	}
 	rxq->iovecs = iovecs;
 
 	dev->data->rx_queues[rx_queue_id] = rxq;
-	fd = tap_setup_queue(dev, internals, rx_queue_id, 1);
+	fd = tap_setup_queue(dev, rx_queue_id, 1);
 	if (fd == -1) {
 		ret = fd;
 		goto error;
@@ -1556,9 +1592,9 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
 
 error:
 	tap_rxq_pool_free(rxq->pool);
-	rxq->pool = NULL;
 	rte_free(rxq->iovecs);
-	rxq->iovecs = NULL;
+	rte_free(rxq);
+	dev->data->rx_queues[rx_queue_id] = NULL;
 	return ret;
 }
 
@@ -1566,7 +1602,7 @@ static int
 tap_tx_queue_setup(struct rte_eth_dev *dev,
 		   uint16_t tx_queue_id,
 		   uint16_t nb_tx_desc __rte_unused,
-		   unsigned int socket_id __rte_unused,
+		   unsigned int socket_id,
 		   const struct rte_eth_txconf *tx_conf)
 {
 	struct pmd_internals *internals = dev->data->dev_private;
@@ -1577,8 +1613,17 @@ tap_tx_queue_setup(struct rte_eth_dev *dev,
 
 	if (tx_queue_id >= dev->data->nb_tx_queues)
 		return -1;
-	dev->data->tx_queues[tx_queue_id] = &internals->txq[tx_queue_id];
-	txq = dev->data->tx_queues[tx_queue_id];
+
+	txq = rte_zmalloc_socket(dev->device->name, sizeof(*txq), 0,
+				  socket_id);
+	if (!txq) {
+		TAP_LOG(ERR,
+			"%s: Couldn't allocate tx queue structure",
+			dev->device->name);
+		return -ENOMEM;
+	}
+
+	dev->data->tx_queues[tx_queue_id] = txq;
 	txq->out_port = dev->data->port_id;
 	txq->queue_id = tx_queue_id;
 
@@ -1588,9 +1633,12 @@ tap_tx_queue_setup(struct rte_eth_dev *dev,
 			 RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
 			 RTE_ETH_TX_OFFLOAD_TCP_CKSUM));
 
-	ret = tap_setup_queue(dev, internals, tx_queue_id, 0);
-	if (ret == -1)
+	ret = tap_setup_queue(dev, tx_queue_id, 0);
+	if (ret == -1) {
+		rte_free(txq);
+		dev->data->tx_queues[tx_queue_id] = NULL;
 		return -1;
+	}
 	TAP_LOG(DEBUG,
 		"  TX TUNTAP device name %s, qid %d on fd %d csum %s",
 		internals->name, tx_queue_id,
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index 7be8f4d01b..365d5a5fe1 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -88,8 +88,7 @@ struct pmd_internals {
 	LIST_HEAD(tap_implicit_flows, rte_flow) implicit_flows;
 #endif
 
-	struct rx_queue rxq[RTE_PMD_TAP_MAX_QUEUES]; /* List of RX queues */
-	struct tx_queue txq[RTE_PMD_TAP_MAX_QUEUES]; /* List of TX queues */
+
 	struct rte_intr_handle *intr_handle;         /* LSC interrupt handle. */
 	int ka_fd;                        /* keep-alive file descriptor */
 	struct rte_mempool *gso_ctx_mp;     /* Mempool for GSO packets */
-- 
2.51.0


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

* [PATCH v5 14/19] net/tap: remove VLA in flow item validation
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (12 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 13/19] net/tap: dynamically allocate queue structures Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 15/19] net/tap: fix Rx descriptor vs scatter segment confusion Stephen Hemminger
                     ` (5 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Replace variable length arrays in tap_flow_item_validate() with
fixed size buffers. The size parameter comes from flow item struct
sizes which are all small (largest is rte_flow_item_ipv6 at ~44
bytes).

Define TAP_FLOW_ITEM_MAX_SIZE (128) as the upper bound with a
safety check to reject unexpected sizes.

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

diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index da1e70019a..e33c6f8e2d 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -706,6 +706,13 @@ tap_flow_create_tcp(const struct rte_flow_item *item, struct convert_data *info)
  * @return
  *   0 on success.
  */
+/*
+ * Maximum size of a flow item in bytes.
+ * Must be larger than all supported rte_flow_item_* structures
+ * (currently the largest is rte_flow_item_ipv6 at ~44 bytes).
+ */
+#define TAP_FLOW_ITEM_MAX_SIZE 128
+
 static int
 tap_flow_item_validate(const struct rte_flow_item *item,
 		       unsigned int size,
@@ -754,11 +761,13 @@ tap_flow_item_validate(const struct rte_flow_item *item,
 	 * TC does not support range so anything else is invalid.
 	 */
 	if (item->spec && item->last) {
-		uint8_t spec[size];
-		uint8_t last[size];
+		uint8_t spec[TAP_FLOW_ITEM_MAX_SIZE];
+		uint8_t last[TAP_FLOW_ITEM_MAX_SIZE];
 		const uint8_t *apply = default_mask;
 		unsigned int i;
 
+		if (size > TAP_FLOW_ITEM_MAX_SIZE)
+			return -1;
 		if (item->mask)
 			apply = item->mask;
 		for (i = 0; i < size; ++i) {
-- 
2.51.0


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

* [PATCH v5 15/19] net/tap: fix Rx descriptor vs scatter segment confusion
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (13 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 14/19] net/tap: remove VLA in flow item validation Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 16/19] net/tap: replace use of VLA in transmit burst Stephen Hemminger
                     ` (4 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The TAP PMD was reusing the nb_rx_desc queue setup parameter to control
the maximum number of scatter segments per received packet. Since the
driver reads one packet at a time from the kernel fd via readv(), it has
no descriptor ring and nb_rx_desc as a queue depth has no meaning here.
This meant applications could inadvertently affect scatter receive
behavior by changing the queue size parameter.

Compute the required number of scatter segments from the MTU and the
mempool buffer size instead, capping at TAP_MAX_RX_SEGS (128). The
nb_rx_desc parameter is now ignored.

While here, convert the iovecs pointer-to-VLA in struct rx_queue to a
flexible array member. This eliminates the separate iovec allocation,
removes the on-stack VLA from queue setup, reduces pointer indirection
on the receive path, and simplifies all cleanup paths to a single free.

Replace the runtime sysconf(_SC_IOV_MAX) check with a compile-time
static_assert against IOV_MAX from limits.h.

This is a bug fix, but too big a change to backport to earlier
releases.

Fixes: 0781f5762cfe ("net/tap: support segmented mbufs")

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 111 ++++++++++++++++++++--------------
 drivers/net/tap/rte_eth_tap.h |   4 +-
 2 files changed, 66 insertions(+), 49 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 75fa517ae2..f2cf7da736 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -35,6 +35,7 @@
 #include <linux/if_tun.h>
 #include <linux/sched.h>
 #include <fcntl.h>
+#include <limits.h>
 
 #include <tap_rss.h>
 #include <rte_eth_tap.h>
@@ -69,7 +70,17 @@
 
 static_assert(RTE_PMD_TAP_MAX_QUEUES <= RTE_MP_MAX_FD_NUM, "TAP max queues exceeds MP fd limit");
 
-#define TAP_IOV_DEFAULT_MAX 1024
+/*
+ * Upper bound on the number of scatter segments per received packet.
+ * Actual value is computed at queue setup from MTU and mbuf data size.
+ * One extra iovec slot is reserved for the tun_pi header, so the total
+ * iovec count passed to readv() is max_rx_segs + 1, which must not
+ * exceed IOV_MAX.
+ */
+#define TAP_MAX_RX_SEGS 128
+
+static_assert(TAP_MAX_RX_SEGS + 1 <= IOV_MAX,
+	      "TAP_MAX_RX_SEGS + 1 (for tun_pi) must not exceed IOV_MAX");
 
 #define TAP_RX_OFFLOAD (RTE_ETH_RX_OFFLOAD_SCATTER |	\
 			RTE_ETH_RX_OFFLOAD_IPV4_CKSUM |	\
@@ -444,9 +455,7 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		int len;
 
 		len = readv(process_private->fds[rxq->queue_id],
-			*rxq->iovecs,
-			1 + (rxq->rxmode->offloads & RTE_ETH_RX_OFFLOAD_SCATTER ?
-			     rxq->nb_rx_desc : 1));
+			    rxq->iovecs,  1 + rxq->max_rx_segs);
 		if (len < (int)sizeof(struct tun_pi))
 			break;
 
@@ -483,9 +492,9 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 			new_tail->next = seg->next;
 
 			/* iovecs[0] is reserved for packet info (pi) */
-			(*rxq->iovecs)[mbuf->nb_segs].iov_len =
+			rxq->iovecs[mbuf->nb_segs].iov_len =
 				buf->buf_len - data_off;
-			(*rxq->iovecs)[mbuf->nb_segs].iov_base =
+			rxq->iovecs[mbuf->nb_segs].iov_base =
 				(char *)buf->buf_addr + data_off;
 
 			seg->data_len = RTE_MIN(seg->buf_len - data_off, len);
@@ -1049,7 +1058,6 @@ tap_dev_close(struct rte_eth_dev *dev)
 
 		if (rxq != NULL) {
 			tap_rxq_pool_free(rxq->pool);
-			rte_free(rxq->iovecs);
 			rte_free(rxq);
 			dev->data->rx_queues[i] = NULL;
 		}
@@ -1115,7 +1123,6 @@ tap_rx_queue_release(struct rte_eth_dev *dev, uint16_t qid)
 	process_private = rte_eth_devices[rxq->in_port].process_private;
 
 	tap_rxq_pool_free(rxq->pool);
-	rte_free(rxq->iovecs);
 
 	if (dev->data->tx_queues[qid] == NULL)
 		tap_queue_close(process_private, qid);
@@ -1495,7 +1502,7 @@ tap_setup_queue(struct rte_eth_dev *dev,
 static int
 tap_rx_queue_setup(struct rte_eth_dev *dev,
 		   uint16_t rx_queue_id,
-		   uint16_t nb_rx_desc,
+		   uint16_t nb_rx_desc __rte_unused,
 		   unsigned int socket_id,
 		   const struct rte_eth_rxconf *rx_conf __rte_unused,
 		   struct rte_mempool *mp)
@@ -1504,30 +1511,51 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
 	struct pmd_process_private *process_private = dev->process_private;
 	struct rx_queue *rxq;
 	struct rte_mbuf **tmp;
-	long iov_max = sysconf(_SC_IOV_MAX);
+	uint16_t max_rx_segs;
+	const uint32_t max_rx_pktlen = dev->data->mtu + RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN;
+	const uint16_t seg_size = rte_pktmbuf_data_room_size(mp);
+	uint16_t data_off = RTE_PKTMBUF_HEADROOM;
+	int ret = 0;
 
-	if (iov_max <= 0) {
-		TAP_LOG(WARNING,
-			"_SC_IOV_MAX is not defined. Using %d as default",
-			TAP_IOV_DEFAULT_MAX);
-		iov_max = TAP_IOV_DEFAULT_MAX;
+	/*
+	 * The nb_rx_desc parameter is ignored: the TAP PMD reads one packet
+	 * at a time from the kernel fd, so there is no descriptor ring.
+	 *
+	 * Compute the number of scatter segments needed to receive the
+	 * largest possible packet (MTU + L2 overhead).  The first segment
+	 * has headroom reserved, subsequent segments use the full data area.
+	 */
+	if (seg_size <= RTE_PKTMBUF_HEADROOM) {
+		TAP_LOG(ERR, "%s: mbuf pool has no usable data room", dev->device->name);
+		return -EINVAL;
 	}
-	uint16_t nb_desc = RTE_MIN(nb_rx_desc, iov_max - 1);
-	struct iovec (*iovecs)[nb_desc + 1];
-	int data_off = RTE_PKTMBUF_HEADROOM;
-	int ret = 0;
-	int fd;
-	int i;
 
-	if (rx_queue_id >= dev->data->nb_rx_queues || !mp) {
+	const uint16_t first_seg_size = seg_size - RTE_PKTMBUF_HEADROOM;
+	if (max_rx_pktlen > first_seg_size)
+		max_rx_segs = 1 + (max_rx_pktlen - first_seg_size + seg_size - 1) /
+				   seg_size;
+	else
+		max_rx_segs = 1;
+
+	if (max_rx_segs > TAP_MAX_RX_SEGS) {
+		TAP_LOG(ERR,
+			"%s: MTU %u requires %u scatter segments, max is %u",
+			dev->device->name, dev->data->mtu,
+			max_rx_segs, TAP_MAX_RX_SEGS);
+		return -EINVAL;
+	}
+
+	if (max_rx_segs > 1 &&
+	    !(dev->data->dev_conf.rxmode.offloads & RTE_ETH_RX_OFFLOAD_SCATTER)) {
+		/* non-fatal since applications might be doing it wrong now */
 		TAP_LOG(WARNING,
-			"nb_rx_queues %d too small or mempool NULL",
-			dev->data->nb_rx_queues);
-		return -1;
+			"%s: MTU %u requires %u scatter segments, but offload not set",
+			dev->device->name, dev->data->mtu, max_rx_segs);
 	}
 
-	rxq = rte_zmalloc_socket(dev->device->name, sizeof(*rxq), 0,
-				  socket_id);
+	rxq = rte_zmalloc_socket(dev->device->name,
+				 sizeof(*rxq) + (max_rx_segs + 1) * sizeof(struct iovec),
+				 RTE_CACHE_LINE_SIZE, socket_id);
 	if (!rxq) {
 		TAP_LOG(ERR,
 			"%s: Couldn't allocate rx queue structure",
@@ -1540,30 +1568,20 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
 	rxq->trigger_seen = 1; /* force initial burst */
 	rxq->in_port = dev->data->port_id;
 	rxq->queue_id = rx_queue_id;
-	rxq->nb_rx_desc = nb_desc;
+	rxq->max_rx_segs = max_rx_segs;
 	rxq->rxmode = &dev->data->dev_conf.rxmode;
-	iovecs = rte_zmalloc_socket(dev->device->name, sizeof(*iovecs), 0,
-				    socket_id);
-	if (!iovecs) {
-		TAP_LOG(WARNING,
-			"%s: Couldn't allocate %d RX descriptors",
-			dev->device->name, nb_desc);
-		rte_free(rxq);
-		return -ENOMEM;
-	}
-	rxq->iovecs = iovecs;
 
 	dev->data->rx_queues[rx_queue_id] = rxq;
-	fd = tap_setup_queue(dev, rx_queue_id, 1);
+	int fd = tap_setup_queue(dev, rx_queue_id, 1);
 	if (fd == -1) {
 		ret = fd;
 		goto error;
 	}
 
-	(*rxq->iovecs)[0].iov_len = sizeof(struct tun_pi);
-	(*rxq->iovecs)[0].iov_base = &rxq->pi;
+	rxq->iovecs[0].iov_len = sizeof(struct tun_pi);
+	rxq->iovecs[0].iov_base = &rxq->pi;
 
-	for (i = 1; i <= nb_desc; i++) {
+	for (uint16_t i = 1; i <= max_rx_segs; i++) {
 		*tmp = rte_pktmbuf_alloc(rxq->mp);
 		if (!*tmp) {
 			TAP_LOG(WARNING,
@@ -1572,8 +1590,8 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
 			ret = -ENOMEM;
 			goto error;
 		}
-		(*rxq->iovecs)[i].iov_len = (*tmp)->buf_len - data_off;
-		(*rxq->iovecs)[i].iov_base =
+		rxq->iovecs[i].iov_len = (*tmp)->buf_len - data_off;
+		rxq->iovecs[i].iov_base =
 			(char *)(*tmp)->buf_addr + data_off;
 		data_off = 0;
 		tmp = &(*tmp)->next;
@@ -1592,7 +1610,6 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
 
 error:
 	tap_rxq_pool_free(rxq->pool);
-	rte_free(rxq->iovecs);
 	rte_free(rxq);
 	dev->data->rx_queues[rx_queue_id] = NULL;
 	return ret;
@@ -1614,8 +1631,8 @@ tap_tx_queue_setup(struct rte_eth_dev *dev,
 	if (tx_queue_id >= dev->data->nb_tx_queues)
 		return -1;
 
-	txq = rte_zmalloc_socket(dev->device->name, sizeof(*txq), 0,
-				  socket_id);
+	txq = rte_zmalloc_socket(dev->device->name, sizeof(*txq),
+				 RTE_CACHE_LINE_SIZE, socket_id);
 	if (!txq) {
 		TAP_LOG(ERR,
 			"%s: Couldn't allocate tx queue structure",
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index 365d5a5fe1..78182b1185 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -49,11 +49,11 @@ struct rx_queue {
 	uint16_t in_port;               /* Port ID */
 	uint16_t queue_id;		/* queue ID*/
 	struct pkt_stats stats;         /* Stats for this RX queue */
-	uint16_t nb_rx_desc;            /* max number of mbufs available */
+	uint16_t max_rx_segs;           /* max scatter segments per packet */
 	struct rte_eth_rxmode *rxmode;  /* RX features */
 	struct rte_mbuf *pool;          /* mbufs pool for this queue */
-	struct iovec (*iovecs)[];       /* descriptors for this queue */
 	struct tun_pi pi;               /* packet info for iovecs */
+	struct iovec iovecs[];          /* iov[0] = pi, iov[1..max_rx_segs] = data */
 };
 
 struct tx_queue {
-- 
2.51.0


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

* [PATCH v5 16/19] net/tap: replace use of VLA in transmit burst
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (14 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 15/19] net/tap: fix Rx descriptor vs scatter segment confusion Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 17/19] net/tap: consolidate queue statistics Stephen Hemminger
                     ` (3 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Replace the per-packet variable-length iovec array on the stack
with a fixed-size array of TAP_MAX_TX_SEGS + 1 (129) entries,
hoisted to function scope in tap_write_mbufs(). Packets with
more than 128 segments are dropped.

This eliminates the last VLA in the driver.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index f2cf7da736..9efdbc7012 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -79,9 +79,15 @@ static_assert(RTE_PMD_TAP_MAX_QUEUES <= RTE_MP_MAX_FD_NUM, "TAP max queues excee
  */
 #define TAP_MAX_RX_SEGS 128
 
+/* Limit on the number of segments per mbuf on Tx */
+#define TAP_MAX_TX_SEGS  128
+
 static_assert(TAP_MAX_RX_SEGS + 1 <= IOV_MAX,
 	      "TAP_MAX_RX_SEGS + 1 (for tun_pi) must not exceed IOV_MAX");
 
+static_assert(TAP_MAX_TX_SEGS + 1 <= IOV_MAX,
+	      "TAP_MAX_TX_SEGS + 1 (for tun_pi) must not exceed IOV_MAX");
+
 #define TAP_RX_OFFLOAD (RTE_ETH_RX_OFFLOAD_SCATTER |	\
 			RTE_ETH_RX_OFFLOAD_IPV4_CKSUM |	\
 			RTE_ETH_RX_OFFLOAD_UDP_CKSUM |	\
@@ -533,13 +539,13 @@ tap_write_mbufs(struct tx_queue *txq, uint16_t num_mbufs,
 			uint16_t *num_packets, unsigned long *num_tx_bytes)
 {
 	struct pmd_process_private *process_private;
+	struct iovec iovecs[TAP_MAX_TX_SEGS + 1];
 	int i;
 
 	process_private = rte_eth_devices[txq->out_port].process_private;
 
 	for (i = 0; i < num_mbufs; i++) {
 		struct rte_mbuf *mbuf = pmbufs[i];
-		struct iovec iovecs[mbuf->nb_segs + 2];
 		struct tun_pi pi = { .flags = 0, .proto = 0x00 };
 		struct rte_mbuf *seg = mbuf;
 		uint64_t l4_ol_flags;
@@ -641,6 +647,10 @@ tap_write_mbufs(struct tx_queue *txq, uint16_t num_mbufs,
 		}
 
 skip_l4_cksum:
+		/* tun_pi header + packet segments must fit in iovecs */
+		if (unlikely(mbuf->nb_segs > TAP_MAX_TX_SEGS))
+			return -1;
+
 		for (j = 0; j < mbuf->nb_segs; j++) {
 			iovecs[k].iov_len = rte_pktmbuf_data_len(seg);
 			iovecs[k].iov_base = rte_pktmbuf_mtod(seg, void *);
@@ -669,7 +679,7 @@ pmd_tx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 	uint16_t num_packets = 0;
 	unsigned long num_tx_bytes = 0;
 	uint32_t max_size;
-	int i;
+	unsigned int i;
 
 	if (unlikely(nb_pkts == 0))
 		return 0;
-- 
2.51.0


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

* [PATCH v5 17/19] net/tap: consolidate queue statistics
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (15 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 16/19] net/tap: replace use of VLA in transmit burst Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 18/19] net/tap: enable VLA warnings Stephen Hemminger
                     ` (2 subsequent siblings)
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The per-queue packet statistics had structure elements
for both transmit and receive but each queue structure
was only used in one direction. Rearrange and get the
correct statistic based on context.

The rx_nombuf statistic can be accumulated in the device
structure and rte_eth_stats_get() handles it from there.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/rte_eth_tap.c | 48 +++++++++++++++++------------------
 drivers/net/tap/rte_eth_tap.h | 17 +++++--------
 2 files changed, 31 insertions(+), 34 deletions(-)

diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 9efdbc7012..2630a53ce4 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -467,7 +467,7 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 
 		/* Packet couldn't fit in the provided mbuf */
 		if (unlikely(rxq->pi.flags & TUN_PKT_STRIP)) {
-			rxq->stats.ierrors++;
+			rxq->stats.errors++;
 			continue;
 		}
 
@@ -479,7 +479,8 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 			struct rte_mbuf *buf = rte_pktmbuf_alloc(rxq->mp);
 
 			if (unlikely(!buf)) {
-				rxq->stats.rx_nombuf++;
+				rte_eth_devices[rxq->in_port].data->rx_mbuf_alloc_failed++;
+
 				/* No new buf has been allocated: do nothing */
 				if (!new_tail || !seg)
 					goto end;
@@ -524,8 +525,8 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		num_rx_bytes += mbuf->pkt_len;
 	}
 end:
-	rxq->stats.ipackets += num_rx;
-	rxq->stats.ibytes += num_rx_bytes;
+	rxq->stats.packets += num_rx;
+	rxq->stats.bytes += num_rx_bytes;
 
 	if (trigger && num_rx < nb_pkts)
 		rxq->trigger_seen = trigger;
@@ -709,7 +710,7 @@ pmd_tx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 			tso_segsz = mbuf_in->tso_segsz + hdrs_len;
 			if (unlikely(tso_segsz == hdrs_len) ||
 				tso_segsz > *txq->mtu) {
-				txq->stats.errs++;
+				txq->stats.errors++;
 				break;
 			}
 			gso_ctx->gso_size = tso_segsz;
@@ -747,7 +748,7 @@ pmd_tx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		ret = tap_write_mbufs(txq, num_mbufs, mbuf,
 				&num_packets, &num_tx_bytes);
 		if (ret == -1) {
-			txq->stats.errs++;
+			txq->stats.errors++;
 			/* free tso mbufs */
 			if (num_tso_mbufs > 0)
 				rte_pktmbuf_free_bulk(mbuf, num_tso_mbufs);
@@ -765,9 +766,9 @@ pmd_tx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
 		}
 	}
 
-	txq->stats.opackets += num_packets;
-	txq->stats.errs += nb_pkts - num_tx;
-	txq->stats.obytes += num_tx_bytes;
+	txq->stats.packets += num_packets;
+	txq->stats.errors += nb_pkts - num_tx;
+	txq->stats.bytes += num_tx_bytes;
 
 	return num_tx;
 }
@@ -960,7 +961,7 @@ tap_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *tap_stats,
 	unsigned int i;
 	uint64_t rx_total = 0, tx_total = 0, tx_err_total = 0;
 	uint64_t rx_bytes_total = 0, tx_bytes_total = 0;
-	uint64_t rx_nombuf = 0, ierrors = 0;
+	uint64_t ierrors = 0;
 
 	/* rx queue statistics */
 	for (i = 0; i < dev->data->nb_rx_queues; i++) {
@@ -969,13 +970,12 @@ tap_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *tap_stats,
 		if (rxq == NULL)
 			continue;
 		if (qstats != NULL && i < RTE_ETHDEV_QUEUE_STAT_CNTRS) {
-			qstats->q_ipackets[i] = rxq->stats.ipackets;
-			qstats->q_ibytes[i] = rxq->stats.ibytes;
+			qstats->q_ipackets[i] = rxq->stats.packets;
+			qstats->q_ibytes[i] = rxq->stats.bytes;
 		}
-		rx_total += rxq->stats.ipackets;
-		rx_bytes_total += rxq->stats.ibytes;
-		rx_nombuf += rxq->stats.rx_nombuf;
-		ierrors += rxq->stats.ierrors;
+		rx_total += rxq->stats.packets;
+		rx_bytes_total += rxq->stats.bytes;
+		ierrors += rxq->stats.errors;
 	}
 
 	/* tx queue statistics */
@@ -985,21 +985,21 @@ tap_stats_get(struct rte_eth_dev *dev, struct rte_eth_stats *tap_stats,
 		if (txq == NULL)
 			continue;
 		if (qstats != NULL && i < RTE_ETHDEV_QUEUE_STAT_CNTRS) {
-			qstats->q_opackets[i] = txq->stats.opackets;
-			qstats->q_obytes[i] = txq->stats.obytes;
+			qstats->q_opackets[i] = txq->stats.packets;
+			qstats->q_obytes[i] = txq->stats.bytes;
 		}
-		tx_total += txq->stats.opackets;
-		tx_bytes_total += txq->stats.obytes;
-		tx_err_total += txq->stats.errs;
+		tx_total += txq->stats.packets;
+		tx_bytes_total += txq->stats.bytes;
+		tx_err_total += txq->stats.errors;
 	}
 
 	tap_stats->ipackets = rx_total;
 	tap_stats->ibytes = rx_bytes_total;
 	tap_stats->ierrors = ierrors;
-	tap_stats->rx_nombuf = rx_nombuf;
 	tap_stats->opackets = tx_total;
 	tap_stats->oerrors = tx_err_total;
 	tap_stats->obytes = tx_bytes_total;
+
 	return 0;
 }
 
@@ -1012,14 +1012,14 @@ tap_stats_reset(struct rte_eth_dev *dev)
 		struct rx_queue *rxq = dev->data->rx_queues[i];
 
 		if (rxq != NULL)
-			memset(&rxq->stats, 0, sizeof(struct pkt_stats));
+			memset(&rxq->stats, 0, sizeof(rxq->stats));
 	}
 
 	for (i = 0; i < dev->data->nb_tx_queues; i++) {
 		struct tx_queue *txq = dev->data->tx_queues[i];
 
 		if (txq != NULL)
-			memset(&txq->stats, 0, sizeof(struct pkt_stats));
+			memset(&txq->stats, 0, sizeof(txq->stats));
 	}
 
 	return 0;
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index 78182b1185..fc7086e97d 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -9,6 +9,7 @@
 #include <sys/queue.h>
 #include <sys/uio.h>
 #include <inttypes.h>
+#include <limits.h>
 #include <net/if.h>
 
 #include <linux/if_tun.h>
@@ -33,14 +34,10 @@ enum rte_tuntap_type {
 	ETH_TUNTAP_TYPE_MAX,
 };
 
-struct pkt_stats {
-	uint64_t opackets;              /* Number of output packets */
-	uint64_t ipackets;              /* Number of input packets */
-	uint64_t obytes;                /* Number of bytes on output */
-	uint64_t ibytes;                /* Number of bytes on input */
-	uint64_t errs;                  /* Number of TX error packets */
-	uint64_t ierrors;               /* Number of RX error packets */
-	uint64_t rx_nombuf;             /* Nb of RX mbuf alloc failures */
+struct queue_stats {
+	uint64_t packets;
+	uint64_t bytes;
+	uint64_t errors;
 };
 
 struct rx_queue {
@@ -48,7 +45,7 @@ struct rx_queue {
 	uint32_t trigger_seen;          /* Last seen Rx trigger value */
 	uint16_t in_port;               /* Port ID */
 	uint16_t queue_id;		/* queue ID*/
-	struct pkt_stats stats;         /* Stats for this RX queue */
+	struct queue_stats stats;        /* Stats for this RX queue */
 	uint16_t max_rx_segs;           /* max scatter segments per packet */
 	struct rte_eth_rxmode *rxmode;  /* RX features */
 	struct rte_mbuf *pool;          /* mbufs pool for this queue */
@@ -60,7 +57,7 @@ struct tx_queue {
 	int type;                       /* Type field - TUN|TAP */
 	uint16_t *mtu;                  /* Pointer to MTU from dev_data */
 	uint16_t csum:1;                /* Enable checksum offloading */
-	struct pkt_stats stats;         /* Stats for this TX queue */
+	struct queue_stats stats;        /* Stats for this TX queue */
 	struct rte_gso_ctx gso_ctx;     /* GSO context */
 	uint16_t out_port;              /* Port ID */
 	uint16_t queue_id;		/* queue ID*/
-- 
2.51.0


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

* [PATCH v5 18/19] net/tap: enable VLA warnings
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (16 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 17/19] net/tap: consolidate queue statistics Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-02-22 17:30   ` [PATCH v5 19/19] test: add unit tests for TAP PMD Stephen Hemminger
  2026-02-25 23:18   ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The TAP driver no longer has any VLA's.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/tap/meson.build | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/net/tap/meson.build b/drivers/net/tap/meson.build
index 7160e9e98d..fa4e6cbec9 100644
--- a/drivers/net/tap/meson.build
+++ b/drivers/net/tap/meson.build
@@ -16,7 +16,6 @@ deps = ['bus_vdev', 'gso', 'hash']
 
 max_queues = '-DTAP_MAX_QUEUES=64'
 cflags += max_queues
-cflags += no_wvla_cflag
 
 require_iova_in_mbuf = false
 
-- 
2.51.0


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

* [PATCH v5 19/19] test: add unit tests for TAP PMD
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (17 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 18/19] net/tap: enable VLA warnings Stephen Hemminger
@ 2026-02-22 17:30   ` Stephen Hemminger
  2026-03-16  8:03     ` David Marchand
  2026-02-25 23:18   ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
  19 siblings, 1 reply; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-22 17:30 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a standalone test suite for the TAP PMD, modeled on the existing
test_pmd_ring tests. Exercises device configuration, link status,
stats, MTU, MAC address, promiscuous/allmulticast modes, queue
start/stop, link up/down, device stop/start, and multi-queue setup.

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

diff --git a/app/test/meson.build b/app/test/meson.build
index 4fd8670e05..2689a23553 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -144,6 +144,7 @@ source_file_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'],
+    'test_pmd_tap.c': ['ethdev', 'net_tap', 'bus_vdev'],
     'test_pmu.c': ['pmu'],
     'test_power.c': ['power', 'power_acpi', 'power_kvm_vm', 'power_intel_pstate',
         'power_amd_pstate', 'power_cppc'],
diff --git a/app/test/test_pmd_tap.c b/app/test/test_pmd_tap.c
new file mode 100644
index 0000000000..482c70b3f6
--- /dev/null
+++ b/app/test/test_pmd_tap.c
@@ -0,0 +1,1143 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+/*
+ * Basic test of TAP device functionality
+ * based off of PMD ring test.
+ */
+
+#include "test.h"
+
+#include <stdio.h>
+
+#ifndef RTE_EXEC_ENV_LINUX
+
+/* TAP PMD is only available on Linux */
+static int
+test_pmd_tap(void)
+{
+	printf("TAP PMD not supported on this platform, skipping test\n");
+	return TEST_SKIPPED;
+}
+
+#else /* RTE_EXEC_ENV_LINUX */
+
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <rte_ethdev.h>
+#include <rte_bus_vdev.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+
+#define SOCKET0 0
+#define RING_SIZE 256
+#define NB_MBUF 4096
+#define MAX_PKT_BURST 32
+#define PKT_LEN 64
+
+static struct rte_mempool *mp;
+static int tap_port0 = -1;
+static int tap_port1 = -1;
+
+static int
+test_tap_ethdev_configure(int port)
+{
+	struct rte_eth_conf port_conf;
+	struct rte_eth_link link;
+	int ret;
+
+	memset(&port_conf, 0, sizeof(struct rte_eth_conf));
+
+	ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+	if (ret < 0) {
+		printf("Configure failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_tx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL);
+	if (ret < 0) {
+		printf("TX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, mp);
+	if (ret < 0) {
+		printf("RX queue setup failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0) {
+		printf("Error starting port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_link_get(port, &link);
+	if (ret < 0) {
+		printf("Link get failed for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d: link status %s, speed %u Mbps\n",
+	       port,
+	       link.link_status ? "up" : "down",
+	       link.link_speed);
+
+	return 0;
+}
+
+static struct rte_mbuf *
+create_test_packet(struct rte_mempool *pool, uint16_t pkt_len)
+{
+	struct rte_mbuf *mbuf;
+	struct rte_ether_hdr *eth_hdr;
+	struct rte_ipv4_hdr *ip_hdr;
+	uint8_t *payload;
+	uint16_t i;
+
+	mbuf = rte_pktmbuf_alloc(pool);
+	if (mbuf == NULL) {
+		printf("%s(): mbuf alloc failed\n", __func__);
+		return NULL;
+	}
+
+	/* Ensure minimum packet size for Ethernet */
+	if (pkt_len < RTE_ETHER_MIN_LEN)
+		pkt_len = RTE_ETHER_MIN_LEN;
+
+	eth_hdr = (struct rte_ether_hdr *)rte_pktmbuf_append(mbuf, pkt_len);
+	if (eth_hdr == NULL) {
+		printf("%s(): append %u bytes failed\n", __func__, pkt_len);
+		rte_pktmbuf_free(mbuf);
+		return NULL;
+	}
+
+	/* Create Ethernet header */
+	eth_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
+	memset(&eth_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN); /* broadcast */
+	memset(&eth_hdr->src_addr, 0x02, RTE_ETHER_ADDR_LEN);
+	eth_hdr->src_addr.addr_bytes[5] = 0x01;
+	eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+	/* Create simple IPv4 header */
+	ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
+	memset(ip_hdr, 0, sizeof(*ip_hdr));
+	ip_hdr->version_ihl = 0x45; /* IPv4, 20 byte header */
+	ip_hdr->total_length = rte_cpu_to_be_16(pkt_len - sizeof(*eth_hdr));
+	ip_hdr->time_to_live = 64;
+	ip_hdr->next_proto_id = IPPROTO_UDP;
+	ip_hdr->src_addr = rte_cpu_to_be_32(0x0A000001); /* 10.0.0.1 */
+	ip_hdr->dst_addr = rte_cpu_to_be_32(0x0A000002); /* 10.0.0.2 */
+
+	/* Fill payload with pattern */
+	payload = (uint8_t *)(ip_hdr + 1);
+	for (i = 0; i < pkt_len - sizeof(*eth_hdr) - sizeof(*ip_hdr); i++)
+		payload[i] = (uint8_t)(i & 0xFF);
+
+	return mbuf;
+}
+
+static int
+test_tap_send_receive(void)
+{
+	struct rte_mbuf *tx_mbufs[MAX_PKT_BURST];
+	struct rte_mbuf *rx_mbufs[MAX_PKT_BURST];
+	uint16_t nb_tx, nb_rx;
+	int i;
+
+	printf("Testing TAP packet send and receive\n");
+
+	/* Create test packets */
+	for (i = 0; i < MAX_PKT_BURST / 2; i++) {
+		tx_mbufs[i] = create_test_packet(mp, PKT_LEN);
+		if (tx_mbufs[i] == NULL) {
+			printf("Failed to create test packet %d\n", i);
+			/* Free already allocated packets */
+			while (--i >= 0)
+				rte_pktmbuf_free(tx_mbufs[i]);
+			return TEST_FAILED;
+		}
+	}
+
+	/* Send packets */
+	nb_tx = rte_eth_tx_burst(tap_port0, 0, tx_mbufs, MAX_PKT_BURST / 2);
+	printf("Transmitted %u packets on port %d\n", nb_tx, tap_port0);
+
+	/* Free any unsent packets */
+	for (i = nb_tx; i < MAX_PKT_BURST / 2; i++)
+		rte_pktmbuf_free(tx_mbufs[i]);
+
+	if (nb_tx == 0) {
+		printf("Warning: No packets transmitted (this may be expected if interface is not up)\n");
+		return TEST_SUCCESS;
+	}
+
+	/* Small delay to allow packets to be processed */
+	usleep(10000);
+
+	/* Try to receive packets (note: TAP loopback depends on kernel config) */
+	nb_rx = rte_eth_rx_burst(tap_port0, 0, rx_mbufs, MAX_PKT_BURST);
+	printf("Received %u packets on port %d\n", nb_rx, tap_port0);
+
+	/* Free received packets */
+	for (i = 0; i < nb_rx; i++)
+		rte_pktmbuf_free(rx_mbufs[i]);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_stats_get(int port)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	printf("Testing TAP PMD stats_get for port %d\n", port);
+
+	ret = rte_eth_stats_get(port, &stats);
+	if (ret != 0) {
+		printf("Error: failed to get stats for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d stats:\n", port);
+	printf("  ipackets: %"PRIu64"\n", stats.ipackets);
+	printf("  opackets: %"PRIu64"\n", stats.opackets);
+	printf("  ibytes:   %"PRIu64"\n", stats.ibytes);
+	printf("  obytes:   %"PRIu64"\n", stats.obytes);
+	printf("  ierrors:  %"PRIu64"\n", stats.ierrors);
+	printf("  oerrors:  %"PRIu64"\n", stats.oerrors);
+
+	return 0;
+}
+
+static int
+test_tap_stats_reset(int port)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	printf("Testing TAP PMD stats_reset for port %d\n", port);
+
+	ret = rte_eth_stats_reset(port);
+	if (ret != 0) {
+		printf("Error: failed to reset stats for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	ret = rte_eth_stats_get(port, &stats);
+	if (ret != 0) {
+		printf("Error: failed to get stats after reset for port %d\n",
+		       port);
+		return -1;
+	}
+
+	/* After reset, all stats should be zero */
+	if (stats.ipackets != 0 || stats.opackets != 0 ||
+	    stats.ibytes != 0 || stats.obytes != 0 ||
+	    stats.ierrors != 0 || stats.oerrors != 0) {
+		printf("Error: port %d stats are not zero after reset\n", port);
+		return -1;
+	}
+
+	printf("Stats reset successful for port %d\n", port);
+	return 0;
+}
+
+static int
+test_tap_link_status(int port)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	printf("Testing TAP PMD link status for port %d\n", port);
+
+	ret = rte_eth_link_get_nowait(port, &link);
+	if (ret < 0) {
+		printf("Error: failed to get link status for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d link: status=%s speed=%u duplex=%s\n",
+	       port,
+	       link.link_status ? "up" : "down",
+	       link.link_speed,
+	       link.link_duplex ? "full" : "half");
+
+	return 0;
+}
+
+static int
+test_tap_dev_info(int port)
+{
+	struct rte_eth_dev_info dev_info;
+	int ret;
+
+	printf("Testing TAP PMD dev_info for port %d\n", port);
+
+	ret = rte_eth_dev_info_get(port, &dev_info);
+	if (ret != 0) {
+		printf("Error: failed to get dev info for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d device info:\n", port);
+	printf("  driver_name: %s\n", dev_info.driver_name);
+	printf("  if_index: %u\n", dev_info.if_index);
+	printf("  max_rx_queues: %u\n", dev_info.max_rx_queues);
+	printf("  max_tx_queues: %u\n", dev_info.max_tx_queues);
+	printf("  max_rx_pktlen: %u\n", dev_info.max_rx_pktlen);
+
+	/* Verify this is indeed a TAP device */
+	if (strcmp(dev_info.driver_name, "net_tap") != 0 &&
+	    strcmp(dev_info.driver_name, "net_tun") != 0) {
+		printf("Warning: unexpected driver name: %s\n",
+		       dev_info.driver_name);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_mtu(int port)
+{
+	uint16_t mtu;
+	int ret;
+
+	printf("Testing TAP PMD MTU operations for port %d\n", port);
+
+	/* Get current MTU */
+	ret = rte_eth_dev_get_mtu(port, &mtu);
+	if (ret != 0) {
+		printf("Error: failed to get MTU for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Current MTU for port %d: %u\n", port, mtu);
+
+	/* Try to set a new MTU */
+	ret = rte_eth_dev_set_mtu(port, 1400);
+	if (ret != 0) {
+		printf("Warning: failed to set MTU to 1400 for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		/* Not a fatal error - may require privileges */
+	} else {
+		printf("MTU set to 1400 for port %d\n", port);
+
+		/* Restore original MTU */
+		ret = rte_eth_dev_set_mtu(port, mtu);
+		if (ret != 0)
+			printf("Warning: failed to restore MTU for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_mac_addr(int port)
+{
+	struct rte_ether_addr mac_addr;
+	int ret;
+
+	printf("Testing TAP PMD MAC address for port %d\n", port);
+
+	ret = rte_eth_macaddr_get(port, &mac_addr);
+	if (ret != 0) {
+		printf("Error: failed to get MAC address for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+
+	printf("Port %d MAC address: " RTE_ETHER_ADDR_PRT_FMT "\n",
+	       port, RTE_ETHER_ADDR_BYTES(&mac_addr));
+
+	return 0;
+}
+
+static int
+test_tap_promiscuous(int port)
+{
+	int ret;
+	int promisc_enabled;
+
+	printf("Testing TAP PMD promiscuous mode for port %d\n", port);
+
+	/* Get current promiscuous state */
+	promisc_enabled = rte_eth_promiscuous_get(port);
+	printf("Promiscuous mode initially %s for port %d\n",
+	       promisc_enabled ? "enabled" : "disabled", port);
+
+	/* Enable promiscuous mode */
+	ret = rte_eth_promiscuous_enable(port);
+	if (ret != 0) {
+		printf("Warning: failed to enable promiscuous mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_promiscuous_get(port) != 1) {
+			printf("Error: promiscuous mode not enabled after enable call\n");
+			return -1;
+		}
+		printf("Promiscuous mode enabled for port %d\n", port);
+	}
+
+	/* Disable promiscuous mode */
+	ret = rte_eth_promiscuous_disable(port);
+	if (ret != 0) {
+		printf("Warning: failed to disable promiscuous mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_promiscuous_get(port) != 0) {
+			printf("Error: promiscuous mode not disabled after disable call\n");
+			return -1;
+		}
+		printf("Promiscuous mode disabled for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_allmulti(int port)
+{
+	int ret;
+	int allmulti_enabled;
+
+	printf("Testing TAP PMD allmulticast mode for port %d\n", port);
+
+	/* Get current allmulticast state */
+	allmulti_enabled = rte_eth_allmulticast_get(port);
+	printf("Allmulticast mode initially %s for port %d\n",
+	       allmulti_enabled ? "enabled" : "disabled", port);
+
+	/* Enable allmulticast mode */
+	ret = rte_eth_allmulticast_enable(port);
+	if (ret != 0) {
+		printf("Warning: failed to enable allmulticast mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_allmulticast_get(port) != 1) {
+			printf("Error: allmulticast mode not enabled after enable call\n");
+			return -1;
+		}
+		printf("Allmulticast mode enabled for port %d\n", port);
+	}
+
+	/* Disable allmulticast mode */
+	ret = rte_eth_allmulticast_disable(port);
+	if (ret != 0) {
+		printf("Warning: failed to disable allmulticast mode for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		if (rte_eth_allmulticast_get(port) != 0) {
+			printf("Error: allmulticast mode not disabled after disable call\n");
+			return -1;
+		}
+		printf("Allmulticast mode disabled for port %d\n", port);
+	}
+
+	return 0;
+}
+
+static int
+test_tap_queue_start_stop(int port)
+{
+	int ret;
+
+	printf("Testing TAP PMD queue start/stop for port %d\n", port);
+
+	/* Stop RX queue */
+	ret = rte_eth_dev_rx_queue_stop(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to stop RX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("RX queue stopped for port %d\n", port);
+
+	/* Stop TX queue */
+	ret = rte_eth_dev_tx_queue_stop(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to stop TX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("TX queue stopped for port %d\n", port);
+
+	/* Start RX queue */
+	ret = rte_eth_dev_rx_queue_start(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to start RX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("RX queue started for port %d\n", port);
+
+	/* Start TX queue */
+	ret = rte_eth_dev_tx_queue_start(port, 0);
+	if (ret != 0 && ret != -ENOTSUP) {
+		printf("Error: failed to start TX queue for port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("TX queue started for port %d\n", port);
+
+	return 0;
+}
+
+static int
+test_tap_link_up_down(int port)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	printf("Testing TAP PMD link up/down for port %d\n", port);
+
+	/* Set link down */
+	ret = rte_eth_dev_set_link_down(port);
+	if (ret != 0) {
+		printf("Warning: failed to set link down for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		ret = rte_eth_link_get_nowait(port, &link);
+		if (ret == 0)
+			printf("Link status after set_link_down: %s\n",
+			       link.link_status ? "up" : "down");
+	}
+
+	/* Set link up */
+	ret = rte_eth_dev_set_link_up(port);
+	if (ret != 0) {
+		printf("Warning: failed to set link up for port %d: %s\n",
+		       port, rte_strerror(-ret));
+	} else {
+		ret = rte_eth_link_get_nowait(port, &link);
+		if (ret == 0)
+			printf("Link status after set_link_up: %s\n",
+			       link.link_status ? "up" : "down");
+	}
+
+	return 0;
+}
+
+static int
+test_tap_dev_stop_start(int port)
+{
+	int ret;
+
+	printf("Testing TAP PMD device stop/start for port %d\n", port);
+
+	/* Stop the device */
+	ret = rte_eth_dev_stop(port);
+	if (ret != 0) {
+		printf("Error: failed to stop port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Device stopped for port %d\n", port);
+
+	/* Start the device again */
+	ret = rte_eth_dev_start(port);
+	if (ret != 0) {
+		printf("Error: failed to start port %d: %s\n",
+		       port, rte_strerror(-ret));
+		return -1;
+	}
+	printf("Device started for port %d\n", port);
+
+	return 0;
+}
+
+static int
+test_tap_multi_queue(void)
+{
+	struct rte_eth_conf port_conf = { 0 };
+	const uint16_t nb_queues = 2;
+
+	printf("Testing TAP PMD multi-queue configuration\n");
+
+	/* Create a separate mempool for multi-queue test */
+	struct rte_mempool *mq_mp
+		= rte_pktmbuf_pool_create("tap_mq_pool", NB_MBUF, 32, 0,
+					  RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+	if (mq_mp == NULL) {
+		printf("Warning: failed to create mempool for multi-queue test: %s\n",
+		       rte_strerror(rte_errno));
+		return TEST_SKIPPED;
+	}
+
+	/* Create a new TAP device for multi-queue test */
+	int ret = rte_vdev_init("net_tap_mq", "iface=dtap_mq");
+	if (ret < 0) {
+		printf("Warning: failed to create multi-queue TAP device: %s\n",
+		       rte_strerror(-ret));
+		rte_mempool_free(mq_mp);
+		return TEST_SKIPPED;
+	}
+
+	/* Find the port */
+	uint16_t port;
+	RTE_ETH_FOREACH_DEV(port) {
+		char name[RTE_ETH_NAME_MAX_LEN];
+		if (rte_eth_dev_get_name_by_port(port, name) == 0 &&
+		    strstr(name, "net_tap_mq"))
+			goto found;
+	}
+
+	printf("Error: could not find multi-queue TAP device\n");
+	rte_vdev_uninit("net_tap_mq");
+	rte_mempool_free(mq_mp);
+	return TEST_FAILED;
+
+found:
+	/* Configure with multiple queues */
+	ret = rte_eth_dev_configure(port, nb_queues, nb_queues, &port_conf);
+	if (ret < 0) {
+		printf("Warning: multi-queue configure failed: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_SKIPPED;
+	}
+
+	/* Setup TX queues */
+	for (uint16_t q = 0; q < nb_queues; q++) {
+		ret = rte_eth_tx_queue_setup(port, q, RING_SIZE, SOCKET0, NULL);
+		if (ret < 0) {
+			printf("Error: TX queue %u setup failed: %s\n",
+			       q, rte_strerror(-ret));
+			rte_vdev_uninit("net_tap_mq");
+			rte_mempool_free(mq_mp);
+			return TEST_FAILED;
+		}
+	}
+
+	/* Setup RX queues */
+	for (uint16_t q = 0; q < nb_queues; q++) {
+		ret = rte_eth_rx_queue_setup(port, q, RING_SIZE, SOCKET0, NULL, mq_mp);
+		if (ret < 0) {
+			printf("Error: RX queue %u setup failed: %s\n",
+			       q, rte_strerror(-ret));
+			rte_vdev_uninit("net_tap_mq");
+			rte_mempool_free(mq_mp);
+			return TEST_FAILED;
+		}
+	}
+
+	ret = rte_eth_dev_start(port);
+	if (ret < 0) {
+		printf("Error: failed to start multi-queue port: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap_mq");
+		rte_mempool_free(mq_mp);
+		return TEST_FAILED;
+	}
+
+	printf("Multi-queue TAP device configured with %u queues\n", nb_queues);
+
+	/* Cleanup */
+	ret = rte_eth_dev_stop(port);
+	if (ret != 0) {
+		printf("Error: rte_eth_dev_stop failed\n");
+		return TEST_FAILED;
+	}
+	rte_eth_dev_close(port);
+	rte_vdev_uninit("net_tap_mq");
+	rte_mempool_free(mq_mp);
+
+	return TEST_SUCCESS;
+}
+
+static void
+test_tap_cleanup(void)
+{
+	int ret;
+
+	printf("Cleaning up TAP PMD test resources\n");
+
+	if (tap_port0 >= 0) {
+		ret = rte_eth_dev_stop(tap_port0);
+		if (ret != 0)
+			printf("Warning: failed to stop port %d: %s\n",
+			       tap_port0, rte_strerror(-ret));
+		rte_eth_dev_close(tap_port0);
+	}
+
+	if (tap_port1 >= 0) {
+		ret = rte_eth_dev_stop(tap_port1);
+		if (ret != 0)
+			printf("Warning: failed to stop port %d: %s\n",
+			       tap_port1, rte_strerror(-ret));
+		rte_eth_dev_close(tap_port1);
+	}
+
+	rte_vdev_uninit("net_tap0");
+	rte_vdev_uninit("net_tap1");
+
+	if (mp != NULL) {
+		rte_mempool_free(mp);
+		mp = NULL;
+	}
+
+	tap_port0 = -1;
+	tap_port1 = -1;
+}
+
+static int
+test_tap_setup(void)
+{
+	int ret;
+	uint16_t port_id;
+
+	printf("Setting up TAP PMD test\n");
+
+	/* Create mempool */
+	mp = rte_pktmbuf_pool_create("tap_test_pool", NB_MBUF, 32, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+	if (mp == NULL) {
+		printf("Error: failed to create mempool: %s\n",
+		       rte_strerror(rte_errno));
+		return -1;
+	}
+
+	/* Create first TAP device */
+	ret = rte_vdev_init("net_tap0", "iface=dtap_test0");
+	if (ret < 0) {
+		printf("Error: failed to create TAP device net_tap0: %s\n",
+		       rte_strerror(-ret));
+		rte_mempool_free(mp);
+		mp = NULL;
+		return -1;
+	}
+
+	/* Create second TAP device */
+	ret = rte_vdev_init("net_tap1", "iface=dtap_test1");
+	if (ret < 0) {
+		printf("Error: failed to create TAP device net_tap1: %s\n",
+		       rte_strerror(-ret));
+		rte_vdev_uninit("net_tap0");
+		rte_mempool_free(mp);
+		mp = NULL;
+		return -1;
+	}
+
+	/* Find the port IDs */
+	RTE_ETH_FOREACH_DEV(port_id) {
+		char name[RTE_ETH_NAME_MAX_LEN];
+		if (rte_eth_dev_get_name_by_port(port_id, name) != 0)
+			continue;
+
+		if (strstr(name, "net_tap0"))
+			tap_port0 = port_id;
+		else if (strstr(name, "net_tap1"))
+			tap_port1 = port_id;
+	}
+
+	if (tap_port0 < 0 || tap_port1 < 0) {
+		printf("Error: failed to find TAP port IDs\n");
+		test_tap_cleanup();
+		return -1;
+	}
+
+	printf("Created TAP devices: tap_port0=%d, tap_port1=%d\n",
+	       tap_port0, tap_port1);
+
+	return 0;
+}
+
+/* Individual test case wrappers */
+
+static int
+test_tap_rx_queue_setup(void)
+{
+	struct rte_eth_conf port_conf = { 0 };
+	struct rte_mempool *tiny_mp = NULL;
+	int port, ret;
+	int result = TEST_FAILED;
+
+	printf("Testing TAP RX queue setup parameter validation\n");
+
+	/* Create a dedicated TAP device for negative tests */
+	ret = rte_vdev_init("net_tap_neg", "iface=dtap_neg");
+	if (ret < 0) {
+		printf("Warning: failed to create TAP device for negative test: %s\n",
+		       rte_strerror(-ret));
+		return TEST_SKIPPED;
+	}
+
+	/* Find the port */
+	uint16_t port_id;
+	port = -1;
+	RTE_ETH_FOREACH_DEV(port_id) {
+		char name[RTE_ETH_NAME_MAX_LEN];
+		if (rte_eth_dev_get_name_by_port(port_id, name) == 0 &&
+		    strstr(name, "net_tap_neg")) {
+			port = port_id;
+			break;
+		}
+	}
+
+	if (port < 0) {
+		printf("Error: could not find negative test TAP device\n");
+		goto cleanup;
+	}
+
+	ret = rte_eth_dev_configure(port, 1, 1, &port_conf);
+	if (ret < 0) {
+		printf("Error: configure failed for negative test port: %s\n",
+		       rte_strerror(-ret));
+		goto cleanup;
+	}
+
+	/* TX queue is needed since TAP requires nb_rx == nb_tx */
+	ret = rte_eth_tx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL);
+	if (ret < 0) {
+		printf("Error: TX queue setup failed for negative test port: %s\n",
+		       rte_strerror(-ret));
+		goto cleanup;
+	}
+
+	/* Test 1: NULL mempool should fail */
+	printf("  Test: NULL mempool\n");
+	ret = rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, NULL);
+	if (ret == 0) {
+		printf("Error: RX queue setup with NULL mempool should have failed\n");
+		goto cleanup;
+	}
+	printf("    Correctly rejected NULL mempool (ret=%d)\n", ret);
+
+	/* Test 2: Invalid queue ID should fail */
+	printf("  Test: invalid queue ID\n");
+	ret = rte_eth_rx_queue_setup(port, 99, RING_SIZE, SOCKET0, NULL, mp);
+	if (ret == 0) {
+		printf("Error: RX queue setup with invalid queue ID should have failed\n");
+		goto cleanup;
+	}
+	printf("    Correctly rejected invalid queue ID (ret=%d)\n", ret);
+
+	/* Test 3: Mempool with data room too small for headroom should fail.
+	 * Create a pool where data_room_size equals headroom, leaving
+	 * zero usable space in the first segment.
+	 */
+	printf("  Test: mempool with no usable data room\n");
+	tiny_mp = rte_pktmbuf_pool_create("tap_tiny_pool", 64, 0, 0,
+					  RTE_PKTMBUF_HEADROOM, rte_socket_id());
+	if (tiny_mp == NULL) {
+		printf("Warning: could not create tiny mempool: %s\n",
+		       rte_strerror(rte_errno));
+		/* Can still pass on the tests above */
+		printf("  Skipping tiny mempool test\n");
+	} else {
+		ret = rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, tiny_mp);
+		if (ret == 0) {
+			printf("Error: RX queue setup with tiny mempool should have failed\n");
+			goto cleanup;
+		}
+		printf("    Correctly rejected tiny mempool (ret=%d)\n", ret);
+	}
+
+	/* Test 4: Valid setup should succeed after all the negative tests */
+	printf("  Test: valid setup after negative tests\n");
+	ret = rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, mp);
+	if (ret < 0) {
+		printf("Error: valid RX queue setup failed after negative tests: %s\n",
+		       rte_strerror(-ret));
+		goto cleanup;
+	}
+	printf("    Valid setup succeeded\n");
+
+	result = TEST_SUCCESS;
+
+cleanup:
+	rte_eth_dev_close(port);
+	rte_vdev_uninit("net_tap_neg");
+	rte_mempool_free(tiny_mp);
+
+	return result;
+}
+
+static int
+test_tap_configure_port0(void)
+{
+	return test_tap_ethdev_configure(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_configure_port1(void)
+{
+	return test_tap_ethdev_configure(tap_port1) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_packet_send_receive(void)
+{
+	return test_tap_send_receive();
+}
+
+static int
+test_tap_get_stats(void)
+{
+	if (test_tap_stats_get(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_stats_get(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_reset_stats(void)
+{
+	if (test_tap_stats_reset(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_stats_reset(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_get_link_status(void)
+{
+	if (test_tap_link_status(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_link_status(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_get_dev_info(void)
+{
+	if (test_tap_dev_info(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_dev_info(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_mtu_ops(void)
+{
+	return test_tap_mtu(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_mac_addr_get(void)
+{
+	if (test_tap_mac_addr(tap_port0) != 0)
+		return TEST_FAILED;
+	if (test_tap_mac_addr(tap_port1) != 0)
+		return TEST_FAILED;
+	return TEST_SUCCESS;
+}
+
+static int
+test_tap_promisc_mode(void)
+{
+	return test_tap_promiscuous(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_allmulti_mode(void)
+{
+	return test_tap_allmulti(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_queue_ops(void)
+{
+	return test_tap_queue_start_stop(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_link_ops(void)
+{
+	return test_tap_link_up_down(tap_port0) == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_stop_start(void)
+{
+	return test_tap_dev_stop_start(tap_port0) == 0 ?
+	       TEST_SUCCESS : TEST_FAILED;
+}
+
+static int
+test_tap_multiqueue(void)
+{
+	return test_tap_multi_queue();
+}
+
+static int
+test_tap_tx_burst(void)
+{
+	struct rte_eth_stats stats_before, stats_after;
+	struct rte_mbuf *mbuf;
+	uint16_t nb_tx;
+	int ret;
+
+	printf("Testing TAP TX burst with invalid mbuf chains\n");
+
+	ret = rte_eth_stats_reset(tap_port0);
+	if (ret != 0) {
+		printf("Error: stats reset failed: %s\n", rte_strerror(-ret));
+		return TEST_FAILED;
+	}
+
+	/* Test 1: Oversized packet should be rejected.
+	 * Create a single-segment packet larger than MTU + L2 overhead.
+	 */
+	printf("  Test: oversized packet\n");
+	mbuf = rte_pktmbuf_alloc(mp);
+	if (mbuf == NULL) {
+		printf("Error: mbuf alloc failed\n");
+		return TEST_FAILED;
+	}
+
+	/* Fill with data exceeding default MTU (1500) + headers */
+	if (rte_pktmbuf_append(mbuf, 1600) == NULL) {
+		printf("Error: pktmbuf_append failed\n");
+		rte_pktmbuf_free(mbuf);
+		return TEST_FAILED;
+	}
+	memset(rte_pktmbuf_mtod(mbuf, void *), 0, 1600);
+
+	rte_eth_stats_get(tap_port0, &stats_before);
+	nb_tx = rte_eth_tx_burst(tap_port0, 0, &mbuf, 1);
+	rte_eth_stats_get(tap_port0, &stats_after);
+
+	if (nb_tx != 0) {
+		printf("Error: oversized packet was accepted (nb_tx=%u)\n", nb_tx);
+		return TEST_FAILED;
+	}
+	rte_pktmbuf_free(mbuf);
+
+	if (stats_after.oerrors <= stats_before.oerrors) {
+		printf("Error: oerrors not incremented for oversized packet\n");
+		return TEST_FAILED;
+	}
+	printf("    Correctly rejected oversized packet\n");
+
+	/* Test 2: mbuf chain with nb_segs >= IOV_MAX.
+	 * The tun_pi header takes one iovec slot, so a chain with
+	 * IOV_MAX segments requires IOV_MAX + 1 iovecs total,
+	 * which must be rejected by tap_write_mbufs.
+	 */
+	printf("  Test: mbuf chain with nb_segs >= IOV_MAX (%d)\n", IOV_MAX);
+
+	struct rte_mbuf *head = rte_pktmbuf_alloc(mp);
+	if (head == NULL) {
+		printf("Error: head mbuf alloc failed\n");
+		return TEST_FAILED;
+	}
+
+	struct rte_ether_hdr *eth = (struct rte_ether_hdr *)
+		rte_pktmbuf_append(head, RTE_ETHER_MIN_LEN);
+	if (eth == NULL) {
+		printf("Error: append to head failed\n");
+		rte_pktmbuf_free(head);
+		return TEST_FAILED;
+	}
+	memset(eth, 0, RTE_ETHER_MIN_LEN);
+	eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+
+	int nsegs;
+	for (nsegs = 1; nsegs < IOV_MAX; nsegs++) {
+		struct rte_mbuf *seg = rte_pktmbuf_alloc(mp);
+		if (seg == NULL) {
+			printf("Warning: could only allocate %d of %d segments\n",
+			       nsegs, IOV_MAX);
+			break;
+		}
+		if (rte_pktmbuf_append(seg, 1) == NULL) {
+			rte_pktmbuf_free(seg);
+			break;
+		}
+
+		ret = rte_pktmbuf_chain(head, seg);
+		if (ret != 0) {
+			rte_pktmbuf_free(seg);
+			break;
+		}
+	}
+
+	if (head->nb_segs < IOV_MAX) {
+		printf("Warning: only built %u segments, need %d to test IOV_MAX\n",
+		       head->nb_segs, IOV_MAX);
+		rte_pktmbuf_free(head);
+		printf("  Skipping IOV_MAX test (insufficient mbufs)\n");
+		return TEST_SUCCESS;
+	}
+
+	printf("    Built chain with %u segments\n", head->nb_segs);
+
+	rte_eth_stats_get(tap_port0, &stats_before);
+	nb_tx = rte_eth_tx_burst(tap_port0, 0, &head, 1);
+	rte_eth_stats_get(tap_port0, &stats_after);
+
+	if (nb_tx != 0) {
+		printf("Error: chain with %u segments (>= IOV_MAX) should be rejected\n",
+		       head->nb_segs);
+		return TEST_FAILED;
+	}
+	rte_pktmbuf_free(head);
+
+	if (stats_after.oerrors <= stats_before.oerrors) {
+		printf("Error: oerrors not incremented for IOV_MAX chain\n");
+		return TEST_FAILED;
+	}
+	printf("    Correctly rejected chain with nb_segs >= IOV_MAX\n");
+
+	printf("  TX validation tests passed\n");
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite test_pmd_tap_suite = {
+	.setup = test_tap_setup,
+	.teardown = test_tap_cleanup,
+	.suite_name = "TAP PMD Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_tap_configure_port0),
+		TEST_CASE(test_tap_configure_port1),
+		TEST_CASE(test_tap_get_dev_info),
+		TEST_CASE(test_tap_get_link_status),
+		TEST_CASE(test_tap_mac_addr_get),
+		TEST_CASE(test_tap_get_stats),
+		TEST_CASE(test_tap_reset_stats),
+		TEST_CASE(test_tap_packet_send_receive),
+		TEST_CASE(test_tap_promisc_mode),
+		TEST_CASE(test_tap_allmulti_mode),
+		TEST_CASE(test_tap_mtu_ops),
+		TEST_CASE(test_tap_queue_ops),
+		TEST_CASE(test_tap_link_ops),
+		TEST_CASE(test_tap_stop_start),
+		TEST_CASE(test_tap_multiqueue),
+		TEST_CASE(test_tap_rx_queue_setup),
+		TEST_CASE(test_tap_tx_burst),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_pmd_tap(void)
+{
+	return unit_test_suite_runner(&test_pmd_tap_suite);
+}
+
+#endif /* RTE_EXEC_ENV_LINUX */
+
+REGISTER_FAST_TEST(tap_pmd_autotest, NOHUGE_OK, ASAN_OK, test_pmd_tap);
-- 
2.51.0


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

* Re: [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal
  2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
                     ` (18 preceding siblings ...)
  2026-02-22 17:30   ` [PATCH v5 19/19] test: add unit tests for TAP PMD Stephen Hemminger
@ 2026-02-25 23:18   ` Stephen Hemminger
  19 siblings, 0 replies; 82+ messages in thread
From: Stephen Hemminger @ 2026-02-25 23:18 UTC (permalink / raw)
  To: dev

On Sun, 22 Feb 2026 09:30:35 -0800
Stephen Hemminger <stephen@networkplumber.org> wrote:

> Patches 1-2 are documentation and bug fixes that stand alone: update
> the features matrix to match current driver capabilities, and fix
> queue statistics to count all queues regardless of the queue stat
> counter limit.
> 
> Patch 3 fixes interface name buffers to use IFNAMSIZ instead of
> RTE_ETH_NAME_MAX_LEN, since these are Linux kernel interface names
> not DPDK device names.
> 
> Patches 4-6 are minor cleanups: replace the runtime speed capability
> function with a compile-time constant (TAP is always 10G), clarify
> TUN/TAP flag operator precedence with parentheses, and extend the
> fixed MAC index to 16 bits to avoid duplicates after 256 hot-plug
> cycles.
> 
> Patches 7-12 fix bugs tagged for stable: a bounds check to prevent
> an out-of-bounds read on truncated L4 headers; resource leaks in
> the primary and secondary process probe error paths; a missing free
> of the IPC reply buffer on queue count mismatch; a use-after-free
> with an orphaned kernel TC rule when remote flow creation fails; and
> a leaked remote_flow allocation on EEXIST from an implicit rule.
> 
> Patches 13-18 restructure the driver internals: dynamically allocate
> queue structures instead of embedding fixed-size arrays in
> pmd_internals; replace the pointer-to-VLA iovec with a flexible
> array member sized from MTU and mbuf geometry; replace the Tx
> per-packet VLA with a fixed-size stack array capped at 128 segments;
> remove the VLA in flow item validation; consolidate per-queue
> statistics into a common structure; and enable -Wvla warnings.
> 
> Patch 19 adds a unit test suite for the TAP PMD covering
> configuration, link, stats, MTU, MAC, promisc, allmulti, queue
> start/stop, link up/down, stop/start, and multi-queue.
> 
> v5 - use IFNAMSIZ for interface name buffers
>    - dynamically allocate queue structures
>    - compute Rx scatter segments from MTU instead of nb_rx_desc
>    - use flex array for Rx iovecs, fixed stack array for Tx
>    - consolidate queue statistics
> 
> 
> Stephen Hemminger (19):
>   net/tap: fix handling of queue stats
>   doc: update tap features
>   net/tap: use correct length for interface names
>   net/tap: replace runtime speed capability with constant
>   net/tap: clarify TUN/TAP flag assignment
>   net/tap: extend fixed MAC range to 16 bits
>   net/tap: skip checksum on truncated L4 headers
>   net/tap: fix resource leaks in tap create error path
>   net/tap: fix resource leaks in secondary process probe
>   net/tap: free IPC reply buffer on queue count mismatch
>   net/tap: fix use-after-free on remote flow creation failure
>   net/tap: free remote flow when implicit rule already exists
>   net/tap: dynamically allocate queue structures
>   net/tap: remove VLA in flow item validation
>   net/tap: fix Rx descriptor vs scatter segment confusion
>   net/tap: replace use of VLA in transmit burst
>   net/tap: consolidate queue statistics
>   net/tap: enable VLA warnings
>   test: add unit tests for TAP PMD
> 
>  app/test/meson.build             |    1 +
>  app/test/test_pmd_tap.c          | 1143 ++++++++++++++++++++++++++++++
>  doc/guides/nics/features/tap.ini |   14 +-
>  drivers/net/tap/meson.build      |    1 -
>  drivers/net/tap/rte_eth_tap.c    |  411 ++++++-----
>  drivers/net/tap/rte_eth_tap.h    |   28 +-
>  drivers/net/tap/tap_flow.c       |   36 +-
>  7 files changed, 1424 insertions(+), 210 deletions(-)
>  create mode 100644 app/test/test_pmd_tap.c
> 

Applied to next-net.

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

* Re: [RFT 1/4] net/mlx5: fix NULL dereference in Tx queue start
  2026-02-17 15:04   ` [RFT 1/4] net/mlx5: fix NULL dereference in Tx queue start Stephen Hemminger
@ 2026-02-26  9:55     ` Dariusz Sosnowski
  0 siblings, 0 replies; 82+ messages in thread
From: Dariusz Sosnowski @ 2026-02-26  9:55 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev, stable

On Tue, Feb 17, 2026 at 07:04:59AM -0800, Stephen Hemminger wrote:
> mlx5_txq_get() can return NULL for an unconfigured queue index,
> but the result is dereferenced to initialise txq_data before the
> NULL check on the following line.  Move the txq_data assignment
> after the NULL guard.
> 
> Fixes: 6f356d3840e6 ("net/mlx5: pass DevX object info in Tx queue start")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>

Acked-by: Dariusz Sosnowski <dsosnowski@nvidia.com>

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

* Re: [RFT 2/4] net/mlx5: fix counter leak in hairpin queue setup
  2026-02-17 15:05   ` [RFT 2/4] net/mlx5: fix counter leak in hairpin queue setup Stephen Hemminger
@ 2026-02-26  9:57     ` Dariusz Sosnowski
  0 siblings, 0 replies; 82+ messages in thread
From: Dariusz Sosnowski @ 2026-02-26  9:57 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev, stable

On Tue, Feb 17, 2026 at 07:05:00AM -0800, Stephen Hemminger wrote:
> When rxq_obj_modify_counter_set_id() fails in
> mlx5_enable_per_queue_hairpin_counter(), the freshly allocated DevX
> queue counter object is not destroyed before returning.  Destroy it
> on the error path.
> 
> Fixes: f0c0731b6d40 ("net/mlx5: add counters for hairpin drop")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>

Acked-by: Dariusz Sosnowski <dsosnowski@nvidia.com>

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

* Re: [RFT 3/4] net/mlx5: fix use-after-free in ASO management init
  2026-02-17 15:05   ` [RFT 3/4] net/mlx5: fix use-after-free in ASO management init Stephen Hemminger
@ 2026-02-26  9:57     ` Dariusz Sosnowski
  0 siblings, 0 replies; 82+ messages in thread
From: Dariusz Sosnowski @ 2026-02-26  9:57 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev, stable

On Tue, Feb 17, 2026 at 07:05:01AM -0800, Stephen Hemminger wrote:
> mlx5_flow_aso_age_mng_init() and mlx5_flow_aso_ct_mng_init() each
> allocate a management structure, then call mlx5_aso_queue_init().
> If the queue init fails, the structure is freed but the pointer in
> the shared context (sh->aso_age_mng / sh->ct_mng) is not set to
> NULL.
> 
> A subsequent call to the same init function sees the non-NULL
> pointer, skips re-allocation, and returns success, leaving the
> caller operating on freed memory.
> 
> Set the pointer to NULL after freeing in both error paths.
> 
> Fixes: f935ed4b645a ("net/mlx5: support flow hit action for aging")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>

Acked-by: Dariusz Sosnowski <dsosnowski@nvidia.com>

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

* Re: [RFT 4/4] net/mlx5: fix counter truncation in queue counter read
  2026-02-17 15:05   ` [RFT 4/4] net/mlx5: fix counter truncation in queue counter read Stephen Hemminger
@ 2026-02-26  9:58     ` Dariusz Sosnowski
  0 siblings, 0 replies; 82+ messages in thread
From: Dariusz Sosnowski @ 2026-02-26  9:58 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev, stable

On Tue, Feb 17, 2026 at 07:05:02AM -0800, Stephen Hemminger wrote:
> mlx5_read_queue_counter() casts its uint64_t *stat argument to
> uint32_t * before passing it to mlx5_devx_cmd_queue_counter_query().
> The query function writes a single uint32_t, so:
> 
>   - On little-endian (x86): only the low 32 bits of *stat are
>     written; the upper 32 bits retain whatever value they had
>     before the call (stack garbage or a stale value).
>   - On big-endian (PowerPC): the write hits the wrong half of the
>     64-bit word entirely.
> 
> Use a local uint32_t for the query and widen to uint64_t on
> assignment.
> 
> Fixes: f0c0731b6d40 ("net/mlx5: add counters for hairpin drop")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>

Acked-by: Dariusz Sosnowski <dsosnowski@nvidia.com>

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

* Re: [RFT 0/4] net/mlx5: fix several correctness bugs
  2026-02-17 15:04 ` [RFT 0/4] net/mlx5: fix several correctness bugs Stephen Hemminger
                     ` (3 preceding siblings ...)
  2026-02-17 15:05   ` [RFT 4/4] net/mlx5: fix counter truncation in queue counter read Stephen Hemminger
@ 2026-03-01 10:33   ` Raslan Darawsheh
  4 siblings, 0 replies; 82+ messages in thread
From: Raslan Darawsheh @ 2026-03-01 10:33 UTC (permalink / raw)
  To: Stephen Hemminger, dev

Hi,


On 17/02/2026 5:04 PM, Stephen Hemminger wrote:
> Found these during a code review of the mlx5 driver.  I don't have
> ConnectX hardware to test, so sending as RFC for maintainers to
> verify and test.
> 
> Summary:
> 
>    1/4  NULL pointer dereference in mlx5_txq_start() - txq_ctrl is
>         dereferenced before the NULL check one line later.
> 
>    2/4  DevX queue counter leak in hairpin counter setup - the
>         counter object is not freed when the subsequent modify call
>         fails.
> 
>    3/4  Use-after-free in ASO age and CT management init - on queue
>         init failure the management structure is freed but the
>         pointer is not NULLed, so a retry dereferences freed memory.
> 
>    4/4  64-bit counter truncation - uint64_t* cast to uint32_t*
>         leaves the upper 32 bits uninitialised, producing wrong
>         hairpin queue statistics.
> 
> Stephen Hemminger (4):
>    net/mlx5: fix NULL dereference in Tx queue start
>    net/mlx5: fix counter leak in hairpin queue setup
>    net/mlx5: fix use-after-free in ASO management init
>    net/mlx5: fix counter truncation in queue counter read
> 
>   drivers/net/mlx5/mlx5.c         | 13 ++++++++++++-
>   drivers/net/mlx5/mlx5_trigger.c |  3 ++-
>   2 files changed, 14 insertions(+), 2 deletions(-)
> 

Series applied to next-net-mlx,

Kindest regards
Raslan Darawsheh


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

* Re: [PATCH v5 19/19] test: add unit tests for TAP PMD
  2026-02-22 17:30   ` [PATCH v5 19/19] test: add unit tests for TAP PMD Stephen Hemminger
@ 2026-03-16  8:03     ` David Marchand
  0 siblings, 0 replies; 82+ messages in thread
From: David Marchand @ 2026-03-16  8:03 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev, Thomas Monjalon, Luca Boccassi

Hello Stephen,

On Sun, 22 Feb 2026 at 18:34, Stephen Hemminger
<stephen@networkplumber.org> wrote:
>
> Add a standalone test suite for the TAP PMD, modeled on the existing
> test_pmd_ring tests. Exercises device configuration, link status,
> stats, MTU, MAC address, promiscuous/allmulticast modes, queue
> start/stop, link up/down, device stop/start, and multi-queue setup.
>
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>

This new test triggers failures in OBS:

[  373s]  74/107 DPDK:fast-tests / tap_pmd_autotest
     RUNNING
[  373s] >>> LD_LIBRARY_PATH=/usr/src/packages/BUILD/obj-x86_64-linux-gnu/lib:/usr/src/packages/BUILD/obj-x86_64-linux-gnu/drivers
MALLOC_PERTURB_=52 DPDK_TEST=tap_pmd_autotest
/usr/src/packages/BUILD/obj-x86_64-linux-gnu/app/dpdk-test --no-huge
-m 2048 -d /usr/src/packages/BUILD/obj-x86_64-linux-gnu/drivers
[  373s] ―――――――――――――――――――――――――――――――――――――
✀  ―――――――――――――――――――――――――――――――――――――
[  373s] EAL: Detected CPU lcores: 12
[  373s] EAL: Detected NUMA nodes: 1
[  373s] EAL: Detected shared linkage of DPDK
[  373s] EAL: Multi-process socket /tmp/dpdk/rte/mp_socket
[  373s] EAL: Selected IOVA mode 'VA'
[  373s] APP: HPET is not enabled, using TSC as default timer
[  373s] RTE>>tap_pmd_autotest
[  373s] TAP: tun_alloc(): Unable to open /dev/net/tun interface
[  373s] TAP: eth_dev_tap_create(): Unable to create TAP interface
[  373s] TAP: eth_dev_tap_create(): TAP Unable to initialize net_tap0
[  373s]  + ------------------------------------------------------- +
[  373s]  + Test Suite : TAP PMD Unit Test Suite
[  373s] Setting up TAP PMD test
[  373s] Error: failed to create TAP device net_tap0: Invalid argument
[  373s]  + ------------------------------------------------------- +
[  373s]  + Test Suite Summary : TAP PMD Unit Test Suite
[  373s]  + ------------------------------------------------------- +
[  373s]  + Tests Total :       17
[  373s]  + Tests Skipped :      0
[  373s]  + Tests Executed :     0
[  373s]  + Tests Unsupported:   0
[  373s]  + Tests Passed :       0
[  373s]  + Tests Failed :      17
[  373s]  + ------------------------------------------------------- +
[  373s] Test Failed
[  373s] RTE>>――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
[  373s]  74/107 DPDK:fast-tests / tap_pmd_autotest
     FAIL            0.23s   (exit status 255 or signal 127
SIGinvalid)


-- 
David Marchand

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

end of thread, other threads:[~2026-03-16  8:03 UTC | newest]

Thread overview: 82+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-15 19:52 [PATCH 00/10] net/tap: tests, cleanups, and error path fixes Stephen Hemminger
2026-02-15 19:52 ` [PATCH 01/10] test: add unit tests for TAP PMD Stephen Hemminger
2026-02-16  0:46   ` Stephen Hemminger
2026-02-15 19:52 ` [PATCH 02/10] net/tap: replace runtime speed capability with constant Stephen Hemminger
2026-02-15 19:52 ` [PATCH 03/10] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
2026-02-15 21:45   ` Morten Brørup
2026-02-16  0:47     ` Stephen Hemminger
2026-02-16  5:56       ` Morten Brørup
2026-02-15 19:52 ` [PATCH 04/10] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
2026-02-15 19:52 ` [PATCH 05/10] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
2026-02-15 19:52 ` [PATCH 06/10] net/tap: fix resource leaks in tap create error path Stephen Hemminger
2026-02-15 19:52 ` [PATCH 07/10] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
2026-02-15 19:52 ` [PATCH 08/10] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
2026-02-15 19:52 ` [PATCH 09/10] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
2026-02-15 19:52 ` [PATCH 10/10] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
2026-02-16 23:02 ` [PATCH v2 00/11] net/tap: test, cleanups and error path fixes Stephen Hemminger
2026-02-16 23:02   ` [PATCH v2 01/11] test: add unit tests for TAP PMD Stephen Hemminger
2026-02-16 23:02   ` [PATCH v2 02/11] net/tap: replace runtime speed capability with constant Stephen Hemminger
2026-02-16 23:02   ` [PATCH v2 03/11] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
2026-02-16 23:02   ` [PATCH v2 04/11] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
2026-02-16 23:02   ` [PATCH v2 05/11] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
2026-02-16 23:02   ` [PATCH v2 06/11] net/tap: fix resource leaks in tap create error path Stephen Hemminger
2026-02-16 23:02   ` [PATCH v2 07/11] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
2026-02-16 23:02   ` [PATCH v2 08/11] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
2026-02-16 23:02   ` [PATCH v2 09/11] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
2026-02-16 23:02   ` [PATCH v2 10/11] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
2026-02-16 23:02   ` [PATCH v2 11/11] net/tap: track device by ifindex instead of name Stephen Hemminger
2026-02-17  1:28     ` Stephen Hemminger
2026-02-17 15:04 ` [RFT 0/4] net/mlx5: fix several correctness bugs Stephen Hemminger
2026-02-17 15:04   ` [RFT 1/4] net/mlx5: fix NULL dereference in Tx queue start Stephen Hemminger
2026-02-26  9:55     ` Dariusz Sosnowski
2026-02-17 15:05   ` [RFT 2/4] net/mlx5: fix counter leak in hairpin queue setup Stephen Hemminger
2026-02-26  9:57     ` Dariusz Sosnowski
2026-02-17 15:05   ` [RFT 3/4] net/mlx5: fix use-after-free in ASO management init Stephen Hemminger
2026-02-26  9:57     ` Dariusz Sosnowski
2026-02-17 15:05   ` [RFT 4/4] net/mlx5: fix counter truncation in queue counter read Stephen Hemminger
2026-02-26  9:58     ` Dariusz Sosnowski
2026-03-01 10:33   ` [RFT 0/4] net/mlx5: fix several correctness bugs Raslan Darawsheh
2026-02-20  5:02 ` [PATCH v3 00/10] net/tap: bug fixes and add test Stephen Hemminger
2026-02-20  5:02   ` [PATCH v3 01/10] test: add unit tests for TAP PMD Stephen Hemminger
2026-02-20  5:02   ` [PATCH v3 02/10] net/tap: replace runtime speed capability with constant Stephen Hemminger
2026-02-20  5:02   ` [PATCH v3 03/10] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
2026-02-20  5:02   ` [PATCH v3 04/10] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
2026-02-20  5:02   ` [PATCH v3 05/10] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
2026-02-20  5:02   ` [PATCH v3 06/10] net/tap: fix resource leaks in tap create error path Stephen Hemminger
2026-02-20  5:02   ` [PATCH v3 07/10] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
2026-02-20  5:02   ` [PATCH v3 08/10] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
2026-02-20  5:02   ` [PATCH v3 09/10] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
2026-02-20  5:02   ` [PATCH v3 10/10] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
2026-02-20 17:02 ` [PATCH 00/10] net/tap: cleanups and bug fixes Stephen Hemminger
2026-02-20 17:02   ` [PATCH v4 01/10] net/tap: replace runtime speed capability with constant Stephen Hemminger
2026-02-20 17:02   ` [PATCH v4 02/10] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
2026-02-20 17:02   ` [PATCH v4 03/10] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
2026-02-20 17:02   ` [PATCH v4 04/10] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
2026-02-20 17:02   ` [PATCH v4 05/10] net/tap: fix resource leaks in tap create error path Stephen Hemminger
2026-02-20 17:02   ` [PATCH v4 06/10] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
2026-02-20 17:02   ` [PATCH v4 07/10] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
2026-02-20 17:02   ` [PATCH v4 08/10] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
2026-02-20 17:02   ` [PATCH v4 09/10] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
2026-02-20 17:02   ` [PATCH v4 10/10] test: add unit tests for TAP PMD Stephen Hemminger
2026-02-22 17:30 ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 01/19] net/tap: fix handling of queue stats Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 02/19] doc: update tap features Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 03/19] net/tap: use correct length for interface names Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 04/19] net/tap: replace runtime speed capability with constant Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 05/19] net/tap: clarify TUN/TAP flag assignment Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 06/19] net/tap: extend fixed MAC range to 16 bits Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 07/19] net/tap: skip checksum on truncated L4 headers Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 08/19] net/tap: fix resource leaks in tap create error path Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 09/19] net/tap: fix resource leaks in secondary process probe Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 10/19] net/tap: free IPC reply buffer on queue count mismatch Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 11/19] net/tap: fix use-after-free on remote flow creation failure Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 12/19] net/tap: free remote flow when implicit rule already exists Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 13/19] net/tap: dynamically allocate queue structures Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 14/19] net/tap: remove VLA in flow item validation Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 15/19] net/tap: fix Rx descriptor vs scatter segment confusion Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 16/19] net/tap: replace use of VLA in transmit burst Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 17/19] net/tap: consolidate queue statistics Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 18/19] net/tap: enable VLA warnings Stephen Hemminger
2026-02-22 17:30   ` [PATCH v5 19/19] test: add unit tests for TAP PMD Stephen Hemminger
2026-03-16  8:03     ` David Marchand
2026-02-25 23:18   ` [PATCH v5 00/19] net/tap: cleanups, bug fixes, and VLA removal Stephen Hemminger

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