|SUCCESS| [v4,2/2] test: add test for af_packet

dpdklab at iol.unh.edu dpdklab at iol.unh.edu
Thu Apr 16 21:12:59 CEST 2026


Test-Label: ai-code-review
Test-Status: SUCCESS
https://dpdk.org/patch/163125

_AI Code Review_

=== Patch Review: series.patch (via Claude) ===
Review date: 2026-04-16

Reviewing the AF_PACKET PMD test patch:

Git SHA: Not provided in patch header
Author: Stephen Hemminger <stephen at networkplumber.org>
Subject: [PATCH v4 2/2] test: add test for af_packet

This patch adds a comprehensive test suite for the AF_PACKET PMD,
including device info, link status, TX/RX, loopback, promiscuous mode,
MAC address, MTU, stats, multi-queue, and multi-threaded tests.

> diff --git a/app/test/test_pmd_af_packet.c b/app/test/test_pmd_af_packet.c
> new file mode 100644
> index 0000000000..abd60fe18c
> --- /dev/null
> +++ b/app/test/test_pmd_af_packet.c

[ ... ]

> +/*
> + * Create a TAP interface for testing.
> + * Returns fd on success, -1 on failure.
> + */
> +static int
> +create_tap_interface(const char *name)
> +{
> +	struct ifreq ifr;
> +	int fd, ret;
> +
> +	fd = open("/dev/net/tun", O_RDWR);
> +	if (fd < 0) {
> +		printf("Cannot open /dev/net/tun: %s\n", strerror(errno));
> +		printf("(Are you running as root?)\n");
> +		return -1;
> +	}
> +
> +	memset(&ifr, 0, sizeof(ifr));
> +	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
> +	snprintf(ifr.ifr_name, IFNAMSIZ, "%s", name);
> +
> +	ret = ioctl(fd, TUNSETIFF, &ifr);
> +	if (ret < 0) {
> +		printf("Cannot create TAP interface '%s': %s\n",
> +		       name, strerror(errno));
> +		close(fd);
> +		return -1;
> +	}
> +
> +	/* Bring the interface up */
> +	int sock = socket(AF_INET, SOCK_DGRAM, 0);

Does this code leak the socket on allocation failure? The function
returns -1 without closing sock when socket() succeeds but later
operations fail. If socket() returns >= 0, should sock be closed
before returning -1?

> +	if (sock >= 0) {
> +		memset(&ifr, 0, sizeof(ifr));
> +		snprintf(ifr.ifr_name, IFNAMSIZ, "%s", name);
> +
> +		/* Get current flags */
> +		if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
> +			ifr.ifr_flags |= IFF_UP;
> +			ioctl(sock, SIOCSIFFLAGS, &ifr);
> +		}
> +		close(sock);
> +	}
> +
> +	printf("Created TAP interface '%s'\n", name);
> +	return fd;
> +}

[ ... ]

> +static int
> +create_af_packet_port(const char *name, const char *args, uint16_t *out_port_id)
> +{
> +	int ret;
> +
> +	ret = rte_vdev_init(name, args);
> +	if (ret != 0) {
> +		printf("Failed to create af_packet device '%s': %d\n", name, ret);
> +		return ret;
> +	}
> +
> +	ret = rte_eth_dev_get_port_by_name(name, out_port_id);
> +	if (ret != 0) {
> +		printf("Failed to get port id for '%s': %d\n", name, ret);
> +		rte_vdev_uninit(name);
> +		return ret;
> +	}
> +
> +	return 0;
> +}

[ ... ]

