Add a comprehensive test suite for the rtap poll mode driver covering: - Device creation and configuration - Device info query - Link status and link up/down control - Statistics collection and reset - Promiscuous and all-multicast mode toggling - MAC address and MTU configuration - Multi-queue setup, teardown, and queue count reduction - Mismatched rx/tx queue count rejection - Queue reconfiguration (stop, reconfigure, restart) - Rx path: inject packet via AF_PACKET, receive via DPDK - Tx path: transmit via DPDK, capture via AF_PACKET - Multi-segment Tx with chained mbufs - Multi-segment Rx with small-buffer mempool - Offload capability reporting and configuration - Tx UDP checksum offload end-to-end verification - File descriptor leak detection after device close - Command-line created device testing
Tests require CAP_NET_ADMIN or root privileges Signed-off-by: Stephen Hemminger <[email protected]> --- app/test/meson.build | 1 + app/test/test_pmd_rtap.c | 2044 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 2045 insertions(+) create mode 100644 app/test/test_pmd_rtap.c diff --git a/app/test/meson.build b/app/test/meson.build index 48874037eb..5855077066 100644 --- a/app/test/meson.build +++ b/app/test/meson.build @@ -143,6 +143,7 @@ source_file_deps = { 'test_pie.c': ['sched'], 'test_pmd_perf.c': ['ethdev', 'net'] + packet_burst_generator_deps, 'test_pmd_ring.c': ['net_ring', 'ethdev', 'bus_vdev'], + 'test_pmd_rtap.c': ['net_rtap', 'ethdev', 'bus_vdev'], 'test_pmd_ring_perf.c': ['ethdev', 'net_ring', 'bus_vdev'], 'test_pmu.c': ['pmu'], 'test_power.c': ['power', 'power_acpi', 'power_kvm_vm', 'power_intel_pstate', diff --git a/app/test/test_pmd_rtap.c b/app/test/test_pmd_rtap.c new file mode 100644 index 0000000000..5544c21c85 --- /dev/null +++ b/app/test/test_pmd_rtap.c @@ -0,0 +1,2044 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2026 Stephen Hemminger + */ +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stddef.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/in.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/types.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> + +#include <rte_common.h> +#include <rte_stdatomic.h> +#include <rte_ethdev.h> +#include <rte_bus_vdev.h> +#include <rte_ether.h> +#include <rte_errno.h> +#include <rte_mbuf.h> +#include <rte_mempool.h> +#include <rte_ip.h> +#include <rte_udp.h> +#include <rte_epoll.h> + +#include "test.h" + +#define SOCKET0 0 +#define RING_SIZE 256 +#define NB_MBUF 4096 +#define MAX_MULTI_QUEUES 4 + +#define RTAP_DRIVER_NAME "net_rtap" +#define TEST_TAP_NAME "rtap_test0" + +/* TX/RX test parameters */ +#define TEST_PKT_PAYLOAD_LEN 64 +#define TEST_MAGIC_BYTE 0xAB +#define RX_BURST_MAX 32 +#define TX_RX_TIMEOUT_US 100000 /* 100ms */ +#define TX_RX_POLL_US 1000 /* 1ms between polls */ + +static struct rte_mempool *mp; +static int rtap_port0 = -1; +static int rtap_port1 = -1; + +/* ========== Helper Functions ========== */ + +static int +check_rtap_available(void) +{ + int fd = open("/dev/net/tun", O_RDWR); + if (fd < 0) { + printf("Cannot access /dev/net/tun: %s\n", strerror(errno)); + printf("RTAP PMD tests require CAP_NET_ADMIN or root privileges\n"); + return -1; + } + close(fd); + return 0; +} + +/* Configure port with specified number of queue pairs */ +static int +port_configure(int port, uint16_t nb_queues, const struct rte_eth_conf *conf) +{ + struct rte_eth_conf null_conf; + + if (conf == NULL) { + memset(&null_conf, 0, sizeof(null_conf)); + conf = &null_conf; + } + + if (rte_eth_dev_configure(port, nb_queues, nb_queues, conf) < 0) { + printf("Configure failed for port %d with %u queues\n", + port, nb_queues); + return -1; + } + + return 0; +} + +/* Setup queue pairs for a port */ +static int +port_setup_queues(int port, uint16_t nb_queues, uint16_t ring_size, + struct rte_mempool *mempool) +{ + for (uint16_t q = 0; q < nb_queues; q++) { + if (rte_eth_tx_queue_setup(port, q, ring_size, SOCKET0, NULL) < 0) { + printf("TX queue %u setup failed port %d\n", q, port); + return -1; + } + + if (rte_eth_rx_queue_setup(port, q, ring_size, SOCKET0, + NULL, mempool) < 0) { + printf("RX queue %u setup failed port %d\n", q, port); + return -1; + } + } + + return 0; +} + +/* Stop, configure, setup queues, and start a port */ +static int +port_reconfigure(int port, uint16_t nb_queues, const struct rte_eth_conf *conf, + uint16_t ring_size, struct rte_mempool *mempool) +{ + int ret; + + ret = rte_eth_dev_stop(port); + if (ret != 0) { + printf("Error stopping port %d: %s\n", port, rte_strerror(-ret)); + return -1; + } + + if (port_configure(port, nb_queues, conf) < 0) + return -1; + + if (port_setup_queues(port, nb_queues, ring_size, mempool) < 0) + return -1; + + if (rte_eth_dev_start(port) < 0) { + printf("Error starting port %d\n", port); + return -1; + } + + return 0; +} + +/* Restore port to clean single-queue started state */ +static int +restore_single_queue(int port) +{ + return port_reconfigure(port, 1, NULL, RING_SIZE, mp); +} + +/* Verify link status matches expected */ +static int +verify_link_status(int port, uint8_t expected_status) +{ + struct rte_eth_link link; + int ret; + + ret = rte_eth_link_get(port, &link); + if (ret < 0) { + printf("Error getting link status: %s\n", rte_strerror(-ret)); + return -1; + } + + if (link.link_status != expected_status) { + printf("Error: link should be %s but is %s\n", + expected_status ? "UP" : "DOWN", + link.link_status ? "UP" : "DOWN"); + return -1; + } + + return 0; +} + +/* Get device info with error checking */ +static int +get_dev_info(int port, struct rte_eth_dev_info *dev_info) +{ + int ret = rte_eth_dev_info_get(port, dev_info); + if (ret != 0) { + printf("Error getting device info for port %d: %s\n", + port, rte_strerror(-ret)); + return -1; + } + return 0; +} + +/* Reset and verify stats are zero */ +static int +reset_and_verify_stats_zero(int port) +{ + struct rte_eth_stats stats; + int ret; + + ret = rte_eth_stats_reset(port); + if (ret != 0) { + printf("Error: stats_reset failed for port %d: %s\n", + port, rte_strerror(-ret)); + return -1; + } + + ret = rte_eth_stats_get(port, &stats); + if (ret != 0) { + printf("Error: stats_get failed for port %d: %s\n", + port, rte_strerror(-ret)); + return -1; + } + + 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; + } + + return 0; +} + +/* Drain all pending RX packets from a port */ +static void +drain_rx_queue(int port, uint16_t queue_id) +{ + struct rte_mbuf *drain[RX_BURST_MAX]; + uint16_t n; + + do { + n = rte_eth_rx_burst(port, queue_id, drain, RX_BURST_MAX); + rte_pktmbuf_free_bulk(drain, n); + } while (n > 0); +} + +/* Set Ethernet address to broadcast */ +static inline void +eth_addr_bcast(struct rte_ether_addr *addr) +{ + memset(addr, 0xff, RTE_ETHER_ADDR_LEN); +} + +/* Bring TAP interface up using ioctl */ +static int +tap_set_up(const char *ifname) +{ + struct ifreq ifr; + int sock, ret = -1; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, IFNAMSIZ); + + if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) + goto out; + + ifr.ifr_flags |= IFF_UP; + + if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) + goto out; + + ret = 0; +out: + close(sock); + return ret; +} + +/* Open an AF_PACKET socket bound to the TAP interface */ +static int +open_tap_socket(const char *ifname) +{ + int sock, ifindex; + struct sockaddr_ll sll; + + sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (sock < 0) { + printf("socket(AF_PACKET) failed: %s\n", strerror(errno)); + return -1; + } + + ifindex = if_nametoindex(ifname); + if (ifindex == 0) { + printf("if_nametoindex(%s) failed: %s\n", ifname, strerror(errno)); + close(sock); + return -1; + } + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + sll.sll_ifindex = ifindex; + sll.sll_protocol = htons(ETH_P_ALL); + + if (bind(sock, (struct sockaddr *)&sll, sizeof(sll)) < 0) { + printf("bind() failed: %s\n", strerror(errno)); + close(sock); + return -1; + } + + return sock; +} + +/* Setup TAP socket with non-blocking mode and bring interface up */ +static int +setup_tap_socket_nb(const char *ifname) +{ + int sock, flags; + + if (tap_set_up(ifname) < 0) { + printf("Failed to bring TAP interface up\n"); + return -1; + } + + sock = open_tap_socket(ifname); + if (sock < 0) + return -1; + + flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); + + return sock; +} + +/* Build a basic test packet with broadcast dest and magic byte payload */ +static void +build_test_packet(uint8_t *pkt, size_t pkt_len, + const struct rte_ether_addr *src_mac, + const struct rte_ether_addr *dst_mac) +{ + struct rte_ether_hdr *eth = (struct rte_ether_hdr *)pkt; + + if (dst_mac) + memcpy(ð->dst_addr, dst_mac, RTE_ETHER_ADDR_LEN); + else + eth_addr_bcast(ð->dst_addr); + + if (src_mac) + memcpy(ð->src_addr, src_mac, RTE_ETHER_ADDR_LEN); + else + rte_eth_random_addr(eth->src_addr.addr_bytes); + + eth->ether_type = htons(RTE_ETHER_TYPE_IPV4); + memset(pkt + RTE_ETHER_HDR_LEN, TEST_MAGIC_BYTE, + pkt_len - RTE_ETHER_HDR_LEN); +} + +/* Poll AF_PACKET socket for a packet matching the given pattern */ +static ssize_t +poll_tap_socket(int sock, uint8_t *buf, size_t buf_size, + uint8_t match_byte, size_t match_offset) +{ + struct timeval tv; + fd_set fds; + int elapsed = 0; + ssize_t rx_len; + + while (elapsed < TX_RX_TIMEOUT_US) { + FD_ZERO(&fds); + FD_SET(sock, &fds); + tv.tv_sec = 0; + tv.tv_usec = TX_RX_POLL_US; + + if (select(sock + 1, &fds, NULL, NULL, &tv) > 0) { + rx_len = recv(sock, buf, buf_size, 0); + if (rx_len > 0 && (size_t)rx_len > match_offset && + buf[match_offset] == match_byte) + return rx_len; + } + elapsed += TX_RX_POLL_US; + } + + return 0; /* Timeout */ +} + +/* Receive packets from DPDK port, filtering for our test packets */ +static uint16_t +receive_test_packets(int port, uint16_t queue_id, struct rte_mbuf **rx_mbufs, + uint16_t max_pkts, size_t expected_len, uint8_t magic_byte) +{ + uint16_t nb_rx = 0; + int elapsed = 0; + + while (elapsed < TX_RX_TIMEOUT_US && nb_rx < max_pkts) { + struct rte_mbuf *burst[RX_BURST_MAX]; + uint16_t n = rte_eth_rx_burst(port, queue_id, burst, RX_BURST_MAX); + + for (uint16_t i = 0; i < n; i++) { + uint8_t *d = rte_pktmbuf_mtod(burst[i], uint8_t *); + + if (burst[i]->pkt_len == expected_len && + d[RTE_ETHER_HDR_LEN] == magic_byte) { + rx_mbufs[nb_rx++] = burst[i]; + if (nb_rx >= max_pkts) + break; + } else { + rte_pktmbuf_free(burst[i]); + } + } + + if (nb_rx > 0) + break; + + usleep(TX_RX_POLL_US); + elapsed += TX_RX_POLL_US; + } + + return nb_rx; +} + +/* Wait for event with timeout using polling */ +static int +wait_for_event(RTE_ATOMIC(int) *event_flag, int initial_count, int timeout_us) +{ + int elapsed = 0; + + while (elapsed < timeout_us) { + if (rte_atomic_load_explicit(event_flag, rte_memory_order_seq_cst) > initial_count) + return 0; + usleep(TX_RX_POLL_US); + elapsed += TX_RX_POLL_US; + } + + return -1; /* Timeout */ +} + +/* Count open file descriptors */ +static int +count_open_fds(void) +{ + DIR *d; + struct dirent *de; + int count = 0; + + d = opendir("/proc/self/fd"); + if (d == NULL) + return -1; + + while ((de = readdir(d)) != NULL) { + if (de->d_name[0] != '.') + count++; + } + closedir(d); + return count - 1; /* Subtract dirfd itself */ +} + +/* ========== Test Functions ========== */ + +static int +test_ethdev_configure_port(int port) +{ + struct rte_eth_link link; + int ret; + + if (port_reconfigure(port, 1, NULL, RING_SIZE, mp) < 0) + return -1; + + ret = rte_eth_link_get(port, &link); + if (ret < 0) { + printf("Link get failed for port %u: %s\n", + port, rte_strerror(-ret)); + return -1; + } + + return 0; +} + +static int +test_get_stats(int port) +{ + printf("Testing rtap PMD stats_get port %d\n", port); + return reset_and_verify_stats_zero(port); +} + +static int +test_stats_reset(int port) +{ + printf("Testing rtap PMD stats_reset port %d\n", port); + return reset_and_verify_stats_zero(port); +} + +static int +test_dev_info(int port) +{ + struct rte_eth_dev_info dev_info; + + printf("Testing rtap PMD dev_info_get port %d\n", port); + + if (get_dev_info(port, &dev_info) < 0) + return -1; + + if (dev_info.max_rx_queues == 0 || dev_info.max_tx_queues == 0) { + printf("Error: invalid max queue values\n"); + return -1; + } + + if (dev_info.max_mac_addrs != 1) { + printf("Error: expected max_mac_addrs = 1, got %u\n", + dev_info.max_mac_addrs); + return -1; + } + + 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); + + return 0; +} + +static int +test_link_status(int port) +{ + struct rte_eth_link link; + int ret; + + printf("Testing rtap PMD link status port %d\n", port); + + ret = rte_eth_link_get(port, &link); + if (ret < 0) { + printf("Error getting link status for port %d: %s\n", + port, rte_strerror(-ret)); + return -1; + } + + printf(" link_status: %s\n", link.link_status ? "UP" : "DOWN"); + printf(" link_speed: %u\n", link.link_speed); + printf(" link_duplex: %s\n", + link.link_duplex ? "full-duplex" : "half-duplex"); + + return 0; +} + +static int +test_set_link_up_down(int port) +{ + int ret; + + printf("Testing rtap PMD link up/down port %d\n", port); + + ret = rte_eth_dev_set_link_down(port); + if (ret < 0) { + printf("Error setting link down for port %d: %s\n", + port, rte_strerror(-ret)); + return -1; + } + + if (verify_link_status(port, RTE_ETH_LINK_DOWN) < 0) + return -1; + + ret = rte_eth_dev_set_link_up(port); + if (ret < 0) { + printf("Error setting link up for port %d: %s\n", + port, rte_strerror(-ret)); + return -1; + } + + if (verify_link_status(port, RTE_ETH_LINK_UP) < 0) + return -1; + + return 0; +} + +static int +test_promiscuous_mode(int port) +{ + int ret; + + printf("Testing rtap PMD promiscuous mode port %d\n", port); + + ret = rte_eth_promiscuous_enable(port); + if (ret < 0) { + printf("Error enabling promiscuous mode: %s\n", + rte_strerror(-ret)); + return -1; + } + + if (rte_eth_promiscuous_get(port) != 1) { + printf("Error: promiscuous mode should be enabled\n"); + return -1; + } + + ret = rte_eth_promiscuous_disable(port); + if (ret < 0) { + printf("Error disabling promiscuous mode: %s\n", + rte_strerror(-ret)); + return -1; + } + + if (rte_eth_promiscuous_get(port) != 0) { + printf("Error: promiscuous mode should be disabled\n"); + return -1; + } + + return 0; +} + +static int +test_allmulticast_mode(int port) +{ + int ret; + + printf("Testing rtap PMD allmulticast mode port %d\n", port); + + ret = rte_eth_allmulticast_enable(port); + if (ret < 0) { + printf("Error enabling allmulticast mode: %s\n", + rte_strerror(-ret)); + return -1; + } + + if (rte_eth_allmulticast_get(port) != 1) { + printf("Error: allmulticast mode should be enabled\n"); + return -1; + } + + ret = rte_eth_allmulticast_disable(port); + if (ret < 0) { + printf("Error disabling allmulticast mode: %s\n", + rte_strerror(-ret)); + return -1; + } + + if (rte_eth_allmulticast_get(port) != 0) { + printf("Error: allmulticast mode should be disabled\n"); + return -1; + } + + return 0; +} + +static int +test_mac_address(int port) +{ + struct rte_ether_addr mac_addr; + struct rte_ether_addr new_mac = { + .addr_bytes = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55} + }; + int ret; + + printf("Testing rtap PMD MAC address port %d\n", port); + + ret = rte_eth_macaddr_get(port, &mac_addr); + if (ret < 0) { + printf("Error getting MAC address: %s\n", rte_strerror(-ret)); + return -1; + } + + printf(" Current MAC: " RTE_ETHER_ADDR_PRT_FMT "\n", + RTE_ETHER_ADDR_BYTES(&mac_addr)); + + ret = rte_eth_dev_default_mac_addr_set(port, &new_mac); + if (ret < 0) { + printf("Error setting MAC address: %s\n", rte_strerror(-ret)); + return -1; + } + + ret = rte_eth_macaddr_get(port, &mac_addr); + if (ret < 0) { + printf("Error getting MAC address: %s\n", rte_strerror(-ret)); + return -1; + } + + if (!rte_is_same_ether_addr(&mac_addr, &new_mac)) { + printf("Error: MAC address not set correctly\n"); + return -1; + } + + printf(" New MAC: " RTE_ETHER_ADDR_PRT_FMT "\n", + RTE_ETHER_ADDR_BYTES(&mac_addr)); + + return 0; +} + +static int +test_mtu_set(int port) +{ + uint16_t orig_mtu; + uint16_t new_mtu = 9000; + int ret; + + printf("Testing rtap PMD MTU set port %d\n", port); + + ret = rte_eth_dev_get_mtu(port, &orig_mtu); + if (ret < 0) { + printf("Error getting MTU: %s\n", rte_strerror(-ret)); + return -1; + } + + printf(" Original MTU: %u\n", orig_mtu); + + ret = rte_eth_dev_set_mtu(port, new_mtu); + if (ret < 0) { + printf("Warning: setting MTU to %u failed: %s\n", + new_mtu, rte_strerror(-ret)); + return 0; + } + + uint16_t current_mtu; + ret = rte_eth_dev_get_mtu(port, ¤t_mtu); + if (ret < 0) { + printf("Error getting MTU: %s\n", rte_strerror(-ret)); + return -1; + } + + printf(" New MTU: %u\n", current_mtu); + rte_eth_dev_set_mtu(port, orig_mtu); + + return 0; +} + +static int +test_queue_reconfigure(int port) +{ + struct rte_eth_dev_info dev_info; + int ret; + + printf("Testing rtap PMD queue reconfigure port %d\n", port); + + if (port_reconfigure(port, 2, NULL, RING_SIZE, mp) < 0) + return -1; + + ret = rte_eth_dev_info_get(port, &dev_info); + if (ret != 0) { + printf("Error getting device info: %s\n", rte_strerror(-ret)); + return -1; + } + + printf(" Configured with %u rx and %u tx queues\n", + dev_info.nb_rx_queues, dev_info.nb_tx_queues); + + if (restore_single_queue(port) < 0) + return -1; + + return 0; +} + +static int +test_multiqueue(int port) +{ + struct rte_eth_dev_info dev_info; + uint16_t queue_counts[] = { 1, 2, MAX_MULTI_QUEUES }; + + printf("Testing rtap PMD multi-queue port %d\n", port); + + for (unsigned int t = 0; t < RTE_DIM(queue_counts); t++) { + uint16_t nb_queues = queue_counts[t]; + + printf(" Configuring %u queue pair(s)\n", nb_queues); + + if (port_reconfigure(port, nb_queues, NULL, RING_SIZE, mp) < 0) + return -1; + + if (get_dev_info(port, &dev_info) < 0) + return -1; + + if (dev_info.nb_rx_queues != nb_queues || + dev_info.nb_tx_queues != nb_queues) { + printf("Error: queue count mismatch\n"); + return -1; + } + + if (reset_and_verify_stats_zero(port) < 0) + return -1; + + /* Verify per-queue xstats are zero */ + int num_xstats = rte_eth_xstats_get(port, NULL, 0); + if (num_xstats > 0) { + struct rte_eth_xstat *xstats = malloc(sizeof(*xstats) * num_xstats); + struct rte_eth_xstat_name *xstat_names = + malloc(sizeof(*xstat_names) * num_xstats); + + if (xstats == NULL || xstat_names == NULL) { + free(xstats); + free(xstat_names); + printf("Error: xstats alloc failed\n"); + return -1; + } + + rte_eth_xstats_get_names(port, xstat_names, num_xstats); + rte_eth_xstats_get(port, xstats, num_xstats); + + for (int x = 0; x < num_xstats; x++) { + if (strstr(xstat_names[x].name, "_q") != NULL && + xstats[x].value != 0) { + printf("Error: xstat %s = %" PRIu64 " not zero\n", + xstat_names[x].name, xstats[x].value); + free(xstats); + free(xstat_names); + return -1; + } + } + free(xstats); + free(xstat_names); + } + + if (verify_link_status(port, RTE_ETH_LINK_UP) < 0) + return -1; + + printf(" %u queue pair(s): OK\n", nb_queues); + } + + if (restore_single_queue(port) < 0) { + printf("Error restoring single queue\n"); + return -1; + } + + return 0; +} + +static int +test_multiqueue_reduce(int port) +{ + printf("Testing rtap PMD queue reduction port %d\n", port); + + if (port_reconfigure(port, MAX_MULTI_QUEUES, NULL, RING_SIZE, mp) < 0) + return -1; + + printf(" Started with %d queues, reducing to 2\n", MAX_MULTI_QUEUES); + + if (port_reconfigure(port, 2, NULL, RING_SIZE, mp) < 0) + return -1; + + if (reset_and_verify_stats_zero(port) < 0) + return -1; + + printf(" Reduced to 2 queues: OK\n"); + printf(" Reducing to 1 queue\n"); + + if (restore_single_queue(port) < 0) + return -1; + + if (reset_and_verify_stats_zero(port) < 0) + return -1; + + printf(" Reduced to 1 queue: OK\n"); + return 0; +} + +static int +test_multiqueue_mismatch(int port) +{ + int ret; + struct { uint16_t rx; uint16_t tx; } mismatch[] = { + { 1, 2 }, { 2, 1 }, { 4, 2 }, { 1, 4 }, + }; + + printf("Testing rtap PMD mismatched queue rejection port %d\n", port); + + ret = rte_eth_dev_stop(port); + if (ret != 0) { + printf("Error stopping port: %s\n", rte_strerror(-ret)); + return -1; + } + + for (unsigned int i = 0; i < RTE_DIM(mismatch); i++) { + struct rte_eth_conf null_conf; + memset(&null_conf, 0, sizeof(null_conf)); + + ret = rte_eth_dev_configure(port, mismatch[i].rx, + mismatch[i].tx, &null_conf); + if (ret == 0) { + printf("Error: configure(%u rx, %u tx) should fail\n", + mismatch[i].rx, mismatch[i].tx); + rte_eth_dev_stop(port); + return -1; + } + printf(" Rejected %u rx / %u tx: OK\n", + mismatch[i].rx, mismatch[i].tx); + } + + if (restore_single_queue(port) < 0) { + printf("Error restoring single queue\n"); + return -1; + } + + return 0; +} + +static int +test_rx_inject(int port) +{ + struct rte_ether_addr mac; + struct rte_mbuf *rx_mbufs[RX_BURST_MAX]; + uint8_t pkt[RTE_ETHER_HDR_LEN + TEST_PKT_PAYLOAD_LEN]; + int sock = -1; + uint16_t nb_rx; + int ret = -1; + + printf("Testing rtap PMD RX (inject via AF_PACKET)\n"); + + if (restore_single_queue(port) < 0) { + printf("Failed to restore single queue config\n"); + return -1; + } + + if (rte_eth_macaddr_get(port, &mac) < 0) { + printf("Failed to get MAC address\n"); + return -1; + } + + sock = setup_tap_socket_nb(TEST_TAP_NAME); + if (sock < 0) + return -1; + + build_test_packet(pkt, sizeof(pkt), NULL, &mac); + drain_rx_queue(port, 0); + rte_eth_stats_reset(port); + + if (send(sock, pkt, sizeof(pkt), 0) < 0) { + printf("send() failed: %s\n", strerror(errno)); + goto out; + } + + nb_rx = receive_test_packets(port, 0, rx_mbufs, 1, sizeof(pkt), + TEST_MAGIC_BYTE); + + if (nb_rx == 0) { + printf("No packet received after %d us\n", TX_RX_TIMEOUT_US); + goto out; + } + + uint8_t *rx_data = rte_pktmbuf_mtod(rx_mbufs[0], uint8_t *); + if (rx_data[RTE_ETHER_HDR_LEN] != TEST_MAGIC_BYTE) { + printf("Payload mismatch\n"); + goto free_rx; + } + + struct rte_eth_stats stats; + rte_eth_stats_get(port, &stats); + if (stats.ipackets == 0) { + printf("RX stats not updated\n"); + goto free_rx; + } + + printf(" RX inject test PASSED (received %u packets)\n", nb_rx); + ret = 0; + +free_rx: + for (uint16_t i = 0; i < nb_rx; i++) + rte_pktmbuf_free(rx_mbufs[i]); +out: + close(sock); + return ret; +} + +static int +test_tx_capture(int port) +{ + struct rte_ether_addr mac; + struct rte_mbuf *tx_mbuf; + struct rte_ether_hdr *eth; + uint8_t rx_buf[256]; + int sock = -1; + uint16_t nb_tx; + ssize_t rx_len; + int ret = -1; + + printf("Testing rtap PMD TX (capture via AF_PACKET)\n"); + + if (restore_single_queue(port) < 0) { + printf("Failed to restore single queue config\n"); + return -1; + } + + if (rte_eth_macaddr_get(port, &mac) < 0) { + printf("Failed to get MAC address\n"); + return -1; + } + + sock = setup_tap_socket_nb(TEST_TAP_NAME); + if (sock < 0) + return -1; + + tx_mbuf = rte_pktmbuf_alloc(mp); + if (tx_mbuf == NULL) { + printf("Failed to allocate mbuf\n"); + goto out; + } + + eth = rte_pktmbuf_mtod(tx_mbuf, struct rte_ether_hdr *); + eth_addr_bcast(ð->dst_addr); + memcpy(ð->src_addr, &mac, RTE_ETHER_ADDR_LEN); + eth->ether_type = htons(RTE_ETHER_TYPE_IPV4); + + uint8_t *payload = (uint8_t *)(eth + 1); + memset(payload, TEST_MAGIC_BYTE, TEST_PKT_PAYLOAD_LEN); + + tx_mbuf->data_len = RTE_ETHER_HDR_LEN + TEST_PKT_PAYLOAD_LEN; + tx_mbuf->pkt_len = tx_mbuf->data_len; + + rte_eth_stats_reset(port); + + nb_tx = rte_eth_tx_burst(port, 0, &tx_mbuf, 1); + if (nb_tx != 1) { + printf("TX failed\n"); + rte_pktmbuf_free(tx_mbuf); + goto out; + } + + rx_len = poll_tap_socket(sock, rx_buf, sizeof(rx_buf), + TEST_MAGIC_BYTE, RTE_ETHER_HDR_LEN); + + if (rx_len <= 0) { + printf("No packet captured after %d us\n", TX_RX_TIMEOUT_US); + goto out; + } + + struct rte_eth_stats stats; + rte_eth_stats_get(port, &stats); + if (stats.opackets == 0) { + printf("TX stats not updated\n"); + goto out; + } + + printf(" TX capture test PASSED (captured %zd bytes)\n", rx_len); + ret = 0; + +out: + close(sock); + return ret; +} + +#define MSEG_NUM_SEGS 3 +#define MSEG_SEG_LEN 40 + +static int +test_tx_multiseg(int port) +{ + struct rte_ether_addr mac; + struct rte_mbuf *head, *seg, *prev; + struct rte_ether_hdr *eth; + uint8_t rx_buf[512]; + int sock = -1; + uint16_t nb_tx; + ssize_t rx_len; + int ret = -1; + uint16_t total_payload = MSEG_NUM_SEGS * MSEG_SEG_LEN; + + printf("Testing rtap PMD multi-segment TX\n"); + + if (restore_single_queue(port) < 0 || + rte_eth_macaddr_get(port, &mac) < 0) { + printf("Failed to setup test\n"); + return -1; + } + + sock = setup_tap_socket_nb(TEST_TAP_NAME); + if (sock < 0) + return -1; + + head = rte_pktmbuf_alloc(mp); + if (head == NULL) { + printf("Failed to allocate head mbuf\n"); + goto out; + } + + eth = rte_pktmbuf_mtod(head, struct rte_ether_hdr *); + eth_addr_bcast(ð->dst_addr); + memcpy(ð->src_addr, &mac, RTE_ETHER_ADDR_LEN); + eth->ether_type = htons(RTE_ETHER_TYPE_IPV4); + + uint8_t *p = (uint8_t *)(eth + 1); + memset(p, 0xA0, MSEG_SEG_LEN); + head->data_len = RTE_ETHER_HDR_LEN + MSEG_SEG_LEN; + head->pkt_len = RTE_ETHER_HDR_LEN + total_payload; + head->nb_segs = MSEG_NUM_SEGS; + + prev = head; + for (int i = 1; i < MSEG_NUM_SEGS; i++) { + seg = rte_pktmbuf_alloc(mp); + if (seg == NULL) { + printf("Failed to allocate segment %d\n", i); + rte_pktmbuf_free(head); + goto out; + } + p = rte_pktmbuf_mtod(seg, uint8_t *); + memset(p, 0xA0 + i, MSEG_SEG_LEN); + seg->data_len = MSEG_SEG_LEN; + prev->next = seg; + prev = seg; + } + + rte_eth_stats_reset(port); + + nb_tx = rte_eth_tx_burst(port, 0, &head, 1); + if (nb_tx != 1) { + printf("TX failed for multi-seg packet\n"); + rte_pktmbuf_free(head); + goto out; + } + + rx_len = poll_tap_socket(sock, rx_buf, sizeof(rx_buf), + 0xA0, RTE_ETHER_HDR_LEN); + + if (rx_len <= 0) { + printf("No packet captured\n"); + goto out; + } + + for (int i = 0; i < MSEG_NUM_SEGS; i++) { + int off = RTE_ETHER_HDR_LEN + i * MSEG_SEG_LEN; + uint8_t expected = 0xA0 + i; + if (rx_buf[off] != expected) { + printf("Segment %d mismatch\n", i); + goto out; + } + } + + struct rte_eth_stats stats; + rte_eth_stats_get(port, &stats); + if (stats.opackets == 0) { + printf("TX stats not updated\n"); + goto out; + } + + printf(" Multi-seg TX test PASSED (%d segs, captured %zd bytes)\n", + MSEG_NUM_SEGS, rx_len); + ret = 0; + +out: + close(sock); + return ret; +} + +#define MSEG_RX_BUF_SIZE 256 +#define MSEG_RX_POOL_SIZE 4096 +#define MSEG_RX_PKT_PAYLOAD 200 + +static int +test_rx_multiseg(int port) +{ + struct rte_mempool *small_mp = NULL; + struct rte_ether_addr mac; + struct rte_mbuf *rx_mbufs[RX_BURST_MAX]; + uint8_t pkt[RTE_ETHER_HDR_LEN + MSEG_RX_PKT_PAYLOAD]; + int sock = -1; + uint16_t nb_rx; + int ret = -1; + + printf("Testing rtap PMD multi-segment RX\n"); + + if (rte_eth_macaddr_get(port, &mac) < 0) { + printf("Failed to get MAC address\n"); + return -1; + } + + small_mp = rte_pktmbuf_pool_create("small_mbuf_pool", + MSEG_RX_POOL_SIZE, 32, 0, MSEG_RX_BUF_SIZE, + rte_socket_id()); + if (small_mp == NULL) { + printf("Failed to create small mempool\n"); + return -1; + } + + if (port_reconfigure(port, 1, NULL, RING_SIZE, small_mp) < 0) + goto free_pool; + + sock = setup_tap_socket_nb(TEST_TAP_NAME); + if (sock < 0) + goto restore; + + drain_rx_queue(port, 0); + + build_test_packet(pkt, sizeof(pkt), NULL, &mac); + memset(pkt + RTE_ETHER_HDR_LEN, 0xDD, MSEG_RX_PKT_PAYLOAD); + + if (send(sock, pkt, sizeof(pkt), 0) < 0) { + printf("send() failed: %s\n", strerror(errno)); + goto close_sock; + } + + nb_rx = receive_test_packets(port, 0, rx_mbufs, 1, sizeof(pkt), 0xDD); + + if (nb_rx == 0) { + printf("No packet received\n"); + goto close_sock; + } + + struct rte_mbuf *m = rx_mbufs[0]; + printf(" Received: pkt_len=%u nb_segs=%u\n", m->pkt_len, m->nb_segs); + + if (m->nb_segs < 2) { + printf(" Expected multi-segment mbuf, got %u segments\n", + m->nb_segs); + } + + if (m->pkt_len < sizeof(pkt)) { + printf(" Packet too short: %u < %zu\n", m->pkt_len, sizeof(pkt)); + goto free_rx; + } + + /* Verify payload across segments */ + uint32_t offset = 0; + struct rte_mbuf *seg = m; + uint32_t seg_off = 0; + int payload_ok = 1; + + while (seg != NULL && offset < m->pkt_len) { + if (seg_off >= seg->data_len) { + seg = seg->next; + seg_off = 0; + continue; + } + if (offset >= RTE_ETHER_HDR_LEN && + offset < RTE_ETHER_HDR_LEN + MSEG_RX_PKT_PAYLOAD) { + uint8_t *d = rte_pktmbuf_mtod_offset(seg, uint8_t *, + seg_off); + if (*d != 0xDD) { + printf(" Payload mismatch at offset %u\n", offset); + payload_ok = 0; + break; + } + } + offset++; + seg_off++; + } + + if (!payload_ok) + goto free_rx; + + printf(" Multi-seg RX test PASSED (%u segments)\n", m->nb_segs); + ret = 0; + +free_rx: + for (uint16_t i = 0; i < nb_rx; i++) + rte_pktmbuf_free(rx_mbufs[i]); + +close_sock: + close(sock); + +restore: + restore_single_queue(port); + +free_pool: + rte_mempool_free(small_mp); + return ret; +} + +static int +test_offload_config(int port) +{ + struct rte_eth_dev_info dev_info; + struct rte_eth_conf conf; + int ret; + + printf("Testing rtap PMD offload configuration port %d\n", port); + + if (get_dev_info(port, &dev_info) < 0) + return -1; + + uint64_t expected_tx = RTE_ETH_TX_OFFLOAD_MULTI_SEGS | + RTE_ETH_TX_OFFLOAD_UDP_CKSUM | + RTE_ETH_TX_OFFLOAD_TCP_CKSUM | + RTE_ETH_TX_OFFLOAD_TCP_TSO; + + if ((dev_info.tx_offload_capa & expected_tx) != expected_tx) { + printf("Missing TX offload capabilities\n"); + return -1; + } + + printf(" TX offload capa: 0x%" PRIx64 " OK\n", + dev_info.tx_offload_capa); + + memset(&conf, 0, sizeof(conf)); + conf.txmode.offloads = RTE_ETH_TX_OFFLOAD_UDP_CKSUM | + RTE_ETH_TX_OFFLOAD_TCP_CKSUM; + + ret = port_reconfigure(port, 1, &conf, RING_SIZE, mp); + if (ret < 0) { + printf("Configure with TX offloads failed\n"); + goto restore; + } + + printf(" TX offload configuration: OK\n"); + +restore: + restore_single_queue(port); + return ret; +} + +#define CSUM_PKT_PAYLOAD 32 + +static int +test_tx_csum_offload(int port) +{ + struct rte_ether_addr mac; + struct rte_mbuf *tx_mbuf; + uint8_t rx_buf[256]; + int sock = -1; + uint16_t nb_tx, pkt_len; + ssize_t rx_len = 0; + int ret = -1; + + printf("Testing rtap PMD TX checksum offload\n"); + + if (restore_single_queue(port) < 0 || + rte_eth_macaddr_get(port, &mac) < 0) { + printf("Failed to setup test\n"); + return -1; + } + + sock = setup_tap_socket_nb(TEST_TAP_NAME); + if (sock < 0) + return -1; + + tx_mbuf = rte_pktmbuf_alloc(mp); + if (tx_mbuf == NULL) { + printf("Failed to allocate mbuf\n"); + goto out; + } + + /* Build Eth + IPv4 + UDP + payload */ + struct rte_ether_hdr *eth = rte_pktmbuf_mtod(tx_mbuf, + struct rte_ether_hdr *); + eth_addr_bcast(ð->dst_addr); + memcpy(ð->src_addr, &mac, RTE_ETHER_ADDR_LEN); + eth->ether_type = htons(RTE_ETHER_TYPE_IPV4); + + struct rte_ipv4_hdr *ip = (struct rte_ipv4_hdr *)(eth + 1); + memset(ip, 0, sizeof(*ip)); + ip->version_ihl = 0x45; + ip->total_length = htons(sizeof(*ip) + sizeof(struct rte_udp_hdr) + + CSUM_PKT_PAYLOAD); + ip->time_to_live = 64; + ip->next_proto_id = IPPROTO_UDP; + ip->src_addr = htonl(0x0a000001); + ip->dst_addr = htonl(0x0a000002); + ip->hdr_checksum = 0; + ip->hdr_checksum = rte_ipv4_cksum(ip); + + struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(ip + 1); + udp->src_port = htons(1234); + udp->dst_port = htons(5678); + udp->dgram_len = htons(sizeof(*udp) + CSUM_PKT_PAYLOAD); + udp->dgram_cksum = 0; + + uint8_t *payload = (uint8_t *)(udp + 1); + memset(payload, 0xCC, CSUM_PKT_PAYLOAD); + + pkt_len = sizeof(*eth) + sizeof(*ip) + sizeof(*udp) + CSUM_PKT_PAYLOAD; + tx_mbuf->data_len = pkt_len; + tx_mbuf->pkt_len = pkt_len; + + tx_mbuf->ol_flags = RTE_MBUF_F_TX_IPV4 | RTE_MBUF_F_TX_UDP_CKSUM; + tx_mbuf->l2_len = sizeof(*eth); + tx_mbuf->l3_len = sizeof(*ip); + udp->dgram_cksum = rte_ipv4_phdr_cksum(ip, tx_mbuf->ol_flags); + + rte_eth_stats_reset(port); + + nb_tx = rte_eth_tx_burst(port, 0, &tx_mbuf, 1); + if (nb_tx != 1) { + printf("TX failed\n"); + rte_pktmbuf_free(tx_mbuf); + goto out; + } + + rx_len = poll_tap_socket(sock, rx_buf, sizeof(rx_buf), + 0x45, sizeof(*eth)); + + if (rx_len <= 0) { + printf("No packet captured\n"); + goto out; + } + + unsigned int cksum_off = sizeof(*eth) + sizeof(*ip) + + offsetof(struct rte_udp_hdr, dgram_cksum); + uint16_t captured_cksum; + + memcpy(&captured_cksum, &rx_buf[cksum_off], sizeof(captured_cksum)); + + if (captured_cksum == 0) { + printf(" Warning: UDP checksum is zero\n"); + } else { + printf(" UDP cksum=0x%04x\n", ntohs(captured_cksum)); + } + + struct rte_eth_stats stats; + rte_eth_stats_get(port, &stats); + if (stats.opackets == 0) { + printf("TX stats not updated\n"); + goto out; + } + + printf(" TX csum offload PASSED (captured %zd bytes)\n", rx_len); + ret = 0; + +out: + close(sock); + return ret; +} + +#define FLOOD_RING_SIZE 64 +#define FLOOD_NUM_PKTS 1000 +#define FLOOD_PKT_SIZE 128 + +static int +test_imissed_counter(int port) +{ + struct rte_eth_stats stats_before, stats_after, stats_after_reset; + struct rte_ether_addr mac; + uint8_t pkt[FLOOD_PKT_SIZE]; + int sock = -1; + int ret = -1; + + printf("Testing rtap PMD imissed counter port %d\n", port); + + if (rte_eth_macaddr_get(port, &mac) < 0) { + printf("Failed to get MAC address\n"); + return -1; + } + + if (port_reconfigure(port, 1, NULL, FLOOD_RING_SIZE, mp) < 0) + goto restore; + + sock = setup_tap_socket_nb(TEST_TAP_NAME); + if (sock < 0) + goto restore; + + drain_rx_queue(port, 0); + + ret = rte_eth_stats_reset(port); + if (ret != 0) { + printf("Failed to reset stats: %s\n", rte_strerror(-ret)); + goto close_sock; + } + + ret = rte_eth_stats_get(port, &stats_before); + if (ret != 0) { + printf("Failed to get baseline stats: %s\n", rte_strerror(-ret)); + goto close_sock; + } + + printf(" Flooding with %d packets (ring size %d)\n", + FLOOD_NUM_PKTS, FLOOD_RING_SIZE); + + build_test_packet(pkt, FLOOD_PKT_SIZE, &mac, &mac); + + for (int i = 0; i < FLOOD_NUM_PKTS; i++) { + if (send(sock, pkt, FLOOD_PKT_SIZE, 0) < 0) { + printf("send() failed after %d packets: %s\n", + i, strerror(errno)); + goto close_sock; + } + } + + usleep(100000); /* 100ms */ + + /* Drain whatever we can receive */ + { + struct rte_mbuf *burst[RX_BURST_MAX]; + uint16_t total_rx = 0; + int attempts = 0; + + while (attempts++ < 100) { + uint16_t n = rte_eth_rx_burst(port, 0, burst, RX_BURST_MAX); + if (n > 0) { + rte_pktmbuf_free_bulk(burst, n); + total_rx += n; + } else { + usleep(1000); + } + } + printf(" Received %u packets out of %d sent\n", + total_rx, FLOOD_NUM_PKTS); + } + + ret = rte_eth_stats_get(port, &stats_after); + if (ret != 0) { + printf("Failed to get stats after flood: %s\n", rte_strerror(-ret)); + goto close_sock; + } + + printf(" Stats: ipackets=%"PRIu64" imissed=%"PRIu64"\n", + stats_after.ipackets, stats_after.imissed); + + if (stats_after.ipackets == 0) { + printf(" ERROR: No packets received\n"); + goto close_sock; + } + + if (stats_after.imissed == 0) { + printf(" WARNING: No packets marked as missed\n"); + } else { + printf(" SUCCESS: imissed counter working (%"PRIu64" drops)\n", + stats_after.imissed); + } + + /* Test stats_reset clears imissed counter */ + printf(" Testing stats_reset for imissed counter\n"); + ret = rte_eth_stats_reset(port); + if (ret != 0) { + printf(" ERROR: stats_reset failed: %s\n", rte_strerror(-ret)); + goto close_sock; + } + + ret = rte_eth_stats_get(port, &stats_after_reset); + if (ret != 0) { + printf(" ERROR: stats_get after reset failed: %s\n", rte_strerror(-ret)); + goto close_sock; + } + + if (stats_after_reset.imissed != 0 || stats_after_reset.ipackets != 0) { + printf(" ERROR: stats not reset properly\n"); + goto close_sock; + } + + printf(" Stats reset: OK (all counters zeroed)\n"); + printf(" imissed counter test PASSED\n"); + ret = 0; + +close_sock: + close(sock); + +restore: + restore_single_queue(port); + return ret; +} + +#define LSC_TIMEOUT_US 500000 /* 500ms */ +#define LSC_POLL_US 1000 /* 1ms between polls */ + +#define RXQ_INTR_TIMEOUT_MS 500 /* 500ms */ + +static RTE_ATOMIC(int) lsc_event_count; +static RTE_ATOMIC(int) lsc_last_status; + +static int +test_lsc_callback(uint16_t port_id, enum rte_eth_event_type type, + void *param __rte_unused, void *ret_param __rte_unused) +{ + struct rte_eth_link link; + + if (type != RTE_ETH_EVENT_INTR_LSC) + return 0; + + if (rte_eth_link_get_nowait(port_id, &link) < 0) { + printf(" Link get nowait failed\n"); + return 0; + } + + rte_atomic_store_explicit(&lsc_last_status, link.link_status, rte_memory_order_relaxed); + rte_atomic_fetch_add_explicit(&lsc_event_count, 1, rte_memory_order_seq_cst); + + printf(" LSC event #%d: port %u link %s\n", + rte_atomic_load_explicit(&lsc_event_count, rte_memory_order_relaxed), + port_id, + link.link_status ? "UP" : "DOWN"); + + return 0; +} + +static int +test_lsc_interrupt(int port) +{ + struct rte_eth_conf lsc_conf; + int initial_count; + int ret = -1; + + printf("Testing rtap PMD link state interrupt port %d\n", port); + + memset(&lsc_conf, 0, sizeof(lsc_conf)); + lsc_conf.intr_conf.lsc = 1; + + if (port_reconfigure(port, 1, &lsc_conf, RING_SIZE, mp) < 0) + goto restore; + + ret = rte_eth_dev_callback_register(port, RTE_ETH_EVENT_INTR_LSC, + test_lsc_callback, NULL); + if (ret < 0) { + printf("Failed to register LSC callback: %s\n", + rte_strerror(-ret)); + goto restore; + } + + rte_atomic_store_explicit(&lsc_event_count, 0, rte_memory_order_relaxed); + rte_atomic_store_explicit(&lsc_last_status, -1, rte_memory_order_relaxed); + + if (verify_link_status(port, RTE_ETH_LINK_UP) < 0) { + ret = -1; + goto stop; + } + + printf(" Link is UP, setting link DOWN\n"); + initial_count = rte_atomic_load_explicit(&lsc_event_count, rte_memory_order_seq_cst); + + ret = rte_eth_dev_set_link_down(port); + if (ret < 0) { + printf("Set link down failed: %s\n", rte_strerror(-ret)); + goto stop; + } + + if (wait_for_event(&lsc_event_count, initial_count, LSC_TIMEOUT_US) < 0) { + printf(" No LSC event received for link DOWN after %d us\n", + LSC_TIMEOUT_US); + if (verify_link_status(port, RTE_ETH_LINK_DOWN) < 0) { + ret = -1; + goto stop; + } + printf(" Link status is DOWN (verified via polling)\n"); + } else { + printf(" LSC event received for link DOWN\n"); + if (rte_atomic_load_explicit(&lsc_last_status, rte_memory_order_seq_cst) != RTE_ETH_LINK_DOWN) { + printf(" ERROR: expected DOWN status in callback\n"); + ret = -1; + goto stop; + } + } + + printf(" Setting link UP\n"); + initial_count = rte_atomic_load_explicit(&lsc_event_count, rte_memory_order_seq_cst); + + ret = rte_eth_dev_set_link_up(port); + if (ret < 0) { + printf("Set link up failed: %s\n", rte_strerror(-ret)); + goto stop; + } + + if (wait_for_event(&lsc_event_count, initial_count, LSC_TIMEOUT_US) < 0) { + printf(" No LSC event received for link UP after %d us\n", + LSC_TIMEOUT_US); + if (verify_link_status(port, RTE_ETH_LINK_UP) < 0) { + ret = -1; + goto stop; + } + printf(" Link status is UP (verified via polling)\n"); + } else { + printf(" LSC event received for link UP\n"); + if (rte_atomic_load_explicit(&lsc_last_status, rte_memory_order_seq_cst) != RTE_ETH_LINK_UP) { + printf(" ERROR: expected UP status in callback\n"); + ret = -1; + goto stop; + } + } + + printf(" LSC interrupt test PASSED (total events: %d)\n", + rte_atomic_load_explicit(&lsc_event_count, rte_memory_order_relaxed)); + ret = 0; + +stop: + rte_eth_dev_stop(port); + rte_eth_dev_callback_unregister(port, RTE_ETH_EVENT_INTR_LSC, + test_lsc_callback, NULL); + +restore: + restore_single_queue(port); + return ret; +} + +static int +test_rxq_interrupt(int port) +{ + struct rte_eth_conf rxq_conf; + struct rte_ether_addr mac; + uint8_t pkt[RTE_ETHER_HDR_LEN + TEST_PKT_PAYLOAD_LEN]; + int sock = -1; + int epfd = -1; + int ret = -1; + + printf("Testing rtap PMD RX queue interrupt port %d\n", port); + + if (rte_eth_macaddr_get(port, &mac) < 0) { + printf("Failed to get MAC address\n"); + return -1; + } + + memset(&rxq_conf, 0, sizeof(rxq_conf)); + rxq_conf.intr_conf.rxq = 1; + + if (port_reconfigure(port, 1, &rxq_conf, RING_SIZE, mp) < 0) + goto restore; + + /* Enable interrupt for queue 0 */ + ret = rte_eth_dev_rx_intr_enable(port, 0); + if (ret < 0) { + printf(" rx_intr_enable failed: %s\n", rte_strerror(-ret)); + goto restore; + } + + /* Add queue 0's eventfd to the per-thread epoll set */ + ret = rte_eth_dev_rx_intr_ctl_q(port, RTE_EPOLL_PER_THREAD, + RTE_INTR_EVENT_ADD, 0, NULL); + if (ret < 0) { + printf(" rx_intr_ctl_q(ADD) failed: %s\n", rte_strerror(-ret)); + printf(" (epoll may not be available in this environment)\n"); + epfd = -1; + } else { + epfd = RTE_EPOLL_PER_THREAD; + } + + sock = setup_tap_socket_nb(TEST_TAP_NAME); + if (sock < 0) + goto disable_intr; + + drain_rx_queue(port, 0); + build_test_packet(pkt, sizeof(pkt), NULL, &mac); + + printf(" Injecting test packet\n"); + if (send(sock, pkt, sizeof(pkt), 0) < 0) { + printf("send() failed: %s\n", strerror(errno)); + goto close_sock; + } + + /* Wait for the Rx interrupt via epoll */ + if (epfd != -1) { + struct rte_epoll_event event; + int nfds; + + nfds = rte_epoll_wait(epfd, &event, 1, RXQ_INTR_TIMEOUT_MS); + if (nfds < 0) { + printf(" rte_epoll_wait failed: %s\n", + rte_strerror(-nfds)); + printf(" (Falling back to polling verification)\n"); + } else if (nfds == 0) { + printf(" WARNING: epoll timeout - no Rx interrupt received\n"); + printf(" (This may be expected in some test environments)\n"); + } else { + printf(" Rx interrupt received via epoll: OK\n"); + } + } + + /* Verify the packet actually arrived */ + { + struct rte_mbuf *rx_mbufs[RX_BURST_MAX]; + uint16_t nb_rx; + int elapsed = 0; + + while (elapsed < TX_RX_TIMEOUT_US) { + nb_rx = rte_eth_rx_burst(port, 0, rx_mbufs, RX_BURST_MAX); + if (nb_rx > 0) { + rte_pktmbuf_free_bulk(rx_mbufs, nb_rx); + printf(" Packet received successfully\n"); + break; + } + usleep(TX_RX_POLL_US); + elapsed += TX_RX_POLL_US; + } + + if (nb_rx == 0) { + printf(" ERROR: No packet received\n"); + ret = -1; + goto close_sock; + } + } + + printf(" RX queue interrupt test PASSED\n"); + ret = 0; + +close_sock: + close(sock); + +disable_intr: + rte_eth_dev_rx_intr_disable(port, 0); + + if (epfd != -1) + rte_eth_dev_rx_intr_ctl_q(port, RTE_EPOLL_PER_THREAD, + RTE_INTR_EVENT_DEL, 0, NULL); + +restore: + restore_single_queue(port); + return ret; +} + +static int +test_fd_leak(void) +{ + int fd_before, fd_after; + int port = -1; + int ret; + + printf("Testing rtap PMD file descriptor leak\n"); + + fd_before = count_open_fds(); + if (fd_before < 0) { + printf("Cannot count open fds\n"); + return -1; + } + + printf(" Open fds before: %d\n", fd_before); + + if (rte_vdev_init("net_rtap_fdtest", "iface=rtap_fdtest") < 0) { + printf("Failed to create net_rtap_fdtest\n"); + return -1; + } + + uint16_t p; + RTE_ETH_FOREACH_DEV(p) { + struct rte_eth_dev_info info; + if (rte_eth_dev_info_get(p, &info) != 0) + continue; + if (p == (uint16_t)rtap_port0 || p == (uint16_t)rtap_port1) + continue; + if (strstr(info.driver_name, "rtap") != NULL) { + port = p; + break; + } + } + + if (port < 0) { + printf("Failed to find fd-test port\n"); + rte_vdev_uninit("net_rtap_fdtest"); + return -1; + } + + if (port_reconfigure(port, 2, NULL, RING_SIZE, mp) < 0) + goto cleanup; + + ret = rte_eth_dev_stop(port); + if (ret != 0) + printf("Warning: stop returned %d\n", ret); + + rte_eth_dev_close(port); + rte_vdev_uninit("net_rtap_fdtest"); + + fd_after = count_open_fds(); + printf(" Open fds after: %d\n", fd_after); + + if (fd_after != fd_before) { + printf(" ERROR: fd leak detected: %d fds leaked\n", + fd_after - fd_before); + return -1; + } + + printf(" fd leak test PASSED\n"); + return 0; + +cleanup: + rte_eth_dev_stop(port); + rte_eth_dev_close(port); + rte_vdev_uninit("net_rtap_fdtest"); + return -1; +} + +static void +test_rtap_cleanup(void) +{ + int ret; + + if (rtap_port0 >= 0) { + ret = rte_eth_dev_stop(rtap_port0); + if (ret != 0) + printf("Error: failed to stop port %u: %s\n", + rtap_port0, rte_strerror(-ret)); + rte_eth_dev_close(rtap_port0); + } + + if (rtap_port1 >= 0) { + ret = rte_eth_dev_stop(rtap_port1); + if (ret != 0) + printf("Error: failed to stop port %u: %s\n", + rtap_port1, rte_strerror(-ret)); + rte_eth_dev_close(rtap_port1); + } + + rte_mempool_free(mp); + rte_vdev_uninit("net_rtap0"); + rte_vdev_uninit("net_rtap1"); +} + +static int +test_pmd_rtap_setup(void) +{ + uint16_t nb_ports; + + if (check_rtap_available() < 0) { + printf("RTAP not available, skipping tests\n"); + return TEST_SKIPPED; + } + + nb_ports = rte_eth_dev_count_avail(); + printf("nb_ports before rtap creation=%d\n", (int)nb_ports); + + mp = rte_pktmbuf_pool_create("mbuf_pool", NB_MBUF, 32, + 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id()); + if (mp == NULL) { + printf("Failed to create mempool\n"); + return TEST_FAILED; + } + + if (rte_vdev_init("net_rtap0", "iface=rtap_test0") < 0) { + printf("Failed to create net_rtap0\n"); + rte_mempool_free(mp); + return TEST_FAILED; + } + + if (rte_vdev_init("net_rtap1", "iface=rtap_test1") < 0) { + printf("Failed to create net_rtap1\n"); + rte_vdev_uninit("net_rtap0"); + rte_mempool_free(mp); + return TEST_FAILED; + } + + uint16_t port; + RTE_ETH_FOREACH_DEV(port) { + struct rte_eth_dev_info dev_info; + int ret = rte_eth_dev_info_get(port, &dev_info); + if (ret != 0) + continue; + + if (strstr(dev_info.driver_name, "rtap") != NULL || + strstr(dev_info.driver_name, "RTAP") != NULL) { + if (rtap_port0 < 0) + rtap_port0 = port; + else if (rtap_port1 < 0) + rtap_port1 = port; + } + } + + if (rtap_port0 < 0) { + printf("Failed to find rtap ports\n"); + test_rtap_cleanup(); + return TEST_FAILED; + } + + printf("rtap_port0=%d rtap_port1=%d\n", rtap_port0, rtap_port1); + return TEST_SUCCESS; +} + +static int +test_ethdev_configure_ports(void) +{ + TEST_ASSERT((test_ethdev_configure_port(rtap_port0) == 0), + "test ethdev configure port rtap_port0 failed"); + + if (rtap_port1 >= 0) { + TEST_ASSERT((test_ethdev_configure_port(rtap_port1) == 0), + "test ethdev configure port rtap_port1 failed"); + } + + return TEST_SUCCESS; +} + +static int +test_command_line_rtap_port(void) +{ + int port, cmdl_port = -1; + int ret; + + printf("Testing command line created rtap port\n"); + + RTE_ETH_FOREACH_DEV(port) { + struct rte_eth_dev_info dev_info; + + ret = rte_eth_dev_info_get(port, &dev_info); + if (ret != 0) + continue; + + if (port == rtap_port0 || port == rtap_port1) + continue; + + if (strstr(dev_info.driver_name, "rtap") != NULL || + strstr(dev_info.driver_name, "RTAP") != NULL) { + printf("Found command line rtap port=%d\n", port); + cmdl_port = port; + break; + } + } + + if (cmdl_port != -1) { + TEST_ASSERT((test_ethdev_configure_port(cmdl_port) == 0), + "test ethdev configure cmdl_port failed"); + TEST_ASSERT((test_stats_reset(cmdl_port) == 0), + "test stats reset cmdl_port failed"); + TEST_ASSERT((test_get_stats(cmdl_port) == 0), + "test get stats cmdl_port failed"); + TEST_ASSERT((rte_eth_dev_stop(cmdl_port) == 0), + "test stop cmdl_port failed"); + } + + return TEST_SUCCESS; +} + +/* Test case wrappers */ +#define TEST_CASE_WRAPPER(name, func) \ + static int test_##name##_for_port(void) { \ + TEST_ASSERT(func(rtap_port0) == 0, #name " failed"); \ + return TEST_SUCCESS; \ + } + +TEST_CASE_WRAPPER(get_stats, test_get_stats) +TEST_CASE_WRAPPER(stats_reset, test_stats_reset) +TEST_CASE_WRAPPER(dev_info, test_dev_info) +TEST_CASE_WRAPPER(link_status, test_link_status) +TEST_CASE_WRAPPER(link_up_down, test_set_link_up_down) +TEST_CASE_WRAPPER(promiscuous, test_promiscuous_mode) +TEST_CASE_WRAPPER(allmulticast, test_allmulticast_mode) +TEST_CASE_WRAPPER(mac_address, test_mac_address) +TEST_CASE_WRAPPER(mtu, test_mtu_set) +TEST_CASE_WRAPPER(multiqueue, test_multiqueue) +TEST_CASE_WRAPPER(multiqueue_reduce, test_multiqueue_reduce) +TEST_CASE_WRAPPER(multiqueue_mismatch, test_multiqueue_mismatch) +TEST_CASE_WRAPPER(queue_reconfigure, test_queue_reconfigure) +TEST_CASE_WRAPPER(rx_inject, test_rx_inject) +TEST_CASE_WRAPPER(tx_capture, test_tx_capture) +TEST_CASE_WRAPPER(tx_multiseg, test_tx_multiseg) +TEST_CASE_WRAPPER(rx_multiseg, test_rx_multiseg) +TEST_CASE_WRAPPER(offload_config, test_offload_config) +TEST_CASE_WRAPPER(tx_csum_offload, test_tx_csum_offload) +TEST_CASE_WRAPPER(stats_imissed, test_imissed_counter) +TEST_CASE_WRAPPER(lsc_interrupt, test_lsc_interrupt) +TEST_CASE_WRAPPER(rxq_interrupt, test_rxq_interrupt) + +static int +test_fd_leak_for_port(void) +{ + TEST_ASSERT(test_fd_leak() == 0, "test fd leak failed"); + return TEST_SUCCESS; +} + +static struct +unit_test_suite test_pmd_rtap_suite = { + .setup = test_pmd_rtap_setup, + .teardown = test_rtap_cleanup, + .suite_name = "Test Pmd RTAP Unit Test Suite", + .unit_test_cases = { + TEST_CASE(test_ethdev_configure_ports), + TEST_CASE(test_dev_info_for_port), + TEST_CASE(test_link_status_for_port), + TEST_CASE(test_link_up_down_for_port), + TEST_CASE(test_get_stats_for_port), + TEST_CASE(test_stats_reset_for_port), + TEST_CASE(test_stats_imissed_for_port), + TEST_CASE(test_promiscuous_for_port), + TEST_CASE(test_allmulticast_for_port), + TEST_CASE(test_mac_address_for_port), + TEST_CASE(test_mtu_for_port), + TEST_CASE(test_multiqueue_for_port), + TEST_CASE(test_multiqueue_reduce_for_port), + TEST_CASE(test_multiqueue_mismatch_for_port), + TEST_CASE(test_queue_reconfigure_for_port), + TEST_CASE(test_rx_inject_for_port), + TEST_CASE(test_tx_capture_for_port), + TEST_CASE(test_tx_multiseg_for_port), + TEST_CASE(test_rx_multiseg_for_port), + TEST_CASE(test_offload_config_for_port), + TEST_CASE(test_tx_csum_offload_for_port), + TEST_CASE(test_lsc_interrupt_for_port), + TEST_CASE(test_rxq_interrupt_for_port), + TEST_CASE(test_fd_leak_for_port), + TEST_CASE(test_command_line_rtap_port), + TEST_CASES_END() + } +}; + +static int +test_pmd_rtap(void) +{ + return unit_test_suite_runner(&test_pmd_rtap_suite); +} + +REGISTER_FAST_TEST(rtap_pmd_autotest, NOHUGE_OK, ASAN_OK, test_pmd_rtap); -- 2.51.0

