From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id E9A09432D9; Wed, 8 Nov 2023 19:36:49 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id AD37642DDE; Wed, 8 Nov 2023 19:36:17 +0100 (CET) Received: from mail-oo1-f50.google.com (mail-oo1-f50.google.com [209.85.161.50]) by mails.dpdk.org (Postfix) with ESMTP id B58E742DAC for ; Wed, 8 Nov 2023 19:36:14 +0100 (CET) Received: by mail-oo1-f50.google.com with SMTP id 006d021491bc7-586753b0ab0so3809479eaf.0 for ; Wed, 08 Nov 2023 10:36:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=networkplumber-org.20230601.gappssmtp.com; s=20230601; t=1699468574; x=1700073374; darn=dpdk.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=ZxBQOrc3DzlSpoSRdVgX3NKVNdfN6wt2OXsbYKLwbLc=; b=JcTFEjGNP1KT86pax71+HeVj5ITdm57Cdte5l2BoYxnquQXh9mYEp0fhALLV5TmXn6 ouMA7RZl9HpR4VL1vN0jZ+bi3fm6+I1E1weu+EY9RT1drVpuyopqU9u2RGP3bzsL+3Vo c3tjObPAQRLBIUhAgEIZH3ag3W369bgBSzqJiEUybQFdUIeS2udaH911psEGbadk8k73 uJKSl1MTlRGD0gWVeLu3QXQeoFoBqNad1oy7ph0yrJppB+D3dVO0frBF0QA4V+dKHgH2 PlrZk/nanhKfIQagkXu+YpaRDtwoKkdwEzdcWiGqlUWciStUNLGeO6X1Vf5wwnLDh7UI qi2Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1699468574; x=1700073374; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ZxBQOrc3DzlSpoSRdVgX3NKVNdfN6wt2OXsbYKLwbLc=; b=TNypumlfEpZxQQknDwHYkaNlYUaZpB8dpCo6ubig7R/Yrdk2288pVyQ8w1Ao+ZBIi7 Ok7SFlj7LiCaKSG40OEZocsK2l4Om4KGgL8kZDQiQH41rkKy9We1Is7ck+/t7GPjmPJU qReFcmZHT+RAEmfECAhOqDHyAD3CVG0W8BVxfNLQTod+80vUXBA4nw7UInRZiS76Va5U KmXzeroIHAHghLsZ0XzHyBHynDI+QyIAiKyAxH44xIFatjs1Pq7IQJGqcGw8doJozRrP kif2QAvm4yTo7+XyFfm1BiUHlKZtgMgRFebIvfIQbHLHV4PqfuJEAcAKsEO7vI4Wu2JT tiLA== X-Gm-Message-State: AOJu0Ywj9Bikus0u3dmYMYkSH7+rmQXQj/9ytwheOYsaOXmYtJ5A8EB8 GjiK1msUlIq2hY6RAIs4vhPak9LhDP9lQn6bj5w= X-Google-Smtp-Source: AGHT+IFsiyASUHX5mKgdeRNPg7G5dPRf1/HM62SpxwWYD49khJ6+9a5rZns3qVBWu6gtPO/0dfZx3w== X-Received: by 2002:a05:6358:63a7:b0:168:e5ee:8fbd with SMTP id k39-20020a05635863a700b00168e5ee8fbdmr2963212rwh.27.1699468573716; Wed, 08 Nov 2023 10:36:13 -0800 (PST) Received: from hermes.local (204-195-123-141.wavecable.com. [204.195.123.141]) by smtp.gmail.com with ESMTPSA id p25-20020a635b19000000b005b944b20f34sm3402522pgb.85.2023.11.08.10.36.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 08 Nov 2023 10:36:12 -0800 (PST) From: Stephen Hemminger To: dev@dpdk.org Cc: Stephen Hemminger Subject: [PATCH v3 5/5] test: cleanups to pcapng test Date: Wed, 8 Nov 2023 10:35:57 -0800 Message-Id: <20231108183557.381955-6-stephen@networkplumber.org> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20231108183557.381955-1-stephen@networkplumber.org> References: <20230921042349.104150-1-stephen@networkplumber.org> <20231108183557.381955-1-stephen@networkplumber.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Overhaul of the pcapng test: - promote it to be a fast test so it gets regularly run. - create null device and use i. - use UDP discard packets that are valid so that for debugging the resulting pcapng file can be looked at with wireshark. - do basic checks on resulting pcap file that lengths and timestamps are in range. - add test for interface options Signed-off-by: Stephen Hemminger --- app/test/meson.build | 2 +- app/test/test_pcapng.c | 418 +++++++++++++++++++++++++++-------------- 2 files changed, 282 insertions(+), 138 deletions(-) diff --git a/app/test/meson.build b/app/test/meson.build index 4183d66b0e9c..dcc93f4a43b4 100644 --- a/app/test/meson.build +++ b/app/test/meson.build @@ -128,7 +128,7 @@ source_file_deps = { 'test_metrics.c': ['metrics'], 'test_mp_secondary.c': ['hash', 'lpm'], 'test_net_ether.c': ['net'], - 'test_pcapng.c': ['ethdev', 'net', 'pcapng'], + 'test_pcapng.c': ['ethdev', 'net', 'pcapng', 'bus_vdev'], 'test_pdcp.c': ['eventdev', 'pdcp', 'net', 'timer', 'security'], 'test_pdump.c': ['pdump'] + sample_packet_forward_deps, 'test_per_lcore.c': [], diff --git a/app/test/test_pcapng.c b/app/test/test_pcapng.c index 55aa2cf93666..c973aa47d1f8 100644 --- a/app/test/test_pcapng.c +++ b/app/test/test_pcapng.c @@ -6,25 +6,34 @@ #include #include +#include #include #include +#include #include #include #include #include +#include +#include +#include +#include #include #include "test.h" -#define NUM_PACKETS 10 -#define DUMMY_MBUF_NUM 3 +#define PCAPNG_TEST_DEBUG 0 + +#define TOTAL_PACKETS 4096 +#define MAX_BURST 64 +#define MAX_GAP_US 100000 +#define DUMMY_MBUF_NUM 3 -static rte_pcapng_t *pcapng; static struct rte_mempool *mp; static const uint32_t pkt_len = 200; static uint16_t port_id; -static char file_name[] = "/tmp/pcapng_test_XXXXXX.pcapng"; +static const char null_dev[] = "net_null0"; /* first mbuf in the packet, should always be at offset 0 */ struct dummy_mbuf { @@ -61,6 +70,7 @@ mbuf1_prepare(struct dummy_mbuf *dm, uint32_t plen) struct { struct rte_ether_hdr eth; struct rte_ipv4_hdr ip; + struct rte_udp_hdr udp; } pkt = { .eth = { .dst_addr.addr_bytes = "\xff\xff\xff\xff\xff\xff", @@ -68,149 +78,201 @@ mbuf1_prepare(struct dummy_mbuf *dm, uint32_t plen) }, .ip = { .version_ihl = RTE_IPV4_VHL_DEF, - .total_length = rte_cpu_to_be_16(plen), - .time_to_live = IPDEFTTL, - .next_proto_id = IPPROTO_RAW, + .time_to_live = 1, + .next_proto_id = IPPROTO_UDP, .src_addr = rte_cpu_to_be_32(RTE_IPV4_LOOPBACK), .dst_addr = rte_cpu_to_be_32(RTE_IPV4_BROADCAST), - } + }, + .udp = { + .dst_port = rte_cpu_to_be_16(9), /* Discard port */ + }, }; memset(dm, 0, sizeof(*dm)); dummy_mbuf_prep(&dm->mb[0], dm->buf[0], sizeof(dm->buf[0]), plen); rte_eth_random_addr(pkt.eth.src_addr.addr_bytes); - memcpy(rte_pktmbuf_mtod(dm->mb, void *), &pkt, RTE_MIN(sizeof(pkt), plen)); + plen -= sizeof(struct rte_ether_hdr); + + pkt.ip.total_length = rte_cpu_to_be_16(plen); + pkt.ip.hdr_checksum = rte_ipv4_cksum(&pkt.ip); + + plen -= sizeof(struct rte_ipv4_hdr); + pkt.udp.src_port = rte_rand(); + pkt.udp.dgram_len = rte_cpu_to_be_16(plen); + + memcpy(rte_pktmbuf_mtod(dm->mb, void *), &pkt, sizeof(pkt)); } static int test_setup(void) { - int tmp_fd; - - port_id = rte_eth_find_next(0); - if (port_id >= RTE_MAX_ETHPORTS) { - fprintf(stderr, "No valid Ether port\n"); - return -1; - } + port_id = rte_eth_dev_count_avail(); - tmp_fd = mkstemps(file_name, strlen(".pcapng")); - if (tmp_fd == -1) { - perror("mkstemps() failure"); - return -1; - } - printf("pcapng: output file %s\n", file_name); - - /* open a test capture file */ - pcapng = rte_pcapng_fdopen(tmp_fd, NULL, NULL, "pcapng_test", NULL); - if (pcapng == NULL) { - fprintf(stderr, "rte_pcapng_fdopen failed\n"); - close(tmp_fd); - return -1; - } - - /* Add interface to the file */ - if (rte_pcapng_add_interface(pcapng, port_id, - NULL, NULL, NULL) != 0) { - fprintf(stderr, "can not add port %u\n", port_id); - return -1; + /* Make a dummy null device to snoop on */ + if (rte_vdev_init(null_dev, NULL) != 0) { + fprintf(stderr, "Failed to create vdev '%s'\n", null_dev); + goto fail; } /* Make a pool for cloned packets */ - mp = rte_pktmbuf_pool_create_by_ops("pcapng_test_pool", IOV_MAX + NUM_PACKETS, - 0, 0, - rte_pcapng_mbuf_size(pkt_len), + mp = rte_pktmbuf_pool_create_by_ops("pcapng_test_pool", + MAX_BURST, 0, 0, + rte_pcapng_mbuf_size(pkt_len) + 128, SOCKET_ID_ANY, "ring_mp_sc"); if (mp == NULL) { fprintf(stderr, "Cannot create mempool\n"); - return -1; + goto fail; } + return 0; + +fail: + rte_vdev_uninit(null_dev); + rte_mempool_free(mp); + return -1; } static int -test_write_packets(void) +fill_pcapng_file(rte_pcapng_t *pcapng, unsigned int num_packets) { - struct rte_mbuf *orig; - struct rte_mbuf *clones[NUM_PACKETS] = { }; struct dummy_mbuf mbfs; - unsigned int i; + struct rte_mbuf *orig; + unsigned int burst_size; + unsigned int count; ssize_t len; /* make a dummy packet */ mbuf1_prepare(&mbfs, pkt_len); - - /* clone them */ orig = &mbfs.mb[0]; - for (i = 0; i < NUM_PACKETS; i++) { - struct rte_mbuf *mc; - mc = rte_pcapng_copy(port_id, 0, orig, mp, pkt_len, - rte_get_tsc_cycles(), 0, NULL); - if (mc == NULL) { - fprintf(stderr, "Cannot copy packet\n"); + for (count = 0; count < num_packets; count += burst_size) { + struct rte_mbuf *clones[MAX_BURST]; + unsigned int i; + + /* put 1 .. MAX_BURST packets in one write call */ + burst_size = rte_rand_max(MAX_BURST) + 1; + for (i = 0; i < burst_size; i++) { + struct rte_mbuf *mc; + + mc = rte_pcapng_copy(port_id, 0, orig, mp, pkt_len, + RTE_PCAPNG_DIRECTION_IN, + NULL); + if (mc == NULL) { + fprintf(stderr, "Cannot copy packet\n"); + return -1; + } + clones[i] = mc; + } + + /* write it to capture file */ + len = rte_pcapng_write_packets(pcapng, clones, burst_size); + rte_pktmbuf_free_bulk(clones, burst_size); + + if (len <= 0) { + fprintf(stderr, "Write of packets failed: %s\n", + rte_strerror(rte_errno)); return -1; } - clones[i] = mc; + + /* Leave a small gap between packets to test for time wrap */ + usleep(rte_rand_max(MAX_GAP_US)); } - /* write it to capture file */ - len = rte_pcapng_write_packets(pcapng, clones, NUM_PACKETS); + return count; +} - rte_pktmbuf_free_bulk(clones, NUM_PACKETS); +static char * +fmt_time(char *buf, size_t size, uint64_t ts_ns) +{ + time_t sec; + size_t len; - if (len <= 0) { - fprintf(stderr, "Write of packets failed\n"); - return -1; - } + sec = ts_ns / NS_PER_S; + len = strftime(buf, size, "%X", localtime(&sec)); + snprintf(buf + len, size - len, ".%09lu", + (unsigned long)(ts_ns % NS_PER_S)); - return 0; + return buf; } -static int -test_write_stats(void) +/* Context for the pcap_loop callback */ +struct pkt_print_ctx { + pcap_t *pcap; + unsigned int count; + uint64_t start_ns; + uint64_t end_ns; +}; + +static void +print_packet(uint64_t ts_ns, const struct rte_ether_hdr *eh, size_t len) { - ssize_t len; + char tbuf[128], src[64], dst[64]; - /* write a statistics block */ - len = rte_pcapng_write_stats(pcapng, port_id, NULL, - 0, 0, 0, - NUM_PACKETS, 0); - if (len <= 0) { - fprintf(stderr, "Write of statistics failed\n"); - return -1; - } - return 0; + fmt_time(tbuf, sizeof(tbuf), ts_ns); + rte_ether_format_addr(dst, sizeof(dst), &eh->dst_addr); + rte_ether_format_addr(src, sizeof(src), &eh->src_addr); + printf("%s: %s -> %s type %x length %zu\n", + tbuf, src, dst, rte_be_to_cpu_16(eh->ether_type), len); } +/* Callback from pcap_loop used to validate packets in the file */ static void -pkt_print(u_char *user, const struct pcap_pkthdr *h, - const u_char *bytes) +parse_pcap_packet(u_char *user, const struct pcap_pkthdr *h, + const u_char *bytes) { - unsigned int *countp = (unsigned int *)user; + struct pkt_print_ctx *ctx = (struct pkt_print_ctx *)user; const struct rte_ether_hdr *eh; - struct tm *tm; - char tbuf[128], src[64], dst[64]; + const struct rte_ipv4_hdr *ip; + uint64_t ns; - tm = localtime(&h->ts.tv_sec); - if (tm == NULL) { - perror("localtime"); - return; + eh = (const struct rte_ether_hdr *)bytes; + ip = (const struct rte_ipv4_hdr *)(eh + 1); + + ctx->count += 1; + + /* The pcap library is misleading in reporting timestamp. + * packet header struct gives timestamp as a timeval (ie. usec); + * but the file is open in nanonsecond mode therefore + * the timestamp is really in timespec (ie. nanoseconds). + */ + ns = h->ts.tv_sec * NS_PER_S + h->ts.tv_usec; + if (ns < ctx->start_ns || ns > ctx->end_ns) { + char tstart[128], tend[128]; + + fmt_time(tstart, sizeof(tstart), ctx->start_ns); + fmt_time(tend, sizeof(tend), ctx->end_ns); + fprintf(stderr, "Timestamp out of range [%s .. %s]\n", + tstart, tend); + goto error; } - if (strftime(tbuf, sizeof(tbuf), "%X", tm) == 0) { - fprintf(stderr, "strftime returned 0!\n"); - return; + if (!rte_is_broadcast_ether_addr(&eh->dst_addr)) { + fprintf(stderr, "Destination is not broadcast\n"); + goto error; } - eh = (const struct rte_ether_hdr *)bytes; - rte_ether_format_addr(dst, sizeof(dst), &eh->dst_addr); - rte_ether_format_addr(src, sizeof(src), &eh->src_addr); - printf("%s.%06lu: %s -> %s type %x length %u\n", - tbuf, (unsigned long)h->ts.tv_usec, - src, dst, rte_be_to_cpu_16(eh->ether_type), h->len); + if (rte_ipv4_cksum(ip) != 0) { + fprintf(stderr, "Bad IPv4 checksum\n"); + goto error; + } + + return; /* packet is normal */ + +error: + print_packet(ns, eh, h->len); + + /* Stop parsing at first error */ + pcap_breakloop(ctx->pcap); +} - *countp += 1; +static uint64_t +current_timestamp(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_REALTIME, &ts); + return rte_timespec_to_ns(&ts); } /* @@ -219,78 +281,162 @@ pkt_print(u_char *user, const struct pcap_pkthdr *h, * but that creates an unwanted dependency. */ static int -test_validate(void) +valid_pcapng_file(const char *file_name, uint64_t started, unsigned int expected) { char errbuf[PCAP_ERRBUF_SIZE]; - unsigned int count = 0; - pcap_t *pcap; + struct pkt_print_ctx ctx = { }; int ret; - pcap = pcap_open_offline(file_name, errbuf); - if (pcap == NULL) { + ctx.start_ns = started; + ctx.end_ns = current_timestamp(); + + ctx.pcap = pcap_open_offline_with_tstamp_precision(file_name, + PCAP_TSTAMP_PRECISION_NANO, + errbuf); + if (ctx.pcap == NULL) { fprintf(stderr, "pcap_open_offline('%s') failed: %s\n", file_name, errbuf); return -1; } - ret = pcap_loop(pcap, 0, pkt_print, (u_char *)&count); - if (ret == 0) - printf("Saw %u packets\n", count); - else + ret = pcap_loop(ctx.pcap, 0, parse_pcap_packet, (u_char *)&ctx); + if (ret != 0) { fprintf(stderr, "pcap_dispatch: failed: %s\n", - pcap_geterr(pcap)); - pcap_close(pcap); + pcap_geterr(ctx.pcap)); + } else if (ctx.count != expected) { + printf("Only %u packets, expected %u\n", + ctx.count, expected); + ret = -1; + } + + pcap_close(ctx.pcap); return ret; } static int -test_write_over_limit_iov_max(void) +test_add_interface(void) { - struct rte_mbuf *orig; - struct rte_mbuf *clones[IOV_MAX + NUM_PACKETS] = { }; - struct dummy_mbuf mbfs; - unsigned int i; - ssize_t len; + char file_name[] = "/tmp/pcapng_test_XXXXXX.pcapng"; + static rte_pcapng_t *pcapng; + int ret, tmp_fd; + uint64_t now = current_timestamp(); - /* make a dummy packet */ - mbuf1_prepare(&mbfs, pkt_len); + tmp_fd = mkstemps(file_name, strlen(".pcapng")); + if (tmp_fd == -1) { + perror("mkstemps() failure"); + goto fail; + } + printf("pcapng: output file %s\n", file_name); - /* clone them */ - orig = &mbfs.mb[0]; - for (i = 0; i < IOV_MAX + NUM_PACKETS; i++) { - struct rte_mbuf *mc; + /* open a test capture file */ + pcapng = rte_pcapng_fdopen(tmp_fd, NULL, NULL, "pcapng_addif", NULL); + if (pcapng == NULL) { + fprintf(stderr, "rte_pcapng_fdopen failed\n"); + close(tmp_fd); + goto fail; + } - mc = rte_pcapng_copy(port_id, 0, orig, mp, pkt_len, - rte_get_tsc_cycles(), 0, NULL); - if (mc == NULL) { - fprintf(stderr, "Cannot copy packet\n"); - return -1; - } - clones[i] = mc; + /* Add interface to the file */ + ret = rte_pcapng_add_interface(pcapng, port_id, + NULL, NULL, NULL); + if (ret < 0) { + fprintf(stderr, "can not add port %u\n", port_id); + goto fail; } - /* write it to capture file */ - len = rte_pcapng_write_packets(pcapng, clones, IOV_MAX + NUM_PACKETS); + /* Add interface with ifname and ifdescr */ + ret = rte_pcapng_add_interface(pcapng, port_id, + "myeth", "Some long description", NULL); + if (ret < 0) { + fprintf(stderr, "can not add port %u with ifname\n", port_id); + goto fail; + } + + /* Add interface with filter */ + ret = rte_pcapng_add_interface(pcapng, port_id, + NULL, NULL, "tcp port 8080"); + if (ret < 0) { + fprintf(stderr, "can not add port %u with filter\n", port_id); + goto fail; + } - rte_pktmbuf_free_bulk(clones, IOV_MAX + NUM_PACKETS); + rte_pcapng_close(pcapng); - if (len <= 0) { - fprintf(stderr, "Write of packets failed\n"); - return -1; + ret = valid_pcapng_file(file_name, now, 0); + /* if test fails want to investigate the file */ + if (ret == 0) + unlink(file_name); + + return ret; + +fail: + rte_pcapng_close(pcapng); + return -1; +} + +static int +test_write_packets(void) +{ + char file_name[] = "/tmp/pcapng_test_XXXXXX.pcapng"; + static rte_pcapng_t *pcapng; + int ret, tmp_fd, count; + uint64_t now = current_timestamp(); + + tmp_fd = mkstemps(file_name, strlen(".pcapng")); + if (tmp_fd == -1) { + perror("mkstemps() failure"); + goto fail; } + printf("pcapng: output file %s\n", file_name); - return 0; + /* open a test capture file */ + pcapng = rte_pcapng_fdopen(tmp_fd, NULL, NULL, "pcapng_test", NULL); + if (pcapng == NULL) { + fprintf(stderr, "rte_pcapng_fdopen failed\n"); + close(tmp_fd); + goto fail; + } + + /* Add interface to the file */ + ret = rte_pcapng_add_interface(pcapng, port_id, + NULL, NULL, NULL); + if (ret < 0) { + fprintf(stderr, "can not add port %u\n", port_id); + goto fail; + } + + count = fill_pcapng_file(pcapng, TOTAL_PACKETS); + if (count < 0) + goto fail; + + /* write a statistics block */ + ret = rte_pcapng_write_stats(pcapng, port_id, + count, 0, "end of test"); + if (ret <= 0) { + fprintf(stderr, "Write of statistics failed\n"); + goto fail; + } + + rte_pcapng_close(pcapng); + + ret = valid_pcapng_file(file_name, now, count); + /* if test fails want to investigate the file */ + if (ret == 0) + unlink(file_name); + + return ret; + +fail: + rte_pcapng_close(pcapng); + return -1; } static void test_cleanup(void) { rte_mempool_free(mp); - - if (pcapng) - rte_pcapng_close(pcapng); - + rte_vdev_uninit(null_dev); } static struct @@ -299,10 +445,8 @@ unit_test_suite test_pcapng_suite = { .teardown = test_cleanup, .suite_name = "Test Pcapng Unit Test Suite", .unit_test_cases = { + TEST_CASE(test_add_interface), TEST_CASE(test_write_packets), - TEST_CASE(test_write_stats), - TEST_CASE(test_validate), - TEST_CASE(test_write_over_limit_iov_max), TEST_CASES_END() } }; @@ -313,4 +457,4 @@ test_pcapng(void) return unit_test_suite_runner(&test_pcapng_suite); } -REGISTER_TEST_COMMAND(pcapng_autotest, test_pcapng); +REGISTER_FAST_TEST(pcapng_autotest, true, true, test_pcapng); -- 2.39.2