> +static int
> +test_af_packet_setup(void)
> +{
> +	char devargs[256];
> +	int ret;
> +
> +	/* Create mempool for mbufs */
> +	mp = rte_pktmbuf_pool_create("af_packet_test_pool", NUM_MBUFS,
> +				     MBUF_CACHE_SIZE, 0,
> +				     RTE_MBUF_DEFAULT_BUF_SIZE,
> +				     rte_socket_id());
> +	if (mp == NULL) {
> +		printf("Failed to create mempool\n");
> +		return -1;
> +	}
> +
> +	/* Create TAP interface for testing */
> +	tap_fd = create_tap_interface(TAP_DEV_NAME);
> +	if (tap_fd >= 0)
> +		tap_created = true;
> +	else {
> +		printf("TAP interface creation failed - tests will be skipped\n");
> +		return 0; /* Return success to allow skipped tests */
> +	}
> +
> +	/* Create and configure af_packet port */
> +	snprintf(devargs, sizeof(devargs), "iface=%s", TAP_DEV_NAME);
> +	ret = create_af_packet_port(AF_PACKET_DEV_NAME, devargs, &port_id);
> +	if (ret != 0) {
> +		printf("Failed to create af_packet port\n");
> +		return -1;
> +	}
> +	port_created = true;
> +
> +	ret = configure_af_packet_port(port_id, 1, 1);
> +	if (ret != 0) {
> +		printf("Failed to configure af_packet port\n");
> +		return -1;
> +	}

Does test_af_packet_setup() leak resources when configure_af_packet_port()
fails? When returning -1 here, mp, tap_fd, and the created vdev port are
not cleaned up. Should test_af_packet_teardown() be called or should cleanup
be performed before returning -1?

> +	port_started = true;
> +
> +	return 0;
> +}

[ ... ]

> +/*
> + * Test: Loopback - TX packets and receive them back via TAP interface.
> + * This exercises both the TX and RX paths in a round-trip.
> + */
> +static int
> +test_af_packet_loopback(void)
> +{
> +	struct rte_mbuf *tx_bufs[BURST_SIZE];
> +	struct rte_mbuf *rx_bufs[BURST_SIZE];
> +	uint16_t nb_tx, total_rx = 0;
> +	uint64_t elapsed = 0;
> +	unsigned int allocated;
> +	char tap_buf[2048];
> +
> +	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
> +		printf("SKIPPED: Port not available (need root)\n");
> +		return TEST_SKIPPED;
> +	}
> +
> +	/* Set TAP fd to non-blocking for reading */
> +	int flags = fcntl(tap_fd, F_GETFL, 0);

Does test_af_packet_loopback() check the return value of fcntl()? If
fcntl(tap_fd, F_GETFL, 0) returns -1, flags contains -1, and the next
fcntl() call will use invalid flags. Should the return value be checked
before using flags?

> +	fcntl(tap_fd, F_SETFL, flags | O_NONBLOCK);

[ ... ]

> +	/*
> +	 * Read packets from TAP interface (they were sent there by af_packet).
> +	 * Then write them back to TAP so af_packet can receive them.
> +	 */
> +	while (elapsed < LOOPBACK_TIMEOUT_US) {
> +		ssize_t bytes_read, bytes_written;
> +
> +		/* Read from TAP (what af_packet sent) */
> +		bytes_read = read(tap_fd, tap_buf, sizeof(tap_buf));
> +		if (bytes_read > 0) {
> +			/* Write back to TAP for af_packet to receive */
> +			bytes_written = write(tap_fd, tap_buf, bytes_read);
> +			TEST_ASSERT(bytes_read == bytes_written, "TAP write %zd != %zd",
> +				    bytes_read, bytes_written);

Does test_af_packet_loopback() check if write() succeeds? If write()
returns -1, bytes_written will be -1, which is different from bytes_read,
but the error condition is not explicitly handled. Should write() failure
be checked before comparing bytes?

> +		}
> +
> +		/* Try to receive via af_packet RX */
> +		uint16_t nb_rx = rte_eth_rx_burst(port_id, 0, rx_bufs, BURST_SIZE);
> +		if (nb_rx > 0) {
> +			total_rx += nb_rx;
> +			rte_pktmbuf_free_bulk(rx_bufs, nb_rx);
> +		}
> +
> +		/* Check if we've received enough */
> +		if (total_rx >= nb_tx)
> +			break;
> +
> +		rte_delay_us_block(STATS_POLL_INTERVAL_US);
> +		elapsed += STATS_POLL_INTERVAL_US;
> +	}
> +
> +	/* Restore TAP fd flags */
> +	fcntl(tap_fd, F_SETFL, flags);

[ ... ]

> +/*
> + * Multi-threaded test context
> + */
> +struct mt_test_ctx {
> +	uint16_t port_id;
> +	RTE_ATOMIC(uint32_t) stop;
> +	uint32_t rx_count;

Does struct mt_test_ctx need rx_count to be atomic? The rx_count field is
modified in mt_reader_thread() and read in test_af_packet_multithread()
without atomic operations. Should rx_count use RTE_ATOMIC(uint32_t) to
prevent data races?

> +};

[ ... ]


More information about the test-report mailing list