|SUCCESS| [v6,3/3] test: add test for af_packet
dpdklab at iol.unh.edu
dpdklab at iol.unh.edu
Thu Apr 30 01:34:56 CEST 2026
Test-Label: ai-code-review
Test-Status: SUCCESS
https://dpdk.org/patch/163444
_AI Code Review_
=== Patch Review: series.patch (via Claude) ===
Review date: 2026-04-29
# Review of DPDK AF_PACKET PMD Patch Series
git sha: Not available in patch headers
Author: Stephen Hemminger <stephen at networkplumber.org>
Subject: [PATCH v6 1/3] maintainers: nominate myself for af_packet
This patch series updates the AF_PACKET maintainer, fixes indentation
issues, and adds comprehensive unit tests for the AF_PACKET PMD.
## Patch 1/3: MAINTAINERS update
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0f5539f851..723e26dec3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -671,7 +671,7 @@ F: app/test/test_link_bonding*
> F: examples/bond/
>
> Linux AF_PACKET
> -M: John W. Linville <linville at tuxdriver.com>
> +M: Stephen Hemminger <stephen at networkplumber.org>
> F: drivers/net/af_packet/
> F: doc/guides/nics/features/afpacket.ini
No issues found.
## Patch 2/3: net/af_packet: fix indentation
> diff --git a/drivers/net/af_packet/rte_eth_af_packet.c
> b/drivers/net/af_packet/rte_eth_af_packet.c
> index 0ee94e71ea..8303ff5ca9 100644
> --- a/drivers/net/af_packet/rte_eth_af_packet.c
> +++ b/drivers/net/af_packet/rte_eth_af_packet.c
> @@ -79,7 +79,7 @@ struct __rte_cache_aligned pkt_tx_queue {
> };
>
> struct pmd_internals {
> - unsigned nb_queues;
> + unsigned int nb_queues;
Does this change from "unsigned" to "unsigned int" preserve consistency
with the rest of the structure? Looking at the broader context, this
appears correct per DPDK coding standards.
[ ... ]
> @@ -602,7 +602,7 @@ eth_dev_close(struct rte_eth_dev *dev)
>
> static int
> eth_link_update(struct rte_eth_dev *dev,
> - int wait_to_complete __rte_unused)
> + int wait_to_complete __rte_unused)
The indentation fixes correctly replace spaces with tabs throughout the
file. No issues found.
## Patch 3/3: test: add test for af_packet
> diff --git a/app/test/test_pmd_af_packet.c b/app/test/test_pmd_af_packet.c
> new file mode 100644
> index 0000000000..8b36c3b539
> --- /dev/null
> +++ b/app/test/test_pmd_af_packet.c
[ ... ]
> +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 if the ioctl calls fail? The function
returns -1 after close(sock) only in the success path.
> + 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;
> +}
If sock >= 0 but the SIOCGIFFLAGS ioctl fails, does this still close the
socket properly? Looking at the code, close(sock) is always called if
sock >= 0, so this appears correct.
[ ... ]
> +static unsigned int
> +alloc_tx_mbufs(struct rte_mbuf **bufs, unsigned int count)
> +{
> + unsigned int i;
> + int ret;
> +
> + ret = rte_pktmbuf_alloc_bulk(mp, bufs, count);
> + if (ret != 0)
> + return 0;
> +
> + for (i = 0; i < count; i++) {
> + struct rte_ether_hdr *eth_hdr;
> + eth_hdr = (struct rte_ether_hdr *)rte_pktmbuf_append(bufs[i],
> + sizeof(struct rte_ether_hdr) + 46);
> + if (eth_hdr == NULL) {
> + /* Free all allocated mbufs on failure */
> + rte_pktmbuf_free_bulk(bufs, count);
> + return 0;
> + }
Does this code correctly free mbufs when rte_pktmbuf_append() fails for
mbuf i > 0? The code calls rte_pktmbuf_free_bulk(bufs, count), but some
bufs[0..i-1] may have had data appended while bufs[i..count-1] may still
be empty. Is this still safe?
Looking at the DPDK documentation, rte_pktmbuf_free_bulk() should handle
this correctly as it frees all valid mbufs in the array.
[ ... ]
> +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);
> + fcntl(tap_fd, F_SETFL, flags | O_NONBLOCK);
Does this code check the return value of the first fcntl() call? If
fcntl(tap_fd, F_GETFL, 0) fails, flags will contain -1, and the second
fcntl() call will attempt to set invalid flags.
> +
> + /* Allocate and send packets */
> + allocated = alloc_tx_mbufs(tx_bufs, BURST_SIZE);
> + if (allocated == 0) {
> + printf("SKIPPED: Could not allocate mbufs\n");
> + return TEST_SKIPPED;
> + }
Does this restore the TAP fd flags before returning on allocation
failure?
> +
> + nb_tx = do_tx_burst(port_id, 0, tx_bufs, allocated);
> + if (nb_tx == 0) {
> + printf("SKIPPED: No packets transmitted\n");
> + return TEST_SKIPPED;
> + }
Does this restore the TAP fd flags before returning when no packets are
transmitted?
[ ... ]
> + /* Restore TAP fd flags */
> + fcntl(tap_fd, F_SETFL, flags);
The function only restores flags on the success path. Does this leak the
modified fd flags on early returns?
[ ... ]
> +static void *
> +mt_reader_thread(void *arg)
> +{
> + struct mt_test_ctx *ctx = arg;
> + struct rte_mbuf *bufs[BURST_SIZE];
> + uint16_t nb_rx;
> +
> + while (!rte_atomic_load_explicit(&ctx->stop, rte_memory_order_relaxed)) {
> + nb_rx = rte_eth_rx_burst(ctx->port_id, 0, bufs, BURST_SIZE);
> + if (nb_rx > 0) {
> + ctx->rx_count += nb_rx;
Does this code properly synchronize ctx->rx_count updates? The rx_count
field is uint32_t but not declared as atomic. Is there a race condition
between the reader thread updating rx_count and the main thread reading
it?
> + rte_pktmbuf_free_bulk(bufs, nb_rx);
> + }
> +
> + rte_delay_us_block(50);
> + }
> +
> + return NULL;
> +}
[ ... ]
> +static int
> +test_af_packet_multithread(void)
> +{
> + struct mt_test_ctx ctx = {0};
> + struct rte_mbuf *bufs[BURST_SIZE];
> + pthread_t reader_tid;
> + struct timespec start, now;
> + uint32_t tx_count = 0;
> + unsigned int allocated;
> + uint16_t nb_tx;
> + int ret;
> +
> + if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
> + printf("SKIPPED: Port not available (need root)\n");
> + return TEST_SKIPPED;
> + }
> +
> + ctx.port_id = port_id;
> +
> + /* Start reader thread */
> + ret = pthread_create(&reader_tid, NULL, mt_reader_thread, &ctx);
> + if (ret != 0) {
> + printf("SKIPPED: Failed to create reader thread: %d\n", ret);
> + return TEST_SKIPPED;
> + }
> +
> + /* Main thread sends packets for the configured duration */
> + clock_gettime(CLOCK_MONOTONIC, &start);
> + do {
> + allocated = alloc_tx_mbufs(bufs, BURST_SIZE / 4);
> + if (allocated == 0)
> + break;
> +
> + nb_tx = do_tx_burst(port_id, 0, bufs, allocated);
> + tx_count += nb_tx;
> +
> + rte_delay_us_block(100);
> + clock_gettime(CLOCK_MONOTONIC, &now);
> + } while (tx_count < MT_NUM_PACKETS &&
> + (now.tv_sec - start.tv_sec) < MT_TEST_DURATION_SEC);
> +
> + /* Signal reader to stop */
> + rte_atomic_store_explicit(&ctx.stop, 1, rte_memory_order_relaxed);
> + pthread_join(reader_tid, NULL);
> +
> + printf("Multi-threaded test: TX=%u, RX=%u (duration=%ds)\n",
> + tx_count, ctx.rx_count, MT_TEST_DURATION_SEC);
Does reading ctx.rx_count here properly synchronize with the reader
thread's updates? Should there be a memory barrier or should rx_count be
declared as RTE_ATOMIC(uint32_t)?
More information about the test-report
mailing list