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

