1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright 2014 Google Inc. |
4 | * Author: willemb@google.com (Willem de Bruijn) |
5 | * |
6 | * Test software tx timestamping, including |
7 | * |
8 | * - SCHED, SND and ACK timestamps |
9 | * - RAW, UDP and TCP |
10 | * - IPv4 and IPv6 |
11 | * - various packet sizes (to test GSO and TSO) |
12 | * |
13 | * Consult the command line arguments for help on running |
14 | * the various testcases. |
15 | * |
16 | * This test requires a dummy TCP server. |
17 | * A simple `nc6 [-u] -l -p $DESTPORT` will do |
18 | */ |
19 | |
20 | #define _GNU_SOURCE |
21 | |
22 | #include <arpa/inet.h> |
23 | #include <asm/types.h> |
24 | #include <error.h> |
25 | #include <errno.h> |
26 | #include <inttypes.h> |
27 | #include <linux/errqueue.h> |
28 | #include <linux/if_ether.h> |
29 | #include <linux/if_packet.h> |
30 | #include <linux/ipv6.h> |
31 | #include <linux/net_tstamp.h> |
32 | #include <netdb.h> |
33 | #include <net/if.h> |
34 | #include <netinet/in.h> |
35 | #include <netinet/ip.h> |
36 | #include <netinet/udp.h> |
37 | #include <netinet/tcp.h> |
38 | #include <poll.h> |
39 | #include <stdarg.h> |
40 | #include <stdbool.h> |
41 | #include <stdio.h> |
42 | #include <stdlib.h> |
43 | #include <string.h> |
44 | #include <sys/epoll.h> |
45 | #include <sys/ioctl.h> |
46 | #include <sys/select.h> |
47 | #include <sys/socket.h> |
48 | #include <sys/time.h> |
49 | #include <sys/types.h> |
50 | #include <time.h> |
51 | #include <unistd.h> |
52 | |
53 | #define NSEC_PER_USEC 1000L |
54 | #define USEC_PER_SEC 1000000L |
55 | #define NSEC_PER_SEC 1000000000LL |
56 | |
57 | /* command line parameters */ |
58 | static int cfg_proto = SOCK_STREAM; |
59 | static int cfg_ipproto = IPPROTO_TCP; |
60 | static int cfg_num_pkts = 4; |
61 | static int do_ipv4 = 1; |
62 | static int do_ipv6 = 1; |
63 | static int cfg_payload_len = 10; |
64 | static int cfg_poll_timeout = 100; |
65 | static int cfg_delay_snd; |
66 | static int cfg_delay_ack; |
67 | static int cfg_delay_tolerance_usec = 500; |
68 | static bool cfg_show_payload; |
69 | static bool cfg_do_pktinfo; |
70 | static bool cfg_busy_poll; |
71 | static int cfg_sleep_usec = 50 * 1000; |
72 | static bool cfg_loop_nodata; |
73 | static bool cfg_use_cmsg; |
74 | static bool cfg_use_pf_packet; |
75 | static bool cfg_use_epoll; |
76 | static bool cfg_epollet; |
77 | static bool cfg_do_listen; |
78 | static uint16_t dest_port = 9000; |
79 | static bool cfg_print_nsec; |
80 | |
81 | static struct sockaddr_in daddr; |
82 | static struct sockaddr_in6 daddr6; |
83 | static struct timespec ts_usr; |
84 | |
85 | static int saved_tskey = -1; |
86 | static int saved_tskey_type = -1; |
87 | |
88 | struct timing_event { |
89 | int64_t min; |
90 | int64_t max; |
91 | int64_t total; |
92 | int count; |
93 | }; |
94 | |
95 | static struct timing_event usr_enq; |
96 | static struct timing_event usr_snd; |
97 | static struct timing_event usr_ack; |
98 | |
99 | static bool test_failed; |
100 | |
101 | static int64_t timespec_to_ns64(struct timespec *ts) |
102 | { |
103 | return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec; |
104 | } |
105 | |
106 | static int64_t timespec_to_us64(struct timespec *ts) |
107 | { |
108 | return ts->tv_sec * USEC_PER_SEC + ts->tv_nsec / NSEC_PER_USEC; |
109 | } |
110 | |
111 | static void init_timing_event(struct timing_event *te) |
112 | { |
113 | te->min = INT64_MAX; |
114 | te->max = 0; |
115 | te->total = 0; |
116 | te->count = 0; |
117 | } |
118 | |
119 | static void add_timing_event(struct timing_event *te, |
120 | struct timespec *t_start, struct timespec *t_end) |
121 | { |
122 | int64_t ts_delta = timespec_to_ns64(ts: t_end) - timespec_to_ns64(ts: t_start); |
123 | |
124 | te->count++; |
125 | if (ts_delta < te->min) |
126 | te->min = ts_delta; |
127 | if (ts_delta > te->max) |
128 | te->max = ts_delta; |
129 | te->total += ts_delta; |
130 | } |
131 | |
132 | static void validate_key(int tskey, int tstype) |
133 | { |
134 | int stepsize; |
135 | |
136 | /* compare key for each subsequent request |
137 | * must only test for one type, the first one requested |
138 | */ |
139 | if (saved_tskey == -1) |
140 | saved_tskey_type = tstype; |
141 | else if (saved_tskey_type != tstype) |
142 | return; |
143 | |
144 | stepsize = cfg_proto == SOCK_STREAM ? cfg_payload_len : 1; |
145 | if (tskey != saved_tskey + stepsize) { |
146 | fprintf(stderr, "ERROR: key %d, expected %d\n" , |
147 | tskey, saved_tskey + stepsize); |
148 | test_failed = true; |
149 | } |
150 | |
151 | saved_tskey = tskey; |
152 | } |
153 | |
154 | static void validate_timestamp(struct timespec *cur, int min_delay) |
155 | { |
156 | int64_t cur64, start64; |
157 | int max_delay; |
158 | |
159 | cur64 = timespec_to_us64(ts: cur); |
160 | start64 = timespec_to_us64(ts: &ts_usr); |
161 | max_delay = min_delay + cfg_delay_tolerance_usec; |
162 | |
163 | if (cur64 < start64 + min_delay || cur64 > start64 + max_delay) { |
164 | fprintf(stderr, "ERROR: %" PRId64 " us expected between %d and %d\n" , |
165 | cur64 - start64, min_delay, max_delay); |
166 | if (!getenv("KSFT_MACHINE_SLOW" )) |
167 | test_failed = true; |
168 | } |
169 | } |
170 | |
171 | static void __print_ts_delta_formatted(int64_t ts_delta) |
172 | { |
173 | if (cfg_print_nsec) |
174 | fprintf(stderr, "%" PRId64 " ns" , ts_delta); |
175 | else |
176 | fprintf(stderr, "%" PRId64 " us" , ts_delta / NSEC_PER_USEC); |
177 | } |
178 | |
179 | static void __print_timestamp(const char *name, struct timespec *cur, |
180 | uint32_t key, int payload_len) |
181 | { |
182 | int64_t ts_delta; |
183 | |
184 | if (!(cur->tv_sec | cur->tv_nsec)) |
185 | return; |
186 | |
187 | if (cfg_print_nsec) |
188 | fprintf(stderr, " %s: %lu s %lu ns (seq=%u, len=%u)" , |
189 | name, cur->tv_sec, cur->tv_nsec, |
190 | key, payload_len); |
191 | else |
192 | fprintf(stderr, " %s: %lu s %lu us (seq=%u, len=%u)" , |
193 | name, cur->tv_sec, cur->tv_nsec / NSEC_PER_USEC, |
194 | key, payload_len); |
195 | |
196 | if (cur != &ts_usr) { |
197 | ts_delta = timespec_to_ns64(ts: cur) - timespec_to_ns64(ts: &ts_usr); |
198 | fprintf(stderr, " (USR +" ); |
199 | __print_ts_delta_formatted(ts_delta); |
200 | fprintf(stderr, ")" ); |
201 | } |
202 | |
203 | fprintf(stderr, "\n" ); |
204 | } |
205 | |
206 | static void print_timestamp_usr(void) |
207 | { |
208 | if (clock_gettime(CLOCK_REALTIME, &ts_usr)) |
209 | error(1, errno, "clock_gettime" ); |
210 | |
211 | __print_timestamp(name: " USR" , cur: &ts_usr, key: 0, payload_len: 0); |
212 | } |
213 | |
214 | static void print_timestamp(struct scm_timestamping *tss, int tstype, |
215 | int tskey, int payload_len) |
216 | { |
217 | const char *tsname; |
218 | |
219 | validate_key(tskey, tstype); |
220 | |
221 | switch (tstype) { |
222 | case SCM_TSTAMP_SCHED: |
223 | tsname = " ENQ" ; |
224 | validate_timestamp(cur: &tss->ts[0], min_delay: 0); |
225 | add_timing_event(te: &usr_enq, t_start: &ts_usr, t_end: &tss->ts[0]); |
226 | break; |
227 | case SCM_TSTAMP_SND: |
228 | tsname = " SND" ; |
229 | validate_timestamp(cur: &tss->ts[0], min_delay: cfg_delay_snd); |
230 | add_timing_event(te: &usr_snd, t_start: &ts_usr, t_end: &tss->ts[0]); |
231 | break; |
232 | case SCM_TSTAMP_ACK: |
233 | tsname = " ACK" ; |
234 | validate_timestamp(cur: &tss->ts[0], min_delay: cfg_delay_ack); |
235 | add_timing_event(te: &usr_ack, t_start: &ts_usr, t_end: &tss->ts[0]); |
236 | break; |
237 | default: |
238 | error(1, 0, "unknown timestamp type: %u" , |
239 | tstype); |
240 | } |
241 | __print_timestamp(name: tsname, cur: &tss->ts[0], key: tskey, payload_len); |
242 | } |
243 | |
244 | static void print_timing_event(char *name, struct timing_event *te) |
245 | { |
246 | if (!te->count) |
247 | return; |
248 | |
249 | fprintf(stderr, " %s: count=%d" , name, te->count); |
250 | fprintf(stderr, ", avg=" ); |
251 | __print_ts_delta_formatted(ts_delta: (int64_t)(te->total / te->count)); |
252 | fprintf(stderr, ", min=" ); |
253 | __print_ts_delta_formatted(ts_delta: te->min); |
254 | fprintf(stderr, ", max=" ); |
255 | __print_ts_delta_formatted(ts_delta: te->max); |
256 | fprintf(stderr, "\n" ); |
257 | } |
258 | |
259 | /* TODO: convert to check_and_print payload once API is stable */ |
260 | static void print_payload(char *data, int len) |
261 | { |
262 | int i; |
263 | |
264 | if (!len) |
265 | return; |
266 | |
267 | if (len > 70) |
268 | len = 70; |
269 | |
270 | fprintf(stderr, "payload: " ); |
271 | for (i = 0; i < len; i++) |
272 | fprintf(stderr, "%02hhx " , data[i]); |
273 | fprintf(stderr, "\n" ); |
274 | } |
275 | |
276 | static void print_pktinfo(int family, int ifindex, void *saddr, void *daddr) |
277 | { |
278 | char sa[INET6_ADDRSTRLEN], da[INET6_ADDRSTRLEN]; |
279 | |
280 | fprintf(stderr, " pktinfo: ifindex=%u src=%s dst=%s\n" , |
281 | ifindex, |
282 | saddr ? inet_ntop(family, saddr, sa, sizeof(sa)) : "unknown" , |
283 | daddr ? inet_ntop(family, daddr, da, sizeof(da)) : "unknown" ); |
284 | } |
285 | |
286 | static void __epoll(int epfd) |
287 | { |
288 | struct epoll_event events; |
289 | int ret; |
290 | |
291 | memset(&events, 0, sizeof(events)); |
292 | ret = epoll_wait(epfd, &events, 1, cfg_poll_timeout); |
293 | if (ret != 1) |
294 | error(1, errno, "epoll_wait" ); |
295 | } |
296 | |
297 | static void __poll(int fd) |
298 | { |
299 | struct pollfd pollfd; |
300 | int ret; |
301 | |
302 | memset(&pollfd, 0, sizeof(pollfd)); |
303 | pollfd.fd = fd; |
304 | ret = poll(&pollfd, 1, cfg_poll_timeout); |
305 | if (ret != 1) |
306 | error(1, errno, "poll" ); |
307 | } |
308 | |
309 | static void __recv_errmsg_cmsg(struct msghdr *msg, int payload_len) |
310 | { |
311 | struct sock_extended_err *serr = NULL; |
312 | struct scm_timestamping *tss = NULL; |
313 | struct cmsghdr *cm; |
314 | int batch = 0; |
315 | |
316 | for (cm = CMSG_FIRSTHDR(msg); |
317 | cm && cm->cmsg_len; |
318 | cm = CMSG_NXTHDR(msg, cm)) { |
319 | if (cm->cmsg_level == SOL_SOCKET && |
320 | cm->cmsg_type == SCM_TIMESTAMPING) { |
321 | tss = (void *) CMSG_DATA(cm); |
322 | } else if ((cm->cmsg_level == SOL_IP && |
323 | cm->cmsg_type == IP_RECVERR) || |
324 | (cm->cmsg_level == SOL_IPV6 && |
325 | cm->cmsg_type == IPV6_RECVERR) || |
326 | (cm->cmsg_level == SOL_PACKET && |
327 | cm->cmsg_type == PACKET_TX_TIMESTAMP)) { |
328 | serr = (void *) CMSG_DATA(cm); |
329 | if (serr->ee_errno != ENOMSG || |
330 | serr->ee_origin != SO_EE_ORIGIN_TIMESTAMPING) { |
331 | fprintf(stderr, "unknown ip error %d %d\n" , |
332 | serr->ee_errno, |
333 | serr->ee_origin); |
334 | serr = NULL; |
335 | } |
336 | } else if (cm->cmsg_level == SOL_IP && |
337 | cm->cmsg_type == IP_PKTINFO) { |
338 | struct in_pktinfo *info = (void *) CMSG_DATA(cm); |
339 | print_pktinfo(AF_INET, ifindex: info->ipi_ifindex, |
340 | saddr: &info->ipi_spec_dst, daddr: &info->ipi_addr); |
341 | } else if (cm->cmsg_level == SOL_IPV6 && |
342 | cm->cmsg_type == IPV6_PKTINFO) { |
343 | struct in6_pktinfo *info6 = (void *) CMSG_DATA(cm); |
344 | print_pktinfo(AF_INET6, ifindex: info6->ipi6_ifindex, |
345 | NULL, daddr: &info6->ipi6_addr); |
346 | } else |
347 | fprintf(stderr, "unknown cmsg %d,%d\n" , |
348 | cm->cmsg_level, cm->cmsg_type); |
349 | |
350 | if (serr && tss) { |
351 | print_timestamp(tss, tstype: serr->ee_info, tskey: serr->ee_data, |
352 | payload_len); |
353 | serr = NULL; |
354 | tss = NULL; |
355 | batch++; |
356 | } |
357 | } |
358 | |
359 | if (batch > 1) |
360 | fprintf(stderr, "batched %d timestamps\n" , batch); |
361 | } |
362 | |
363 | static int recv_errmsg(int fd) |
364 | { |
365 | static char ctrl[1024 /* overprovision*/]; |
366 | static struct msghdr msg; |
367 | struct iovec entry; |
368 | static char *data; |
369 | int ret = 0; |
370 | |
371 | data = malloc(cfg_payload_len); |
372 | if (!data) |
373 | error(1, 0, "malloc" ); |
374 | |
375 | memset(&msg, 0, sizeof(msg)); |
376 | memset(&entry, 0, sizeof(entry)); |
377 | memset(ctrl, 0, sizeof(ctrl)); |
378 | |
379 | entry.iov_base = data; |
380 | entry.iov_len = cfg_payload_len; |
381 | msg.msg_iov = &entry; |
382 | msg.msg_iovlen = 1; |
383 | msg.msg_name = NULL; |
384 | msg.msg_namelen = 0; |
385 | msg.msg_control = ctrl; |
386 | msg.msg_controllen = sizeof(ctrl); |
387 | |
388 | ret = recvmsg(fd, &msg, MSG_ERRQUEUE); |
389 | if (ret == -1 && errno != EAGAIN) |
390 | error(1, errno, "recvmsg" ); |
391 | |
392 | if (ret >= 0) { |
393 | __recv_errmsg_cmsg(msg: &msg, payload_len: ret); |
394 | if (cfg_show_payload) |
395 | print_payload(data, len: cfg_payload_len); |
396 | } |
397 | |
398 | free(data); |
399 | return ret == -1; |
400 | } |
401 | |
402 | static uint16_t get_ip_csum(const uint16_t *start, int num_words, |
403 | unsigned long sum) |
404 | { |
405 | int i; |
406 | |
407 | for (i = 0; i < num_words; i++) |
408 | sum += start[i]; |
409 | |
410 | while (sum >> 16) |
411 | sum = (sum & 0xFFFF) + (sum >> 16); |
412 | |
413 | return ~sum; |
414 | } |
415 | |
416 | static uint16_t get_udp_csum(const struct udphdr *udph, int alen) |
417 | { |
418 | unsigned long pseudo_sum, csum_len; |
419 | const void *csum_start = udph; |
420 | |
421 | pseudo_sum = htons(IPPROTO_UDP); |
422 | pseudo_sum += udph->len; |
423 | |
424 | /* checksum ip(v6) addresses + udp header + payload */ |
425 | csum_start -= alen * 2; |
426 | csum_len = ntohs(udph->len) + alen * 2; |
427 | |
428 | return get_ip_csum(start: csum_start, num_words: csum_len >> 1, sum: pseudo_sum); |
429 | } |
430 | |
431 | static int (void *p) |
432 | { |
433 | struct iphdr *iph = p; |
434 | |
435 | memset(iph, 0, sizeof(*iph)); |
436 | |
437 | iph->ihl = 5; |
438 | iph->version = 4; |
439 | iph->ttl = 2; |
440 | iph->saddr = daddr.sin_addr.s_addr; /* set for udp csum calc */ |
441 | iph->daddr = daddr.sin_addr.s_addr; |
442 | iph->protocol = IPPROTO_UDP; |
443 | |
444 | /* kernel writes saddr, csum, len */ |
445 | |
446 | return sizeof(*iph); |
447 | } |
448 | |
449 | static int (void *p) |
450 | { |
451 | struct ipv6hdr *ip6h = p; |
452 | |
453 | memset(ip6h, 0, sizeof(*ip6h)); |
454 | |
455 | ip6h->version = 6; |
456 | ip6h->payload_len = htons(sizeof(struct udphdr) + cfg_payload_len); |
457 | ip6h->nexthdr = IPPROTO_UDP; |
458 | ip6h->hop_limit = 64; |
459 | |
460 | ip6h->saddr = daddr6.sin6_addr; |
461 | ip6h->daddr = daddr6.sin6_addr; |
462 | |
463 | /* kernel does not write saddr in case of ipv6 */ |
464 | |
465 | return sizeof(*ip6h); |
466 | } |
467 | |
468 | static void (void *p, bool is_ipv4) |
469 | { |
470 | struct udphdr *udph = p; |
471 | |
472 | udph->source = ntohs(dest_port + 1); /* spoof */ |
473 | udph->dest = ntohs(dest_port); |
474 | udph->len = ntohs(sizeof(*udph) + cfg_payload_len); |
475 | udph->check = 0; |
476 | |
477 | udph->check = get_udp_csum(udph, alen: is_ipv4 ? sizeof(struct in_addr) : |
478 | sizeof(struct in6_addr)); |
479 | } |
480 | |
481 | static void do_test(int family, unsigned int report_opt) |
482 | { |
483 | char control[CMSG_SPACE(sizeof(uint32_t))]; |
484 | struct sockaddr_ll laddr; |
485 | unsigned int sock_opt; |
486 | struct cmsghdr *cmsg; |
487 | struct msghdr msg; |
488 | struct iovec iov; |
489 | char *buf; |
490 | int fd, i, val = 1, total_len, epfd = 0; |
491 | |
492 | init_timing_event(te: &usr_enq); |
493 | init_timing_event(te: &usr_snd); |
494 | init_timing_event(te: &usr_ack); |
495 | |
496 | total_len = cfg_payload_len; |
497 | if (cfg_use_pf_packet || cfg_proto == SOCK_RAW) { |
498 | total_len += sizeof(struct udphdr); |
499 | if (cfg_use_pf_packet || cfg_ipproto == IPPROTO_RAW) { |
500 | if (family == PF_INET) |
501 | total_len += sizeof(struct iphdr); |
502 | else |
503 | total_len += sizeof(struct ipv6hdr); |
504 | } |
505 | /* special case, only rawv6_sendmsg: |
506 | * pass proto in sin6_port if not connected |
507 | * also see ANK comment in net/ipv4/raw.c |
508 | */ |
509 | daddr6.sin6_port = htons(cfg_ipproto); |
510 | } |
511 | |
512 | buf = malloc(total_len); |
513 | if (!buf) |
514 | error(1, 0, "malloc" ); |
515 | |
516 | fd = socket(cfg_use_pf_packet ? PF_PACKET : family, |
517 | cfg_proto, cfg_ipproto); |
518 | if (fd < 0) |
519 | error(1, errno, "socket" ); |
520 | |
521 | if (cfg_use_epoll) { |
522 | struct epoll_event ev; |
523 | |
524 | memset(&ev, 0, sizeof(ev)); |
525 | ev.data.fd = fd; |
526 | if (cfg_epollet) |
527 | ev.events |= EPOLLET; |
528 | epfd = epoll_create(1); |
529 | if (epfd <= 0) |
530 | error(1, errno, "epoll_create" ); |
531 | if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev)) |
532 | error(1, errno, "epoll_ctl" ); |
533 | } |
534 | |
535 | /* reset expected key on each new socket */ |
536 | saved_tskey = -1; |
537 | |
538 | if (cfg_proto == SOCK_STREAM) { |
539 | if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, |
540 | (char*) &val, sizeof(val))) |
541 | error(1, 0, "setsockopt no nagle" ); |
542 | |
543 | if (family == PF_INET) { |
544 | if (connect(fd, (void *) &daddr, sizeof(daddr))) |
545 | error(1, errno, "connect ipv4" ); |
546 | } else { |
547 | if (connect(fd, (void *) &daddr6, sizeof(daddr6))) |
548 | error(1, errno, "connect ipv6" ); |
549 | } |
550 | } |
551 | |
552 | if (cfg_do_pktinfo) { |
553 | if (family == AF_INET6) { |
554 | if (setsockopt(fd, SOL_IPV6, IPV6_RECVPKTINFO, |
555 | &val, sizeof(val))) |
556 | error(1, errno, "setsockopt pktinfo ipv6" ); |
557 | } else { |
558 | if (setsockopt(fd, SOL_IP, IP_PKTINFO, |
559 | &val, sizeof(val))) |
560 | error(1, errno, "setsockopt pktinfo ipv4" ); |
561 | } |
562 | } |
563 | |
564 | sock_opt = SOF_TIMESTAMPING_SOFTWARE | |
565 | SOF_TIMESTAMPING_OPT_CMSG | |
566 | SOF_TIMESTAMPING_OPT_ID; |
567 | |
568 | if (!cfg_use_cmsg) |
569 | sock_opt |= report_opt; |
570 | |
571 | if (cfg_loop_nodata) |
572 | sock_opt |= SOF_TIMESTAMPING_OPT_TSONLY; |
573 | |
574 | if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, |
575 | (char *) &sock_opt, sizeof(sock_opt))) |
576 | error(1, 0, "setsockopt timestamping" ); |
577 | |
578 | for (i = 0; i < cfg_num_pkts; i++) { |
579 | memset(&msg, 0, sizeof(msg)); |
580 | memset(buf, 'a' + i, total_len); |
581 | |
582 | if (cfg_use_pf_packet || cfg_proto == SOCK_RAW) { |
583 | int off = 0; |
584 | |
585 | if (cfg_use_pf_packet || cfg_ipproto == IPPROTO_RAW) { |
586 | if (family == PF_INET) |
587 | off = fill_header_ipv4(p: buf); |
588 | else |
589 | off = fill_header_ipv6(p: buf); |
590 | } |
591 | |
592 | fill_header_udp(p: buf + off, is_ipv4: family == PF_INET); |
593 | } |
594 | |
595 | print_timestamp_usr(); |
596 | |
597 | iov.iov_base = buf; |
598 | iov.iov_len = total_len; |
599 | |
600 | if (cfg_proto != SOCK_STREAM) { |
601 | if (cfg_use_pf_packet) { |
602 | memset(&laddr, 0, sizeof(laddr)); |
603 | |
604 | laddr.sll_family = AF_PACKET; |
605 | laddr.sll_ifindex = 1; |
606 | laddr.sll_protocol = htons(family == AF_INET ? ETH_P_IP : ETH_P_IPV6); |
607 | laddr.sll_halen = ETH_ALEN; |
608 | |
609 | msg.msg_name = (void *)&laddr; |
610 | msg.msg_namelen = sizeof(laddr); |
611 | } else if (family == PF_INET) { |
612 | msg.msg_name = (void *)&daddr; |
613 | msg.msg_namelen = sizeof(daddr); |
614 | } else { |
615 | msg.msg_name = (void *)&daddr6; |
616 | msg.msg_namelen = sizeof(daddr6); |
617 | } |
618 | } |
619 | |
620 | msg.msg_iov = &iov; |
621 | msg.msg_iovlen = 1; |
622 | |
623 | if (cfg_use_cmsg) { |
624 | memset(control, 0, sizeof(control)); |
625 | |
626 | msg.msg_control = control; |
627 | msg.msg_controllen = sizeof(control); |
628 | |
629 | cmsg = CMSG_FIRSTHDR(&msg); |
630 | cmsg->cmsg_level = SOL_SOCKET; |
631 | cmsg->cmsg_type = SO_TIMESTAMPING; |
632 | cmsg->cmsg_len = CMSG_LEN(sizeof(uint32_t)); |
633 | |
634 | *((uint32_t *) CMSG_DATA(cmsg)) = report_opt; |
635 | } |
636 | |
637 | val = sendmsg(fd, &msg, 0); |
638 | if (val != total_len) |
639 | error(1, errno, "send" ); |
640 | |
641 | /* wait for all errors to be queued, else ACKs arrive OOO */ |
642 | if (cfg_sleep_usec) |
643 | usleep(cfg_sleep_usec); |
644 | |
645 | if (!cfg_busy_poll) { |
646 | if (cfg_use_epoll) |
647 | __epoll(epfd); |
648 | else |
649 | __poll(fd); |
650 | } |
651 | |
652 | while (!recv_errmsg(fd)) {} |
653 | } |
654 | |
655 | print_timing_event(name: "USR-ENQ" , te: &usr_enq); |
656 | print_timing_event(name: "USR-SND" , te: &usr_snd); |
657 | print_timing_event(name: "USR-ACK" , te: &usr_ack); |
658 | |
659 | if (close(fd)) |
660 | error(1, errno, "close" ); |
661 | |
662 | free(buf); |
663 | usleep(100 * NSEC_PER_USEC); |
664 | } |
665 | |
666 | static void __attribute__((noreturn)) usage(const char *filepath) |
667 | { |
668 | fprintf(stderr, "\nUsage: %s [options] hostname\n" |
669 | "\nwhere options are:\n" |
670 | " -4: only IPv4\n" |
671 | " -6: only IPv6\n" |
672 | " -h: show this message\n" |
673 | " -b: busy poll to read from error queue\n" |
674 | " -c N: number of packets for each test\n" |
675 | " -C: use cmsg to set tstamp recording options\n" |
676 | " -e: use level-triggered epoll() instead of poll()\n" |
677 | " -E: use event-triggered epoll() instead of poll()\n" |
678 | " -F: poll()/epoll() waits forever for an event\n" |
679 | " -I: request PKTINFO\n" |
680 | " -l N: send N bytes at a time\n" |
681 | " -L listen on hostname and port\n" |
682 | " -n: set no-payload option\n" |
683 | " -N: print timestamps and durations in nsec (instead of usec)\n" |
684 | " -p N: connect to port N\n" |
685 | " -P: use PF_PACKET\n" |
686 | " -r: use raw\n" |
687 | " -R: use raw (IP_HDRINCL)\n" |
688 | " -S N: usec to sleep before reading error queue\n" |
689 | " -t N: tolerance (usec) for timestamp validation\n" |
690 | " -u: use udp\n" |
691 | " -v: validate SND delay (usec)\n" |
692 | " -V: validate ACK delay (usec)\n" |
693 | " -x: show payload (up to 70 bytes)\n" , |
694 | filepath); |
695 | exit(1); |
696 | } |
697 | |
698 | static void parse_opt(int argc, char **argv) |
699 | { |
700 | int proto_count = 0; |
701 | int c; |
702 | |
703 | while ((c = getopt(argc, argv, |
704 | "46bc:CeEFhIl:LnNp:PrRS:t:uv:V:x" )) != -1) { |
705 | switch (c) { |
706 | case '4': |
707 | do_ipv6 = 0; |
708 | break; |
709 | case '6': |
710 | do_ipv4 = 0; |
711 | break; |
712 | case 'b': |
713 | cfg_busy_poll = true; |
714 | break; |
715 | case 'c': |
716 | cfg_num_pkts = strtoul(optarg, NULL, 10); |
717 | break; |
718 | case 'C': |
719 | cfg_use_cmsg = true; |
720 | break; |
721 | case 'e': |
722 | cfg_use_epoll = true; |
723 | break; |
724 | case 'E': |
725 | cfg_use_epoll = true; |
726 | cfg_epollet = true; |
727 | case 'F': |
728 | cfg_poll_timeout = -1; |
729 | break; |
730 | case 'I': |
731 | cfg_do_pktinfo = true; |
732 | break; |
733 | case 'l': |
734 | cfg_payload_len = strtoul(optarg, NULL, 10); |
735 | break; |
736 | case 'L': |
737 | cfg_do_listen = true; |
738 | break; |
739 | case 'n': |
740 | cfg_loop_nodata = true; |
741 | break; |
742 | case 'N': |
743 | cfg_print_nsec = true; |
744 | break; |
745 | case 'p': |
746 | dest_port = strtoul(optarg, NULL, 10); |
747 | break; |
748 | case 'P': |
749 | proto_count++; |
750 | cfg_use_pf_packet = true; |
751 | cfg_proto = SOCK_DGRAM; |
752 | cfg_ipproto = 0; |
753 | break; |
754 | case 'r': |
755 | proto_count++; |
756 | cfg_proto = SOCK_RAW; |
757 | cfg_ipproto = IPPROTO_UDP; |
758 | break; |
759 | case 'R': |
760 | proto_count++; |
761 | cfg_proto = SOCK_RAW; |
762 | cfg_ipproto = IPPROTO_RAW; |
763 | break; |
764 | case 'S': |
765 | cfg_sleep_usec = strtoul(optarg, NULL, 10); |
766 | break; |
767 | case 't': |
768 | cfg_delay_tolerance_usec = strtoul(optarg, NULL, 10); |
769 | break; |
770 | case 'u': |
771 | proto_count++; |
772 | cfg_proto = SOCK_DGRAM; |
773 | cfg_ipproto = IPPROTO_UDP; |
774 | break; |
775 | case 'v': |
776 | cfg_delay_snd = strtoul(optarg, NULL, 10); |
777 | break; |
778 | case 'V': |
779 | cfg_delay_ack = strtoul(optarg, NULL, 10); |
780 | break; |
781 | case 'x': |
782 | cfg_show_payload = true; |
783 | break; |
784 | case 'h': |
785 | default: |
786 | usage(filepath: argv[0]); |
787 | } |
788 | } |
789 | |
790 | if (!cfg_payload_len) |
791 | error(1, 0, "payload may not be nonzero" ); |
792 | if (cfg_proto != SOCK_STREAM && cfg_payload_len > 1472) |
793 | error(1, 0, "udp packet might exceed expected MTU" ); |
794 | if (!do_ipv4 && !do_ipv6) |
795 | error(1, 0, "pass -4 or -6, not both" ); |
796 | if (proto_count > 1) |
797 | error(1, 0, "pass -P, -r, -R or -u, not multiple" ); |
798 | if (cfg_do_pktinfo && cfg_use_pf_packet) |
799 | error(1, 0, "cannot ask for pktinfo over pf_packet" ); |
800 | if (cfg_busy_poll && cfg_use_epoll) |
801 | error(1, 0, "pass epoll or busy_poll, not both" ); |
802 | |
803 | if (optind != argc - 1) |
804 | error(1, 0, "missing required hostname argument" ); |
805 | } |
806 | |
807 | static void resolve_hostname(const char *hostname) |
808 | { |
809 | struct addrinfo hints = { .ai_family = do_ipv4 ? AF_INET : AF_INET6 }; |
810 | struct addrinfo *addrs, *cur; |
811 | int have_ipv4 = 0, have_ipv6 = 0; |
812 | |
813 | retry: |
814 | if (getaddrinfo(hostname, NULL, &hints, &addrs)) |
815 | error(1, errno, "getaddrinfo" ); |
816 | |
817 | cur = addrs; |
818 | while (cur && !have_ipv4 && !have_ipv6) { |
819 | if (!have_ipv4 && cur->ai_family == AF_INET) { |
820 | memcpy(&daddr, cur->ai_addr, sizeof(daddr)); |
821 | daddr.sin_port = htons(dest_port); |
822 | have_ipv4 = 1; |
823 | } |
824 | else if (!have_ipv6 && cur->ai_family == AF_INET6) { |
825 | memcpy(&daddr6, cur->ai_addr, sizeof(daddr6)); |
826 | daddr6.sin6_port = htons(dest_port); |
827 | have_ipv6 = 1; |
828 | } |
829 | cur = cur->ai_next; |
830 | } |
831 | if (addrs) |
832 | freeaddrinfo(addrs); |
833 | |
834 | if (do_ipv6 && hints.ai_family != AF_INET6) { |
835 | hints.ai_family = AF_INET6; |
836 | goto retry; |
837 | } |
838 | |
839 | do_ipv4 &= have_ipv4; |
840 | do_ipv6 &= have_ipv6; |
841 | } |
842 | |
843 | static void do_listen(int family, void *addr, int alen) |
844 | { |
845 | int fd, type; |
846 | |
847 | type = cfg_proto == SOCK_RAW ? SOCK_DGRAM : cfg_proto; |
848 | |
849 | fd = socket(family, type, 0); |
850 | if (fd == -1) |
851 | error(1, errno, "socket rx" ); |
852 | |
853 | if (bind(fd, addr, alen)) |
854 | error(1, errno, "bind rx" ); |
855 | |
856 | if (type == SOCK_STREAM && listen(fd, 10)) |
857 | error(1, errno, "listen rx" ); |
858 | |
859 | /* leave fd open, will be closed on process exit. |
860 | * this enables connect() to succeed and avoids icmp replies |
861 | */ |
862 | } |
863 | |
864 | static void do_main(int family) |
865 | { |
866 | fprintf(stderr, "family: %s %s\n" , |
867 | family == PF_INET ? "INET" : "INET6" , |
868 | cfg_use_pf_packet ? "(PF_PACKET)" : "" ); |
869 | |
870 | fprintf(stderr, "test SND\n" ); |
871 | do_test(family, report_opt: SOF_TIMESTAMPING_TX_SOFTWARE); |
872 | |
873 | fprintf(stderr, "test ENQ\n" ); |
874 | do_test(family, report_opt: SOF_TIMESTAMPING_TX_SCHED); |
875 | |
876 | fprintf(stderr, "test ENQ + SND\n" ); |
877 | do_test(family, report_opt: SOF_TIMESTAMPING_TX_SCHED | |
878 | SOF_TIMESTAMPING_TX_SOFTWARE); |
879 | |
880 | if (cfg_proto == SOCK_STREAM) { |
881 | fprintf(stderr, "\ntest ACK\n" ); |
882 | do_test(family, report_opt: SOF_TIMESTAMPING_TX_ACK); |
883 | |
884 | fprintf(stderr, "\ntest SND + ACK\n" ); |
885 | do_test(family, report_opt: SOF_TIMESTAMPING_TX_SOFTWARE | |
886 | SOF_TIMESTAMPING_TX_ACK); |
887 | |
888 | fprintf(stderr, "\ntest ENQ + SND + ACK\n" ); |
889 | do_test(family, report_opt: SOF_TIMESTAMPING_TX_SCHED | |
890 | SOF_TIMESTAMPING_TX_SOFTWARE | |
891 | SOF_TIMESTAMPING_TX_ACK); |
892 | } |
893 | } |
894 | |
895 | const char *sock_names[] = { NULL, "TCP" , "UDP" , "RAW" }; |
896 | |
897 | int main(int argc, char **argv) |
898 | { |
899 | if (argc == 1) |
900 | usage(filepath: argv[0]); |
901 | |
902 | parse_opt(argc, argv); |
903 | resolve_hostname(hostname: argv[argc - 1]); |
904 | |
905 | fprintf(stderr, "protocol: %s\n" , sock_names[cfg_proto]); |
906 | fprintf(stderr, "payload: %u\n" , cfg_payload_len); |
907 | fprintf(stderr, "server port: %u\n" , dest_port); |
908 | fprintf(stderr, "\n" ); |
909 | |
910 | if (do_ipv4) { |
911 | if (cfg_do_listen) |
912 | do_listen(PF_INET, addr: &daddr, alen: sizeof(daddr)); |
913 | do_main(PF_INET); |
914 | } |
915 | |
916 | if (do_ipv6) { |
917 | if (cfg_do_listen) |
918 | do_listen(PF_INET6, addr: &daddr6, alen: sizeof(daddr6)); |
919 | do_main(PF_INET6); |
920 | } |
921 | |
922 | return test_failed; |
923 | } |
924 | |