Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright (c) 2021 Microsoft Corporation
3 : : */
4 : :
5 : : #include <stdio.h>
6 : : #include <stdlib.h>
7 : : #include <unistd.h>
8 : :
9 : : #include <rte_bus_vdev.h>
10 : : #include <rte_ethdev.h>
11 : : #include <rte_ether.h>
12 : : #include <rte_ip.h>
13 : : #include <rte_mbuf.h>
14 : : #include <rte_mempool.h>
15 : : #include <rte_net.h>
16 : : #include <rte_pcapng.h>
17 : : #include <rte_random.h>
18 : : #include <rte_reciprocal.h>
19 : : #include <rte_time.h>
20 : : #include <rte_udp.h>
21 : :
22 : : #include <pcap/pcap.h>
23 : :
24 : : #include "test.h"
25 : :
26 : : #define PCAPNG_TEST_DEBUG 0
27 : :
28 : : #define TOTAL_PACKETS 4096
29 : : #define MAX_BURST 64
30 : : #define MAX_GAP_US 100000
31 : : #define DUMMY_MBUF_NUM 3
32 : :
33 : : static struct rte_mempool *mp;
34 : : static const uint32_t pkt_len = 200;
35 : : static uint16_t port_id;
36 : : static const char null_dev[] = "net_null0";
37 : :
38 : : /* first mbuf in the packet, should always be at offset 0 */
39 : : struct dummy_mbuf {
40 : : struct rte_mbuf mb[DUMMY_MBUF_NUM];
41 : : uint8_t buf[DUMMY_MBUF_NUM][RTE_MBUF_DEFAULT_BUF_SIZE];
42 : : };
43 : :
44 : : static void
45 : 3 : dummy_mbuf_prep(struct rte_mbuf *mb, uint8_t buf[], uint32_t buf_len,
46 : : uint32_t data_len)
47 : : {
48 : : uint32_t i;
49 : : uint8_t *db;
50 : :
51 : 3 : mb->buf_addr = buf;
52 [ + - ]: 3 : rte_mbuf_iova_set(mb, (uintptr_t)buf);
53 [ + - ]: 3 : mb->buf_len = buf_len;
54 : : rte_mbuf_refcnt_set(mb, 1);
55 : :
56 : : /* set pool pointer to dummy value, test doesn't use it */
57 [ + - ]: 3 : mb->pool = (void *)buf;
58 : :
59 : : rte_pktmbuf_reset(mb);
60 [ + - ]: 3 : db = (uint8_t *)rte_pktmbuf_append(mb, data_len);
61 : :
62 [ + + ]: 603 : for (i = 0; i != data_len; i++)
63 : 600 : db[i] = i;
64 : 3 : }
65 : :
66 : : /* Make an IP packet consisting of chain of one packets */
67 : : static void
68 : 1 : mbuf1_prepare(struct dummy_mbuf *dm, uint32_t plen)
69 : : {
70 : : struct {
71 : : struct rte_ether_hdr eth;
72 : : struct rte_ipv4_hdr ip;
73 : : struct rte_udp_hdr udp;
74 : 1 : } pkt = {
75 : : .eth = {
76 : : .dst_addr.addr_bytes = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
77 : : .ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),
78 : : },
79 : : .ip = {
80 : : .version_ihl = RTE_IPV4_VHL_DEF,
81 : : .time_to_live = 1,
82 : : .next_proto_id = IPPROTO_UDP,
83 : : .src_addr = rte_cpu_to_be_32(RTE_IPV4_LOOPBACK),
84 : : .dst_addr = rte_cpu_to_be_32(RTE_IPV4_BROADCAST),
85 : : },
86 : : .udp = {
87 : : .dst_port = rte_cpu_to_be_16(9), /* Discard port */
88 : : },
89 : : };
90 : :
91 : : memset(dm, 0, sizeof(*dm));
92 : 1 : dummy_mbuf_prep(&dm->mb[0], dm->buf[0], sizeof(dm->buf[0]), plen);
93 : :
94 : 1 : rte_eth_random_addr(pkt.eth.src_addr.addr_bytes);
95 : 1 : plen -= sizeof(struct rte_ether_hdr);
96 : :
97 [ + - ]: 1 : pkt.ip.total_length = rte_cpu_to_be_16(plen);
98 : 1 : pkt.ip.hdr_checksum = rte_ipv4_cksum(&pkt.ip);
99 : :
100 : 1 : plen -= sizeof(struct rte_ipv4_hdr);
101 : 1 : pkt.udp.src_port = rte_rand();
102 [ + - ]: 1 : pkt.udp.dgram_len = rte_cpu_to_be_16(plen);
103 : :
104 : 1 : memcpy(rte_pktmbuf_mtod(dm->mb, void *), &pkt, sizeof(pkt));
105 : :
106 : : /* Idea here is to create mbuf chain big enough that after mbuf deep copy they won't be
107 : : * compressed into single mbuf to properly test store of chained mbufs
108 : : */
109 : 1 : dummy_mbuf_prep(&dm->mb[1], dm->buf[1], sizeof(dm->buf[1]), pkt_len);
110 : 1 : dummy_mbuf_prep(&dm->mb[2], dm->buf[2], sizeof(dm->buf[2]), pkt_len);
111 : : rte_pktmbuf_chain(&dm->mb[0], &dm->mb[1]);
112 : : rte_pktmbuf_chain(&dm->mb[0], &dm->mb[2]);
113 : 1 : }
114 : :
115 : : static int
116 : 1 : test_setup(void)
117 : : {
118 : 1 : port_id = rte_eth_dev_count_avail();
119 : :
120 : : /* Make a dummy null device to snoop on */
121 [ - + ]: 1 : if (rte_vdev_init(null_dev, NULL) != 0) {
122 : 0 : fprintf(stderr, "Failed to create vdev '%s'\n", null_dev);
123 : 0 : goto fail;
124 : : }
125 : :
126 : : /* Make a pool for cloned packets */
127 : 2 : mp = rte_pktmbuf_pool_create_by_ops("pcapng_test_pool",
128 : : MAX_BURST * 32, 0, 0,
129 : 1 : rte_pcapng_mbuf_size(pkt_len) + 128,
130 : : SOCKET_ID_ANY, "ring_mp_sc");
131 [ - + ]: 1 : if (mp == NULL) {
132 : 0 : fprintf(stderr, "Cannot create mempool\n");
133 : 0 : goto fail;
134 : : }
135 : :
136 : : return 0;
137 : :
138 : 0 : fail:
139 : 0 : rte_vdev_uninit(null_dev);
140 : 0 : rte_mempool_free(mp);
141 : 0 : return -1;
142 : : }
143 : :
144 : : static int
145 : 1 : fill_pcapng_file(rte_pcapng_t *pcapng, unsigned int num_packets)
146 : : {
147 : : struct dummy_mbuf mbfs;
148 : : struct rte_mbuf *orig;
149 : : unsigned int burst_size;
150 : : unsigned int count;
151 : : ssize_t len;
152 : :
153 : : /* make a dummy packet */
154 : 1 : mbuf1_prepare(&mbfs, pkt_len);
155 : : orig = &mbfs.mb[0];
156 : :
157 [ + + ]: 135 : for (count = 0; count < num_packets; count += burst_size) {
158 : : struct rte_mbuf *clones[MAX_BURST];
159 : : unsigned int i;
160 : :
161 : : /* put 1 .. MAX_BURST packets in one write call */
162 : 134 : burst_size = rte_rand_max(MAX_BURST) + 1;
163 [ + + ]: 4251 : for (i = 0; i < burst_size; i++) {
164 : : struct rte_mbuf *mc;
165 : :
166 : 4117 : mc = rte_pcapng_copy(port_id, 0, orig, mp, rte_pktmbuf_pkt_len(orig),
167 : : RTE_PCAPNG_DIRECTION_IN, NULL);
168 [ - + ]: 4117 : if (mc == NULL) {
169 : 0 : fprintf(stderr, "Cannot copy packet\n");
170 : 0 : return -1;
171 : : }
172 : 4117 : clones[i] = mc;
173 : : }
174 : :
175 : : /* write it to capture file */
176 : 134 : len = rte_pcapng_write_packets(pcapng, clones, burst_size);
177 : 134 : rte_pktmbuf_free_bulk(clones, burst_size);
178 : :
179 [ - + ]: 134 : if (len <= 0) {
180 : 0 : fprintf(stderr, "Write of packets failed: %s\n",
181 : : rte_strerror(rte_errno));
182 : 0 : return -1;
183 : : }
184 : :
185 : : /* Leave a small gap between packets to test for time wrap */
186 : 134 : usleep(rte_rand_max(MAX_GAP_US));
187 : : }
188 : :
189 : 1 : return count;
190 : : }
191 : :
192 : : static char *
193 : 0 : fmt_time(char *buf, size_t size, uint64_t ts_ns)
194 : : {
195 : : time_t sec;
196 : : size_t len;
197 : :
198 : 0 : sec = ts_ns / NS_PER_S;
199 : 0 : len = strftime(buf, size, "%X", localtime(&sec));
200 : 0 : snprintf(buf + len, size - len, ".%09lu",
201 : 0 : (unsigned long)(ts_ns % NS_PER_S));
202 : :
203 : 0 : return buf;
204 : : }
205 : :
206 : : /* Context for the pcap_loop callback */
207 : : struct pkt_print_ctx {
208 : : pcap_t *pcap;
209 : : unsigned int count;
210 : : uint64_t start_ns;
211 : : uint64_t end_ns;
212 : : };
213 : :
214 : : static void
215 : 0 : print_packet(uint64_t ts_ns, const struct rte_ether_hdr *eh, size_t len)
216 : : {
217 : : char tbuf[128], src[64], dst[64];
218 : :
219 : 0 : fmt_time(tbuf, sizeof(tbuf), ts_ns);
220 : 0 : rte_ether_format_addr(dst, sizeof(dst), &eh->dst_addr);
221 : 0 : rte_ether_format_addr(src, sizeof(src), &eh->src_addr);
222 : 0 : printf("%s: %s -> %s type %x length %zu\n",
223 [ # # ]: 0 : tbuf, src, dst, rte_be_to_cpu_16(eh->ether_type), len);
224 : 0 : }
225 : :
226 : : /* Callback from pcap_loop used to validate packets in the file */
227 : : static void
228 : 4117 : parse_pcap_packet(u_char *user, const struct pcap_pkthdr *h,
229 : : const u_char *bytes)
230 : : {
231 : : struct pkt_print_ctx *ctx = (struct pkt_print_ctx *)user;
232 : : const struct rte_ether_hdr *eh;
233 : : const struct rte_ipv4_hdr *ip;
234 : : uint64_t ns;
235 : :
236 : : eh = (const struct rte_ether_hdr *)bytes;
237 : 4117 : ip = (const struct rte_ipv4_hdr *)(eh + 1);
238 : :
239 : 4117 : ctx->count += 1;
240 : :
241 : : /* The pcap library is misleading in reporting timestamp.
242 : : * packet header struct gives timestamp as a timeval (ie. usec);
243 : : * but the file is open in nanonsecond mode therefore
244 : : * the timestamp is really in timespec (ie. nanoseconds).
245 : : */
246 : 4117 : ns = (uint64_t)h->ts.tv_sec * NS_PER_S + h->ts.tv_usec;
247 [ + - - + ]: 4117 : if (ns < ctx->start_ns || ns > ctx->end_ns) {
248 : : char tstart[128], tend[128];
249 : :
250 : 0 : fmt_time(tstart, sizeof(tstart), ctx->start_ns);
251 : 0 : fmt_time(tend, sizeof(tend), ctx->end_ns);
252 : 0 : fprintf(stderr, "Timestamp out of range [%s .. %s]\n",
253 : : tstart, tend);
254 : 0 : goto error;
255 : : }
256 : :
257 [ - + ]: 4117 : if (!rte_is_broadcast_ether_addr(&eh->dst_addr)) {
258 : 0 : fprintf(stderr, "Destination is not broadcast\n");
259 : 0 : goto error;
260 : : }
261 : :
262 [ - + ]: 4117 : if (rte_ipv4_cksum(ip) != 0) {
263 : 0 : fprintf(stderr, "Bad IPv4 checksum\n");
264 : 0 : goto error;
265 : : }
266 : :
267 : : return; /* packet is normal */
268 : :
269 : 0 : error:
270 : 0 : print_packet(ns, eh, h->len);
271 : :
272 : : /* Stop parsing at first error */
273 : 0 : pcap_breakloop(ctx->pcap);
274 : : }
275 : :
276 : : static uint64_t
277 : : current_timestamp(void)
278 : : {
279 : : struct timespec ts;
280 : :
281 : 4 : clock_gettime(CLOCK_REALTIME, &ts);
282 : : return rte_timespec_to_ns(&ts);
283 : : }
284 : :
285 : : /*
286 : : * Open the resulting pcapng file with libpcap
287 : : * Would be better to use capinfos from wireshark
288 : : * but that creates an unwanted dependency.
289 : : */
290 : : static int
291 : 2 : valid_pcapng_file(const char *file_name, uint64_t started, unsigned int expected)
292 : : {
293 : : char errbuf[PCAP_ERRBUF_SIZE];
294 : 2 : struct pkt_print_ctx ctx = { };
295 : : int ret;
296 : :
297 : 2 : ctx.start_ns = started;
298 : 2 : ctx.end_ns = current_timestamp();
299 : :
300 : 2 : ctx.pcap = pcap_open_offline_with_tstamp_precision(file_name,
301 : : PCAP_TSTAMP_PRECISION_NANO,
302 : : errbuf);
303 [ - + ]: 2 : if (ctx.pcap == NULL) {
304 : 0 : fprintf(stderr, "pcap_open_offline('%s') failed: %s\n",
305 : : file_name, errbuf);
306 : 0 : return -1;
307 : : }
308 : :
309 : 2 : ret = pcap_loop(ctx.pcap, 0, parse_pcap_packet, (u_char *)&ctx);
310 [ - + ]: 2 : if (ret != 0) {
311 : 0 : fprintf(stderr, "pcap_dispatch: failed: %s\n",
312 : : pcap_geterr(ctx.pcap));
313 [ - + ]: 2 : } else if (ctx.count != expected) {
314 : : printf("Only %u packets, expected %u\n",
315 : : ctx.count, expected);
316 : : ret = -1;
317 : : }
318 : :
319 : 2 : pcap_close(ctx.pcap);
320 : :
321 : 2 : return ret;
322 : : }
323 : :
324 : : static int
325 : 1 : test_add_interface(void)
326 : : {
327 : 1 : char file_name[] = "/tmp/pcapng_test_XXXXXX.pcapng";
328 : : static rte_pcapng_t *pcapng;
329 : : int ret, tmp_fd;
330 : : uint64_t now = current_timestamp();
331 : :
332 : 1 : tmp_fd = mkstemps(file_name, strlen(".pcapng"));
333 [ - + ]: 1 : if (tmp_fd == -1) {
334 : 0 : perror("mkstemps() failure");
335 : 0 : goto fail;
336 : : }
337 : : printf("pcapng: output file %s\n", file_name);
338 : :
339 : : /* open a test capture file */
340 : 1 : pcapng = rte_pcapng_fdopen(tmp_fd, NULL, NULL, "pcapng_addif", NULL);
341 [ - + ]: 1 : if (pcapng == NULL) {
342 : 0 : fprintf(stderr, "rte_pcapng_fdopen failed\n");
343 : 0 : close(tmp_fd);
344 : 0 : goto fail;
345 : : }
346 : :
347 : : /* Add interface to the file */
348 : 1 : ret = rte_pcapng_add_interface(pcapng, port_id,
349 : : NULL, NULL, NULL);
350 [ - + ]: 1 : if (ret < 0) {
351 : 0 : fprintf(stderr, "can not add port %u\n", port_id);
352 : 0 : goto fail;
353 : : }
354 : :
355 : : /* Add interface with ifname and ifdescr */
356 : 1 : ret = rte_pcapng_add_interface(pcapng, port_id,
357 : : "myeth", "Some long description", NULL);
358 [ - + ]: 1 : if (ret < 0) {
359 : 0 : fprintf(stderr, "can not add port %u with ifname\n", port_id);
360 : 0 : goto fail;
361 : : }
362 : :
363 : : /* Add interface with filter */
364 : 1 : ret = rte_pcapng_add_interface(pcapng, port_id,
365 : : NULL, NULL, "tcp port 8080");
366 [ - + ]: 1 : if (ret < 0) {
367 : 0 : fprintf(stderr, "can not add port %u with filter\n", port_id);
368 : 0 : goto fail;
369 : : }
370 : :
371 : 1 : rte_pcapng_close(pcapng);
372 : :
373 : 1 : ret = valid_pcapng_file(file_name, now, 0);
374 : : /* if test fails want to investigate the file */
375 [ + - ]: 1 : if (ret == 0)
376 : 1 : unlink(file_name);
377 : :
378 : : return ret;
379 : :
380 : 0 : fail:
381 : 0 : rte_pcapng_close(pcapng);
382 : 0 : return -1;
383 : : }
384 : :
385 : : static int
386 : 1 : test_write_packets(void)
387 : : {
388 : 1 : char file_name[] = "/tmp/pcapng_test_XXXXXX.pcapng";
389 : : static rte_pcapng_t *pcapng;
390 : : int ret, tmp_fd, count;
391 : : uint64_t now = current_timestamp();
392 : :
393 : 1 : tmp_fd = mkstemps(file_name, strlen(".pcapng"));
394 [ - + ]: 1 : if (tmp_fd == -1) {
395 : 0 : perror("mkstemps() failure");
396 : 0 : goto fail;
397 : : }
398 : : printf("pcapng: output file %s\n", file_name);
399 : :
400 : : /* open a test capture file */
401 : 1 : pcapng = rte_pcapng_fdopen(tmp_fd, NULL, NULL, "pcapng_test", NULL);
402 [ - + ]: 1 : if (pcapng == NULL) {
403 : 0 : fprintf(stderr, "rte_pcapng_fdopen failed\n");
404 : 0 : close(tmp_fd);
405 : 0 : goto fail;
406 : : }
407 : :
408 : : /* Add interface to the file */
409 : 1 : ret = rte_pcapng_add_interface(pcapng, port_id,
410 : : NULL, NULL, NULL);
411 [ - + ]: 1 : if (ret < 0) {
412 : 0 : fprintf(stderr, "can not add port %u\n", port_id);
413 : 0 : goto fail;
414 : : }
415 : :
416 : 1 : count = fill_pcapng_file(pcapng, TOTAL_PACKETS);
417 [ - + ]: 1 : if (count < 0)
418 : 0 : goto fail;
419 : :
420 : : /* write a statistics block */
421 : 1 : ret = rte_pcapng_write_stats(pcapng, port_id,
422 : : count, 0, "end of test");
423 [ - + ]: 1 : if (ret <= 0) {
424 : 0 : fprintf(stderr, "Write of statistics failed\n");
425 : 0 : goto fail;
426 : : }
427 : :
428 : 1 : rte_pcapng_close(pcapng);
429 : :
430 : 1 : ret = valid_pcapng_file(file_name, now, count);
431 : : /* if test fails want to investigate the file */
432 [ + - ]: 1 : if (ret == 0)
433 : 1 : unlink(file_name);
434 : :
435 : : return ret;
436 : :
437 : 0 : fail:
438 : 0 : rte_pcapng_close(pcapng);
439 : 0 : return -1;
440 : : }
441 : :
442 : : static void
443 : 1 : test_cleanup(void)
444 : : {
445 : 1 : rte_mempool_free(mp);
446 : 1 : rte_vdev_uninit(null_dev);
447 : 1 : }
448 : :
449 : : static struct
450 : : unit_test_suite test_pcapng_suite = {
451 : : .setup = test_setup,
452 : : .teardown = test_cleanup,
453 : : .suite_name = "Test Pcapng Unit Test Suite",
454 : : .unit_test_cases = {
455 : : TEST_CASE(test_add_interface),
456 : : TEST_CASE(test_write_packets),
457 : : TEST_CASES_END()
458 : : }
459 : : };
460 : :
461 : : static int
462 : 1 : test_pcapng(void)
463 : : {
464 : 1 : return unit_test_suite_runner(&test_pcapng_suite);
465 : : }
466 : :
467 : 251 : REGISTER_FAST_TEST(pcapng_autotest, true, true, test_pcapng);
|