]>
Commit | Line | Data |
---|---|---|
fc44ef5a | 1 | // SPDX-License-Identifier: GPL-2.0-only |
23a95442 DB |
2 | /* |
3 | * Copyright 2013 Red Hat, Inc. | |
4 | * Author: Daniel Borkmann <dborkman@redhat.com> | |
37beae65 | 5 | * Chetan Loke <loke.chetan@gmail.com> (TPACKET_V3 usage example) |
23a95442 DB |
6 | * |
7 | * A basic test of packet socket's TPACKET_V1/TPACKET_V2/TPACKET_V3 behavior. | |
8 | * | |
9 | * Control: | |
10 | * Test the setup of the TPACKET socket with different patterns that are | |
11 | * known to fail (TODO) resp. succeed (OK). | |
12 | * | |
13 | * Datapath: | |
14 | * Open a pair of packet sockets and send resp. receive an a priori known | |
15 | * packet pattern accross the sockets and check if it was received resp. | |
16 | * sent correctly. Fanout in combination with RX_RING is currently not | |
17 | * tested here. | |
18 | * | |
19 | * The test currently runs for | |
20 | * - TPACKET_V1: RX_RING, TX_RING | |
21 | * - TPACKET_V2: RX_RING, TX_RING | |
22 | * - TPACKET_V3: RX_RING | |
23a95442 DB |
23 | */ |
24 | ||
25 | #include <stdio.h> | |
26 | #include <stdlib.h> | |
27 | #include <sys/types.h> | |
28 | #include <sys/stat.h> | |
29 | #include <sys/socket.h> | |
30 | #include <sys/mman.h> | |
31 | #include <linux/if_packet.h> | |
32 | #include <linux/filter.h> | |
33 | #include <ctype.h> | |
34 | #include <fcntl.h> | |
35 | #include <unistd.h> | |
36 | #include <bits/wordsize.h> | |
37 | #include <net/ethernet.h> | |
38 | #include <netinet/ip.h> | |
39 | #include <arpa/inet.h> | |
40 | #include <stdint.h> | |
41 | #include <string.h> | |
42 | #include <assert.h> | |
43 | #include <net/if.h> | |
44 | #include <inttypes.h> | |
45 | #include <poll.h> | |
46 | ||
47 | #include "psock_lib.h" | |
48 | ||
57aefc7c SKSO |
49 | #include "../kselftest.h" |
50 | ||
23a95442 DB |
51 | #ifndef bug_on |
52 | # define bug_on(cond) assert(!(cond)) | |
53 | #endif | |
54 | ||
55 | #ifndef __aligned_tpacket | |
56 | # define __aligned_tpacket __attribute__((aligned(TPACKET_ALIGNMENT))) | |
57 | #endif | |
58 | ||
59 | #ifndef __align_tpacket | |
60 | # define __align_tpacket(x) __attribute__((aligned(TPACKET_ALIGN(x)))) | |
61 | #endif | |
62 | ||
23a95442 | 63 | #define NUM_PACKETS 100 |
37beae65 | 64 | #define ALIGN_8(x) (((x) + 8 - 1) & ~(8 - 1)) |
23a95442 DB |
65 | |
66 | struct ring { | |
67 | struct iovec *rd; | |
68 | uint8_t *mm_space; | |
69 | size_t mm_len, rd_len; | |
70 | struct sockaddr_ll ll; | |
71 | void (*walk)(int sock, struct ring *ring); | |
72 | int type, rd_num, flen, version; | |
73 | union { | |
74 | struct tpacket_req req; | |
75 | struct tpacket_req3 req3; | |
76 | }; | |
77 | }; | |
78 | ||
79 | struct block_desc { | |
80 | uint32_t version; | |
81 | uint32_t offset_to_priv; | |
82 | struct tpacket_hdr_v1 h1; | |
83 | }; | |
84 | ||
85 | union frame_map { | |
86 | struct { | |
87 | struct tpacket_hdr tp_h __aligned_tpacket; | |
88 | struct sockaddr_ll s_ll __align_tpacket(sizeof(struct tpacket_hdr)); | |
89 | } *v1; | |
90 | struct { | |
91 | struct tpacket2_hdr tp_h __aligned_tpacket; | |
92 | struct sockaddr_ll s_ll __align_tpacket(sizeof(struct tpacket2_hdr)); | |
93 | } *v2; | |
94 | void *raw; | |
95 | }; | |
96 | ||
97 | static unsigned int total_packets, total_bytes; | |
98 | ||
99 | static int pfsocket(int ver) | |
100 | { | |
c1878f7a | 101 | int ret, sock = socket(PF_PACKET, SOCK_RAW, 0); |
23a95442 DB |
102 | if (sock == -1) { |
103 | perror("socket"); | |
104 | exit(1); | |
105 | } | |
106 | ||
107 | ret = setsockopt(sock, SOL_PACKET, PACKET_VERSION, &ver, sizeof(ver)); | |
108 | if (ret == -1) { | |
109 | perror("setsockopt"); | |
110 | exit(1); | |
111 | } | |
112 | ||
113 | return sock; | |
114 | } | |
115 | ||
116 | static void status_bar_update(void) | |
117 | { | |
118 | if (total_packets % 10 == 0) { | |
119 | fprintf(stderr, "."); | |
120 | fflush(stderr); | |
121 | } | |
122 | } | |
123 | ||
124 | static void test_payload(void *pay, size_t len) | |
125 | { | |
126 | struct ethhdr *eth = pay; | |
127 | ||
128 | if (len < sizeof(struct ethhdr)) { | |
129 | fprintf(stderr, "test_payload: packet too " | |
130 | "small: %zu bytes!\n", len); | |
131 | exit(1); | |
132 | } | |
133 | ||
134 | if (eth->h_proto != htons(ETH_P_IP)) { | |
135 | fprintf(stderr, "test_payload: wrong ethernet " | |
136 | "type: 0x%x!\n", ntohs(eth->h_proto)); | |
137 | exit(1); | |
138 | } | |
139 | } | |
140 | ||
141 | static void create_payload(void *pay, size_t *len) | |
142 | { | |
143 | int i; | |
144 | struct ethhdr *eth = pay; | |
145 | struct iphdr *ip = pay + sizeof(*eth); | |
146 | ||
147 | /* Lets create some broken crap, that still passes | |
148 | * our BPF filter. | |
149 | */ | |
150 | ||
151 | *len = DATA_LEN + 42; | |
152 | ||
153 | memset(pay, 0xff, ETH_ALEN * 2); | |
154 | eth->h_proto = htons(ETH_P_IP); | |
155 | ||
156 | for (i = 0; i < sizeof(*ip); ++i) | |
157 | ((uint8_t *) pay)[i + sizeof(*eth)] = (uint8_t) rand(); | |
158 | ||
159 | ip->ihl = 5; | |
160 | ip->version = 4; | |
161 | ip->protocol = 0x11; | |
162 | ip->frag_off = 0; | |
163 | ip->ttl = 64; | |
164 | ip->tot_len = htons((uint16_t) *len - sizeof(*eth)); | |
165 | ||
166 | ip->saddr = htonl(INADDR_LOOPBACK); | |
167 | ip->daddr = htonl(INADDR_LOOPBACK); | |
168 | ||
169 | memset(pay + sizeof(*eth) + sizeof(*ip), | |
170 | DATA_CHAR, DATA_LEN); | |
171 | } | |
172 | ||
173 | static inline int __v1_rx_kernel_ready(struct tpacket_hdr *hdr) | |
174 | { | |
175 | return ((hdr->tp_status & TP_STATUS_USER) == TP_STATUS_USER); | |
176 | } | |
177 | ||
178 | static inline void __v1_rx_user_ready(struct tpacket_hdr *hdr) | |
179 | { | |
180 | hdr->tp_status = TP_STATUS_KERNEL; | |
181 | __sync_synchronize(); | |
182 | } | |
183 | ||
184 | static inline int __v2_rx_kernel_ready(struct tpacket2_hdr *hdr) | |
185 | { | |
186 | return ((hdr->tp_status & TP_STATUS_USER) == TP_STATUS_USER); | |
187 | } | |
188 | ||
189 | static inline void __v2_rx_user_ready(struct tpacket2_hdr *hdr) | |
190 | { | |
191 | hdr->tp_status = TP_STATUS_KERNEL; | |
192 | __sync_synchronize(); | |
193 | } | |
194 | ||
195 | static inline int __v1_v2_rx_kernel_ready(void *base, int version) | |
196 | { | |
197 | switch (version) { | |
198 | case TPACKET_V1: | |
199 | return __v1_rx_kernel_ready(base); | |
200 | case TPACKET_V2: | |
201 | return __v2_rx_kernel_ready(base); | |
202 | default: | |
203 | bug_on(1); | |
204 | return 0; | |
205 | } | |
206 | } | |
207 | ||
208 | static inline void __v1_v2_rx_user_ready(void *base, int version) | |
209 | { | |
210 | switch (version) { | |
211 | case TPACKET_V1: | |
212 | __v1_rx_user_ready(base); | |
213 | break; | |
214 | case TPACKET_V2: | |
215 | __v2_rx_user_ready(base); | |
216 | break; | |
217 | } | |
218 | } | |
219 | ||
220 | static void walk_v1_v2_rx(int sock, struct ring *ring) | |
221 | { | |
222 | struct pollfd pfd; | |
223 | int udp_sock[2]; | |
224 | union frame_map ppd; | |
225 | unsigned int frame_num = 0; | |
226 | ||
227 | bug_on(ring->type != PACKET_RX_RING); | |
228 | ||
229 | pair_udp_open(udp_sock, PORT_BASE); | |
23a95442 DB |
230 | |
231 | memset(&pfd, 0, sizeof(pfd)); | |
232 | pfd.fd = sock; | |
233 | pfd.events = POLLIN | POLLERR; | |
234 | pfd.revents = 0; | |
235 | ||
236 | pair_udp_send(udp_sock, NUM_PACKETS); | |
237 | ||
238 | while (total_packets < NUM_PACKETS * 2) { | |
239 | while (__v1_v2_rx_kernel_ready(ring->rd[frame_num].iov_base, | |
240 | ring->version)) { | |
241 | ppd.raw = ring->rd[frame_num].iov_base; | |
242 | ||
243 | switch (ring->version) { | |
244 | case TPACKET_V1: | |
245 | test_payload((uint8_t *) ppd.raw + ppd.v1->tp_h.tp_mac, | |
246 | ppd.v1->tp_h.tp_snaplen); | |
247 | total_bytes += ppd.v1->tp_h.tp_snaplen; | |
248 | break; | |
249 | ||
250 | case TPACKET_V2: | |
251 | test_payload((uint8_t *) ppd.raw + ppd.v2->tp_h.tp_mac, | |
252 | ppd.v2->tp_h.tp_snaplen); | |
253 | total_bytes += ppd.v2->tp_h.tp_snaplen; | |
254 | break; | |
255 | } | |
256 | ||
257 | status_bar_update(); | |
258 | total_packets++; | |
259 | ||
260 | __v1_v2_rx_user_ready(ppd.raw, ring->version); | |
261 | ||
262 | frame_num = (frame_num + 1) % ring->rd_num; | |
263 | } | |
264 | ||
265 | poll(&pfd, 1, 1); | |
266 | } | |
267 | ||
268 | pair_udp_close(udp_sock); | |
269 | ||
270 | if (total_packets != 2 * NUM_PACKETS) { | |
271 | fprintf(stderr, "walk_v%d_rx: received %u out of %u pkts\n", | |
272 | ring->version, total_packets, NUM_PACKETS); | |
273 | exit(1); | |
274 | } | |
275 | ||
276 | fprintf(stderr, " %u pkts (%u bytes)", NUM_PACKETS, total_bytes >> 1); | |
277 | } | |
278 | ||
279 | static inline int __v1_tx_kernel_ready(struct tpacket_hdr *hdr) | |
280 | { | |
fdd5f43a | 281 | return !(hdr->tp_status & (TP_STATUS_SEND_REQUEST | TP_STATUS_SENDING)); |
23a95442 DB |
282 | } |
283 | ||
284 | static inline void __v1_tx_user_ready(struct tpacket_hdr *hdr) | |
285 | { | |
286 | hdr->tp_status = TP_STATUS_SEND_REQUEST; | |
287 | __sync_synchronize(); | |
288 | } | |
289 | ||
290 | static inline int __v2_tx_kernel_ready(struct tpacket2_hdr *hdr) | |
291 | { | |
fdd5f43a | 292 | return !(hdr->tp_status & (TP_STATUS_SEND_REQUEST | TP_STATUS_SENDING)); |
23a95442 DB |
293 | } |
294 | ||
295 | static inline void __v2_tx_user_ready(struct tpacket2_hdr *hdr) | |
296 | { | |
297 | hdr->tp_status = TP_STATUS_SEND_REQUEST; | |
298 | __sync_synchronize(); | |
299 | } | |
300 | ||
fe878cad SV |
301 | static inline int __v3_tx_kernel_ready(struct tpacket3_hdr *hdr) |
302 | { | |
303 | return !(hdr->tp_status & (TP_STATUS_SEND_REQUEST | TP_STATUS_SENDING)); | |
304 | } | |
305 | ||
306 | static inline void __v3_tx_user_ready(struct tpacket3_hdr *hdr) | |
307 | { | |
308 | hdr->tp_status = TP_STATUS_SEND_REQUEST; | |
309 | __sync_synchronize(); | |
310 | } | |
311 | ||
312 | static inline int __tx_kernel_ready(void *base, int version) | |
23a95442 DB |
313 | { |
314 | switch (version) { | |
315 | case TPACKET_V1: | |
316 | return __v1_tx_kernel_ready(base); | |
317 | case TPACKET_V2: | |
318 | return __v2_tx_kernel_ready(base); | |
fe878cad SV |
319 | case TPACKET_V3: |
320 | return __v3_tx_kernel_ready(base); | |
23a95442 DB |
321 | default: |
322 | bug_on(1); | |
323 | return 0; | |
324 | } | |
325 | } | |
326 | ||
fe878cad | 327 | static inline void __tx_user_ready(void *base, int version) |
23a95442 DB |
328 | { |
329 | switch (version) { | |
330 | case TPACKET_V1: | |
331 | __v1_tx_user_ready(base); | |
332 | break; | |
333 | case TPACKET_V2: | |
334 | __v2_tx_user_ready(base); | |
335 | break; | |
fe878cad SV |
336 | case TPACKET_V3: |
337 | __v3_tx_user_ready(base); | |
338 | break; | |
23a95442 DB |
339 | } |
340 | } | |
341 | ||
342 | static void __v1_v2_set_packet_loss_discard(int sock) | |
343 | { | |
344 | int ret, discard = 1; | |
345 | ||
346 | ret = setsockopt(sock, SOL_PACKET, PACKET_LOSS, (void *) &discard, | |
347 | sizeof(discard)); | |
348 | if (ret == -1) { | |
349 | perror("setsockopt"); | |
350 | exit(1); | |
351 | } | |
352 | } | |
353 | ||
fe878cad SV |
354 | static inline void *get_next_frame(struct ring *ring, int n) |
355 | { | |
356 | uint8_t *f0 = ring->rd[0].iov_base; | |
357 | ||
358 | switch (ring->version) { | |
359 | case TPACKET_V1: | |
360 | case TPACKET_V2: | |
361 | return ring->rd[n].iov_base; | |
362 | case TPACKET_V3: | |
363 | return f0 + (n * ring->req3.tp_frame_size); | |
364 | default: | |
365 | bug_on(1); | |
366 | } | |
367 | } | |
368 | ||
369 | static void walk_tx(int sock, struct ring *ring) | |
23a95442 DB |
370 | { |
371 | struct pollfd pfd; | |
372 | int rcv_sock, ret; | |
373 | size_t packet_len; | |
374 | union frame_map ppd; | |
375 | char packet[1024]; | |
376 | unsigned int frame_num = 0, got = 0; | |
377 | struct sockaddr_ll ll = { | |
378 | .sll_family = PF_PACKET, | |
379 | .sll_halen = ETH_ALEN, | |
380 | }; | |
fe878cad SV |
381 | int nframes; |
382 | ||
383 | /* TPACKET_V{1,2} sets up the ring->rd* related variables based | |
384 | * on frames (e.g., rd_num is tp_frame_nr) whereas V3 sets these | |
385 | * up based on blocks (e.g, rd_num is tp_block_nr) | |
386 | */ | |
387 | if (ring->version <= TPACKET_V2) | |
388 | nframes = ring->rd_num; | |
389 | else | |
390 | nframes = ring->req3.tp_frame_nr; | |
23a95442 DB |
391 | |
392 | bug_on(ring->type != PACKET_TX_RING); | |
fe878cad | 393 | bug_on(nframes < NUM_PACKETS); |
23a95442 DB |
394 | |
395 | rcv_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); | |
396 | if (rcv_sock == -1) { | |
397 | perror("socket"); | |
398 | exit(1); | |
399 | } | |
400 | ||
401 | pair_udp_setfilter(rcv_sock); | |
402 | ||
403 | ll.sll_ifindex = if_nametoindex("lo"); | |
404 | ret = bind(rcv_sock, (struct sockaddr *) &ll, sizeof(ll)); | |
405 | if (ret == -1) { | |
406 | perror("bind"); | |
407 | exit(1); | |
408 | } | |
409 | ||
410 | memset(&pfd, 0, sizeof(pfd)); | |
411 | pfd.fd = sock; | |
412 | pfd.events = POLLOUT | POLLERR; | |
413 | pfd.revents = 0; | |
414 | ||
415 | total_packets = NUM_PACKETS; | |
416 | create_payload(packet, &packet_len); | |
417 | ||
418 | while (total_packets > 0) { | |
fe878cad SV |
419 | void *next = get_next_frame(ring, frame_num); |
420 | ||
421 | while (__tx_kernel_ready(next, ring->version) && | |
23a95442 | 422 | total_packets > 0) { |
fe878cad | 423 | ppd.raw = next; |
23a95442 DB |
424 | |
425 | switch (ring->version) { | |
426 | case TPACKET_V1: | |
427 | ppd.v1->tp_h.tp_snaplen = packet_len; | |
428 | ppd.v1->tp_h.tp_len = packet_len; | |
429 | ||
430 | memcpy((uint8_t *) ppd.raw + TPACKET_HDRLEN - | |
431 | sizeof(struct sockaddr_ll), packet, | |
432 | packet_len); | |
433 | total_bytes += ppd.v1->tp_h.tp_snaplen; | |
434 | break; | |
435 | ||
436 | case TPACKET_V2: | |
437 | ppd.v2->tp_h.tp_snaplen = packet_len; | |
438 | ppd.v2->tp_h.tp_len = packet_len; | |
439 | ||
440 | memcpy((uint8_t *) ppd.raw + TPACKET2_HDRLEN - | |
441 | sizeof(struct sockaddr_ll), packet, | |
442 | packet_len); | |
443 | total_bytes += ppd.v2->tp_h.tp_snaplen; | |
444 | break; | |
fe878cad SV |
445 | case TPACKET_V3: { |
446 | struct tpacket3_hdr *tx = next; | |
447 | ||
448 | tx->tp_snaplen = packet_len; | |
449 | tx->tp_len = packet_len; | |
450 | tx->tp_next_offset = 0; | |
451 | ||
452 | memcpy((uint8_t *)tx + TPACKET3_HDRLEN - | |
453 | sizeof(struct sockaddr_ll), packet, | |
454 | packet_len); | |
455 | total_bytes += tx->tp_snaplen; | |
456 | break; | |
457 | } | |
23a95442 DB |
458 | } |
459 | ||
460 | status_bar_update(); | |
461 | total_packets--; | |
462 | ||
fe878cad | 463 | __tx_user_ready(next, ring->version); |
23a95442 | 464 | |
fe878cad | 465 | frame_num = (frame_num + 1) % nframes; |
23a95442 DB |
466 | } |
467 | ||
468 | poll(&pfd, 1, 1); | |
469 | } | |
470 | ||
471 | bug_on(total_packets != 0); | |
472 | ||
473 | ret = sendto(sock, NULL, 0, 0, NULL, 0); | |
474 | if (ret == -1) { | |
475 | perror("sendto"); | |
476 | exit(1); | |
477 | } | |
478 | ||
479 | while ((ret = recvfrom(rcv_sock, packet, sizeof(packet), | |
480 | 0, NULL, NULL)) > 0 && | |
481 | total_packets < NUM_PACKETS) { | |
482 | got += ret; | |
483 | test_payload(packet, ret); | |
484 | ||
485 | status_bar_update(); | |
486 | total_packets++; | |
487 | } | |
488 | ||
489 | close(rcv_sock); | |
490 | ||
491 | if (total_packets != NUM_PACKETS) { | |
492 | fprintf(stderr, "walk_v%d_rx: received %u out of %u pkts\n", | |
493 | ring->version, total_packets, NUM_PACKETS); | |
494 | exit(1); | |
495 | } | |
496 | ||
497 | fprintf(stderr, " %u pkts (%u bytes)", NUM_PACKETS, got); | |
498 | } | |
499 | ||
500 | static void walk_v1_v2(int sock, struct ring *ring) | |
501 | { | |
502 | if (ring->type == PACKET_RX_RING) | |
503 | walk_v1_v2_rx(sock, ring); | |
504 | else | |
fe878cad | 505 | walk_tx(sock, ring); |
23a95442 DB |
506 | } |
507 | ||
508 | static uint64_t __v3_prev_block_seq_num = 0; | |
509 | ||
510 | void __v3_test_block_seq_num(struct block_desc *pbd) | |
511 | { | |
37beae65 | 512 | if (__v3_prev_block_seq_num + 1 != pbd->h1.seq_num) { |
23a95442 DB |
513 | fprintf(stderr, "\nprev_block_seq_num:%"PRIu64", expected " |
514 | "seq:%"PRIu64" != actual seq:%"PRIu64"\n", | |
515 | __v3_prev_block_seq_num, __v3_prev_block_seq_num + 1, | |
37beae65 | 516 | (uint64_t) pbd->h1.seq_num); |
23a95442 DB |
517 | exit(1); |
518 | } | |
519 | ||
37beae65 | 520 | __v3_prev_block_seq_num = pbd->h1.seq_num; |
23a95442 DB |
521 | } |
522 | ||
523 | static void __v3_test_block_len(struct block_desc *pbd, uint32_t bytes, int block_num) | |
524 | { | |
37beae65 DB |
525 | if (pbd->h1.num_pkts && bytes != pbd->h1.blk_len) { |
526 | fprintf(stderr, "\nblock:%u with %upackets, expected " | |
527 | "len:%u != actual len:%u\n", block_num, | |
528 | pbd->h1.num_pkts, bytes, pbd->h1.blk_len); | |
529 | exit(1); | |
23a95442 DB |
530 | } |
531 | } | |
532 | ||
533 | static void __v3_test_block_header(struct block_desc *pbd, const int block_num) | |
534 | { | |
37beae65 | 535 | if ((pbd->h1.block_status & TP_STATUS_USER) == 0) { |
23a95442 DB |
536 | fprintf(stderr, "\nblock %u: not in TP_STATUS_USER\n", block_num); |
537 | exit(1); | |
538 | } | |
539 | ||
540 | __v3_test_block_seq_num(pbd); | |
541 | } | |
542 | ||
543 | static void __v3_walk_block(struct block_desc *pbd, const int block_num) | |
544 | { | |
37beae65 DB |
545 | int num_pkts = pbd->h1.num_pkts, i; |
546 | unsigned long bytes = 0, bytes_with_padding = ALIGN_8(sizeof(*pbd)); | |
23a95442 DB |
547 | struct tpacket3_hdr *ppd; |
548 | ||
549 | __v3_test_block_header(pbd, block_num); | |
550 | ||
37beae65 DB |
551 | ppd = (struct tpacket3_hdr *) ((uint8_t *) pbd + |
552 | pbd->h1.offset_to_first_pkt); | |
553 | ||
23a95442 DB |
554 | for (i = 0; i < num_pkts; ++i) { |
555 | bytes += ppd->tp_snaplen; | |
556 | ||
557 | if (ppd->tp_next_offset) | |
558 | bytes_with_padding += ppd->tp_next_offset; | |
559 | else | |
560 | bytes_with_padding += ALIGN_8(ppd->tp_snaplen + ppd->tp_mac); | |
561 | ||
562 | test_payload((uint8_t *) ppd + ppd->tp_mac, ppd->tp_snaplen); | |
563 | ||
564 | status_bar_update(); | |
565 | total_packets++; | |
566 | ||
567 | ppd = (struct tpacket3_hdr *) ((uint8_t *) ppd + ppd->tp_next_offset); | |
568 | __sync_synchronize(); | |
569 | } | |
570 | ||
571 | __v3_test_block_len(pbd, bytes_with_padding, block_num); | |
572 | total_bytes += bytes; | |
573 | } | |
574 | ||
575 | void __v3_flush_block(struct block_desc *pbd) | |
576 | { | |
37beae65 | 577 | pbd->h1.block_status = TP_STATUS_KERNEL; |
23a95442 DB |
578 | __sync_synchronize(); |
579 | } | |
580 | ||
581 | static void walk_v3_rx(int sock, struct ring *ring) | |
582 | { | |
583 | unsigned int block_num = 0; | |
584 | struct pollfd pfd; | |
585 | struct block_desc *pbd; | |
586 | int udp_sock[2]; | |
587 | ||
588 | bug_on(ring->type != PACKET_RX_RING); | |
589 | ||
590 | pair_udp_open(udp_sock, PORT_BASE); | |
23a95442 DB |
591 | |
592 | memset(&pfd, 0, sizeof(pfd)); | |
593 | pfd.fd = sock; | |
594 | pfd.events = POLLIN | POLLERR; | |
595 | pfd.revents = 0; | |
596 | ||
597 | pair_udp_send(udp_sock, NUM_PACKETS); | |
598 | ||
599 | while (total_packets < NUM_PACKETS * 2) { | |
600 | pbd = (struct block_desc *) ring->rd[block_num].iov_base; | |
601 | ||
37beae65 | 602 | while ((pbd->h1.block_status & TP_STATUS_USER) == 0) |
23a95442 DB |
603 | poll(&pfd, 1, 1); |
604 | ||
605 | __v3_walk_block(pbd, block_num); | |
606 | __v3_flush_block(pbd); | |
607 | ||
608 | block_num = (block_num + 1) % ring->rd_num; | |
609 | } | |
610 | ||
611 | pair_udp_close(udp_sock); | |
612 | ||
613 | if (total_packets != 2 * NUM_PACKETS) { | |
614 | fprintf(stderr, "walk_v3_rx: received %u out of %u pkts\n", | |
615 | total_packets, NUM_PACKETS); | |
616 | exit(1); | |
617 | } | |
618 | ||
619 | fprintf(stderr, " %u pkts (%u bytes)", NUM_PACKETS, total_bytes >> 1); | |
620 | } | |
621 | ||
622 | static void walk_v3(int sock, struct ring *ring) | |
623 | { | |
624 | if (ring->type == PACKET_RX_RING) | |
625 | walk_v3_rx(sock, ring); | |
626 | else | |
fe878cad | 627 | walk_tx(sock, ring); |
23a95442 DB |
628 | } |
629 | ||
630 | static void __v1_v2_fill(struct ring *ring, unsigned int blocks) | |
631 | { | |
632 | ring->req.tp_block_size = getpagesize() << 2; | |
633 | ring->req.tp_frame_size = TPACKET_ALIGNMENT << 7; | |
634 | ring->req.tp_block_nr = blocks; | |
635 | ||
636 | ring->req.tp_frame_nr = ring->req.tp_block_size / | |
637 | ring->req.tp_frame_size * | |
638 | ring->req.tp_block_nr; | |
639 | ||
640 | ring->mm_len = ring->req.tp_block_size * ring->req.tp_block_nr; | |
641 | ring->walk = walk_v1_v2; | |
642 | ring->rd_num = ring->req.tp_frame_nr; | |
643 | ring->flen = ring->req.tp_frame_size; | |
644 | } | |
645 | ||
fe878cad | 646 | static void __v3_fill(struct ring *ring, unsigned int blocks, int type) |
23a95442 | 647 | { |
fe878cad SV |
648 | if (type == PACKET_RX_RING) { |
649 | ring->req3.tp_retire_blk_tov = 64; | |
650 | ring->req3.tp_sizeof_priv = 0; | |
651 | ring->req3.tp_feature_req_word = TP_FT_REQ_FILL_RXHASH; | |
652 | } | |
23a95442 DB |
653 | ring->req3.tp_block_size = getpagesize() << 2; |
654 | ring->req3.tp_frame_size = TPACKET_ALIGNMENT << 7; | |
655 | ring->req3.tp_block_nr = blocks; | |
656 | ||
657 | ring->req3.tp_frame_nr = ring->req3.tp_block_size / | |
658 | ring->req3.tp_frame_size * | |
659 | ring->req3.tp_block_nr; | |
660 | ||
661 | ring->mm_len = ring->req3.tp_block_size * ring->req3.tp_block_nr; | |
662 | ring->walk = walk_v3; | |
663 | ring->rd_num = ring->req3.tp_block_nr; | |
664 | ring->flen = ring->req3.tp_block_size; | |
665 | } | |
666 | ||
667 | static void setup_ring(int sock, struct ring *ring, int version, int type) | |
668 | { | |
669 | int ret = 0; | |
670 | unsigned int blocks = 256; | |
671 | ||
672 | ring->type = type; | |
673 | ring->version = version; | |
674 | ||
675 | switch (version) { | |
676 | case TPACKET_V1: | |
677 | case TPACKET_V2: | |
678 | if (type == PACKET_TX_RING) | |
679 | __v1_v2_set_packet_loss_discard(sock); | |
680 | __v1_v2_fill(ring, blocks); | |
681 | ret = setsockopt(sock, SOL_PACKET, type, &ring->req, | |
682 | sizeof(ring->req)); | |
683 | break; | |
684 | ||
685 | case TPACKET_V3: | |
fe878cad | 686 | __v3_fill(ring, blocks, type); |
23a95442 DB |
687 | ret = setsockopt(sock, SOL_PACKET, type, &ring->req3, |
688 | sizeof(ring->req3)); | |
689 | break; | |
690 | } | |
691 | ||
692 | if (ret == -1) { | |
693 | perror("setsockopt"); | |
694 | exit(1); | |
695 | } | |
696 | ||
697 | ring->rd_len = ring->rd_num * sizeof(*ring->rd); | |
698 | ring->rd = malloc(ring->rd_len); | |
699 | if (ring->rd == NULL) { | |
700 | perror("malloc"); | |
701 | exit(1); | |
702 | } | |
703 | ||
704 | total_packets = 0; | |
705 | total_bytes = 0; | |
706 | } | |
707 | ||
708 | static void mmap_ring(int sock, struct ring *ring) | |
709 | { | |
710 | int i; | |
711 | ||
712 | ring->mm_space = mmap(0, ring->mm_len, PROT_READ | PROT_WRITE, | |
713 | MAP_SHARED | MAP_LOCKED | MAP_POPULATE, sock, 0); | |
714 | if (ring->mm_space == MAP_FAILED) { | |
715 | perror("mmap"); | |
716 | exit(1); | |
717 | } | |
718 | ||
719 | memset(ring->rd, 0, ring->rd_len); | |
720 | for (i = 0; i < ring->rd_num; ++i) { | |
721 | ring->rd[i].iov_base = ring->mm_space + (i * ring->flen); | |
722 | ring->rd[i].iov_len = ring->flen; | |
723 | } | |
724 | } | |
725 | ||
726 | static void bind_ring(int sock, struct ring *ring) | |
727 | { | |
728 | int ret; | |
729 | ||
c1878f7a SV |
730 | pair_udp_setfilter(sock); |
731 | ||
23a95442 DB |
732 | ring->ll.sll_family = PF_PACKET; |
733 | ring->ll.sll_protocol = htons(ETH_P_ALL); | |
734 | ring->ll.sll_ifindex = if_nametoindex("lo"); | |
735 | ring->ll.sll_hatype = 0; | |
736 | ring->ll.sll_pkttype = 0; | |
737 | ring->ll.sll_halen = 0; | |
738 | ||
739 | ret = bind(sock, (struct sockaddr *) &ring->ll, sizeof(ring->ll)); | |
740 | if (ret == -1) { | |
741 | perror("bind"); | |
742 | exit(1); | |
743 | } | |
744 | } | |
745 | ||
746 | static void walk_ring(int sock, struct ring *ring) | |
747 | { | |
748 | ring->walk(sock, ring); | |
749 | } | |
750 | ||
751 | static void unmap_ring(int sock, struct ring *ring) | |
752 | { | |
753 | munmap(ring->mm_space, ring->mm_len); | |
754 | free(ring->rd); | |
755 | } | |
756 | ||
757 | static int test_kernel_bit_width(void) | |
758 | { | |
759 | char in[512], *ptr; | |
760 | int num = 0, fd; | |
761 | ssize_t ret; | |
762 | ||
763 | fd = open("/proc/kallsyms", O_RDONLY); | |
764 | if (fd == -1) { | |
765 | perror("open"); | |
766 | exit(1); | |
767 | } | |
768 | ||
769 | ret = read(fd, in, sizeof(in)); | |
770 | if (ret <= 0) { | |
771 | perror("read"); | |
772 | exit(1); | |
773 | } | |
774 | ||
775 | close(fd); | |
776 | ||
777 | ptr = in; | |
778 | while(!isspace(*ptr)) { | |
779 | num++; | |
780 | ptr++; | |
781 | } | |
782 | ||
783 | return num * 4; | |
784 | } | |
785 | ||
786 | static int test_user_bit_width(void) | |
787 | { | |
788 | return __WORDSIZE; | |
789 | } | |
790 | ||
791 | static const char *tpacket_str[] = { | |
792 | [TPACKET_V1] = "TPACKET_V1", | |
793 | [TPACKET_V2] = "TPACKET_V2", | |
794 | [TPACKET_V3] = "TPACKET_V3", | |
795 | }; | |
796 | ||
797 | static const char *type_str[] = { | |
798 | [PACKET_RX_RING] = "PACKET_RX_RING", | |
799 | [PACKET_TX_RING] = "PACKET_TX_RING", | |
800 | }; | |
801 | ||
802 | static int test_tpacket(int version, int type) | |
803 | { | |
804 | int sock; | |
805 | struct ring ring; | |
806 | ||
807 | fprintf(stderr, "test: %s with %s ", tpacket_str[version], | |
808 | type_str[type]); | |
809 | fflush(stderr); | |
810 | ||
811 | if (version == TPACKET_V1 && | |
812 | test_kernel_bit_width() != test_user_bit_width()) { | |
813 | fprintf(stderr, "test: skip %s %s since user and kernel " | |
814 | "space have different bit width\n", | |
815 | tpacket_str[version], type_str[type]); | |
57aefc7c | 816 | return KSFT_SKIP; |
23a95442 DB |
817 | } |
818 | ||
819 | sock = pfsocket(version); | |
820 | memset(&ring, 0, sizeof(ring)); | |
821 | setup_ring(sock, &ring, version, type); | |
822 | mmap_ring(sock, &ring); | |
823 | bind_ring(sock, &ring); | |
824 | walk_ring(sock, &ring); | |
825 | unmap_ring(sock, &ring); | |
826 | close(sock); | |
827 | ||
828 | fprintf(stderr, "\n"); | |
829 | return 0; | |
830 | } | |
831 | ||
832 | int main(void) | |
833 | { | |
834 | int ret = 0; | |
835 | ||
836 | ret |= test_tpacket(TPACKET_V1, PACKET_RX_RING); | |
837 | ret |= test_tpacket(TPACKET_V1, PACKET_TX_RING); | |
838 | ||
839 | ret |= test_tpacket(TPACKET_V2, PACKET_RX_RING); | |
840 | ret |= test_tpacket(TPACKET_V2, PACKET_TX_RING); | |
841 | ||
842 | ret |= test_tpacket(TPACKET_V3, PACKET_RX_RING); | |
fe878cad | 843 | ret |= test_tpacket(TPACKET_V3, PACKET_TX_RING); |
23a95442 DB |
844 | |
845 | if (ret) | |
846 | return 1; | |
847 | ||
848 | printf("OK. All tests passed\n"); | |
849 | return 0; | |
850 | } |