Home | History | Annotate | Download | only in timestamping
      1 /*
      2  * Copyright 2014 Google Inc.
      3  * Author: willemb (at) google.com (Willem de Bruijn)
      4  *
      5  * Test software tx timestamping, including
      6  *
      7  * - SCHED, SND and ACK timestamps
      8  * - RAW, UDP and TCP
      9  * - IPv4 and IPv6
     10  * - various packet sizes (to test GSO and TSO)
     11  *
     12  * Consult the command line arguments for help on running
     13  * the various testcases.
     14  *
     15  * This test requires a dummy TCP server.
     16  * A simple `nc6 [-u] -l -p $DESTPORT` will do
     17  *
     18  *
     19  * This program is free software; you can redistribute it and/or modify it
     20  * under the terms and conditions of the GNU General Public License,
     21  * version 2, as published by the Free Software Foundation.
     22  *
     23  * This program is distributed in the hope it will be useful, but WITHOUT
     24  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     25  * FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for
     26  * more details.
     27  *
     28  * You should have received a copy of the GNU General Public License along with
     29  * this program; if not, write to the Free Software Foundation, Inc.,
     30  * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
     31  */
     32 
     33 #define _GNU_SOURCE
     34 
     35 #include <arpa/inet.h>
     36 #include <asm/types.h>
     37 #include <error.h>
     38 #include <errno.h>
     39 #include <inttypes.h>
     40 #include <linux/errqueue.h>
     41 #include <linux/if_ether.h>
     42 #include <linux/net_tstamp.h>
     43 #include <netdb.h>
     44 #include <net/if.h>
     45 #include <netinet/in.h>
     46 #include <netinet/ip.h>
     47 #include <netinet/udp.h>
     48 #include <netinet/tcp.h>
     49 #include <netpacket/packet.h>
     50 #include <poll.h>
     51 #include <stdarg.h>
     52 #include <stdbool.h>
     53 #include <stdio.h>
     54 #include <stdlib.h>
     55 #include <string.h>
     56 #include <sys/ioctl.h>
     57 #include <sys/select.h>
     58 #include <sys/socket.h>
     59 #include <sys/time.h>
     60 #include <sys/types.h>
     61 #include <time.h>
     62 #include <unistd.h>
     63 
     64 /* command line parameters */
     65 static int cfg_proto = SOCK_STREAM;
     66 static int cfg_ipproto = IPPROTO_TCP;
     67 static int cfg_num_pkts = 4;
     68 static int do_ipv4 = 1;
     69 static int do_ipv6 = 1;
     70 static int cfg_payload_len = 10;
     71 static bool cfg_show_payload;
     72 static bool cfg_do_pktinfo;
     73 static bool cfg_loop_nodata;
     74 static uint16_t dest_port = 9000;
     75 
     76 static struct sockaddr_in daddr;
     77 static struct sockaddr_in6 daddr6;
     78 static struct timespec ts_prev;
     79 
     80 static void __print_timestamp(const char *name, struct timespec *cur,
     81 			      uint32_t key, int payload_len)
     82 {
     83 	if (!(cur->tv_sec | cur->tv_nsec))
     84 		return;
     85 
     86 	fprintf(stderr, "  %s: %lu s %lu us (seq=%u, len=%u)",
     87 			name, cur->tv_sec, cur->tv_nsec / 1000,
     88 			key, payload_len);
     89 
     90 	if ((ts_prev.tv_sec | ts_prev.tv_nsec)) {
     91 		int64_t cur_ms, prev_ms;
     92 
     93 		cur_ms = (long) cur->tv_sec * 1000 * 1000;
     94 		cur_ms += cur->tv_nsec / 1000;
     95 
     96 		prev_ms = (long) ts_prev.tv_sec * 1000 * 1000;
     97 		prev_ms += ts_prev.tv_nsec / 1000;
     98 
     99 		fprintf(stderr, "  (%+" PRId64 " us)", cur_ms - prev_ms);
    100 	}
    101 
    102 	ts_prev = *cur;
    103 	fprintf(stderr, "\n");
    104 }
    105 
    106 static void print_timestamp_usr(void)
    107 {
    108 	struct timespec ts;
    109 	struct timeval tv;	/* avoid dependency on -lrt */
    110 
    111 	gettimeofday(&tv, NULL);
    112 	ts.tv_sec = tv.tv_sec;
    113 	ts.tv_nsec = tv.tv_usec * 1000;
    114 
    115 	__print_timestamp("  USR", &ts, 0, 0);
    116 }
    117 
    118 static void print_timestamp(struct scm_timestamping *tss, int tstype,
    119 			    int tskey, int payload_len)
    120 {
    121 	const char *tsname;
    122 
    123 	switch (tstype) {
    124 	case SCM_TSTAMP_SCHED:
    125 		tsname = "  ENQ";
    126 		break;
    127 	case SCM_TSTAMP_SND:
    128 		tsname = "  SND";
    129 		break;
    130 	case SCM_TSTAMP_ACK:
    131 		tsname = "  ACK";
    132 		break;
    133 	default:
    134 		error(1, 0, "unknown timestamp type: %u",
    135 		tstype);
    136 	}
    137 	__print_timestamp(tsname, &tss->ts[0], tskey, payload_len);
    138 }
    139 
    140 /* TODO: convert to check_and_print payload once API is stable */
    141 static void print_payload(char *data, int len)
    142 {
    143 	int i;
    144 
    145 	if (!len)
    146 		return;
    147 
    148 	if (len > 70)
    149 		len = 70;
    150 
    151 	fprintf(stderr, "payload: ");
    152 	for (i = 0; i < len; i++)
    153 		fprintf(stderr, "%02hhx ", data[i]);
    154 	fprintf(stderr, "\n");
    155 }
    156 
    157 static void print_pktinfo(int family, int ifindex, void *saddr, void *daddr)
    158 {
    159 	char sa[INET6_ADDRSTRLEN], da[INET6_ADDRSTRLEN];
    160 
    161 	fprintf(stderr, "         pktinfo: ifindex=%u src=%s dst=%s\n",
    162 		ifindex,
    163 		saddr ? inet_ntop(family, saddr, sa, sizeof(sa)) : "unknown",
    164 		daddr ? inet_ntop(family, daddr, da, sizeof(da)) : "unknown");
    165 }
    166 
    167 static void __poll(int fd)
    168 {
    169 	struct pollfd pollfd;
    170 	int ret;
    171 
    172 	memset(&pollfd, 0, sizeof(pollfd));
    173 	pollfd.fd = fd;
    174 	ret = poll(&pollfd, 1, 100);
    175 	if (ret != 1)
    176 		error(1, errno, "poll");
    177 }
    178 
    179 static void __recv_errmsg_cmsg(struct msghdr *msg, int payload_len)
    180 {
    181 	struct sock_extended_err *serr = NULL;
    182 	struct scm_timestamping *tss = NULL;
    183 	struct cmsghdr *cm;
    184 	int batch = 0;
    185 
    186 	for (cm = CMSG_FIRSTHDR(msg);
    187 	     cm && cm->cmsg_len;
    188 	     cm = CMSG_NXTHDR(msg, cm)) {
    189 		if (cm->cmsg_level == SOL_SOCKET &&
    190 		    cm->cmsg_type == SCM_TIMESTAMPING) {
    191 			tss = (void *) CMSG_DATA(cm);
    192 		} else if ((cm->cmsg_level == SOL_IP &&
    193 			    cm->cmsg_type == IP_RECVERR) ||
    194 			   (cm->cmsg_level == SOL_IPV6 &&
    195 			    cm->cmsg_type == IPV6_RECVERR)) {
    196 			serr = (void *) CMSG_DATA(cm);
    197 			if (serr->ee_errno != ENOMSG ||
    198 			    serr->ee_origin != SO_EE_ORIGIN_TIMESTAMPING) {
    199 				fprintf(stderr, "unknown ip error %d %d\n",
    200 						serr->ee_errno,
    201 						serr->ee_origin);
    202 				serr = NULL;
    203 			}
    204 		} else if (cm->cmsg_level == SOL_IP &&
    205 			   cm->cmsg_type == IP_PKTINFO) {
    206 			struct in_pktinfo *info = (void *) CMSG_DATA(cm);
    207 			print_pktinfo(AF_INET, info->ipi_ifindex,
    208 				      &info->ipi_spec_dst, &info->ipi_addr);
    209 		} else if (cm->cmsg_level == SOL_IPV6 &&
    210 			   cm->cmsg_type == IPV6_PKTINFO) {
    211 			struct in6_pktinfo *info6 = (void *) CMSG_DATA(cm);
    212 			print_pktinfo(AF_INET6, info6->ipi6_ifindex,
    213 				      NULL, &info6->ipi6_addr);
    214 		} else
    215 			fprintf(stderr, "unknown cmsg %d,%d\n",
    216 					cm->cmsg_level, cm->cmsg_type);
    217 
    218 		if (serr && tss) {
    219 			print_timestamp(tss, serr->ee_info, serr->ee_data,
    220 					payload_len);
    221 			serr = NULL;
    222 			tss = NULL;
    223 			batch++;
    224 		}
    225 	}
    226 
    227 	if (batch > 1)
    228 		fprintf(stderr, "batched %d timestamps\n", batch);
    229 }
    230 
    231 static int recv_errmsg(int fd)
    232 {
    233 	static char ctrl[1024 /* overprovision*/];
    234 	static struct msghdr msg;
    235 	struct iovec entry;
    236 	static char *data;
    237 	int ret = 0;
    238 
    239 	data = malloc(cfg_payload_len);
    240 	if (!data)
    241 		error(1, 0, "malloc");
    242 
    243 	memset(&msg, 0, sizeof(msg));
    244 	memset(&entry, 0, sizeof(entry));
    245 	memset(ctrl, 0, sizeof(ctrl));
    246 
    247 	entry.iov_base = data;
    248 	entry.iov_len = cfg_payload_len;
    249 	msg.msg_iov = &entry;
    250 	msg.msg_iovlen = 1;
    251 	msg.msg_name = NULL;
    252 	msg.msg_namelen = 0;
    253 	msg.msg_control = ctrl;
    254 	msg.msg_controllen = sizeof(ctrl);
    255 
    256 	ret = recvmsg(fd, &msg, MSG_ERRQUEUE);
    257 	if (ret == -1 && errno != EAGAIN)
    258 		error(1, errno, "recvmsg");
    259 
    260 	if (ret >= 0) {
    261 		__recv_errmsg_cmsg(&msg, ret);
    262 		if (cfg_show_payload)
    263 			print_payload(data, cfg_payload_len);
    264 	}
    265 
    266 	free(data);
    267 	return ret == -1;
    268 }
    269 
    270 static void do_test(int family, unsigned int opt)
    271 {
    272 	char *buf;
    273 	int fd, i, val = 1, total_len;
    274 
    275 	if (family == AF_INET6 && cfg_proto != SOCK_STREAM) {
    276 		/* due to lack of checksum generation code */
    277 		fprintf(stderr, "test: skipping datagram over IPv6\n");
    278 		return;
    279 	}
    280 
    281 	total_len = cfg_payload_len;
    282 	if (cfg_proto == SOCK_RAW) {
    283 		total_len += sizeof(struct udphdr);
    284 		if (cfg_ipproto == IPPROTO_RAW)
    285 			total_len += sizeof(struct iphdr);
    286 	}
    287 
    288 	buf = malloc(total_len);
    289 	if (!buf)
    290 		error(1, 0, "malloc");
    291 
    292 	fd = socket(family, cfg_proto, cfg_ipproto);
    293 	if (fd < 0)
    294 		error(1, errno, "socket");
    295 
    296 	if (cfg_proto == SOCK_STREAM) {
    297 		if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
    298 			       (char*) &val, sizeof(val)))
    299 			error(1, 0, "setsockopt no nagle");
    300 
    301 		if (family == PF_INET) {
    302 			if (connect(fd, (void *) &daddr, sizeof(daddr)))
    303 				error(1, errno, "connect ipv4");
    304 		} else {
    305 			if (connect(fd, (void *) &daddr6, sizeof(daddr6)))
    306 				error(1, errno, "connect ipv6");
    307 		}
    308 	}
    309 
    310 	if (cfg_do_pktinfo) {
    311 		if (family == AF_INET6) {
    312 			if (setsockopt(fd, SOL_IPV6, IPV6_RECVPKTINFO,
    313 				       &val, sizeof(val)))
    314 				error(1, errno, "setsockopt pktinfo ipv6");
    315 		} else {
    316 			if (setsockopt(fd, SOL_IP, IP_PKTINFO,
    317 				       &val, sizeof(val)))
    318 				error(1, errno, "setsockopt pktinfo ipv4");
    319 		}
    320 	}
    321 
    322 	opt |= SOF_TIMESTAMPING_SOFTWARE |
    323 	       SOF_TIMESTAMPING_OPT_CMSG |
    324 	       SOF_TIMESTAMPING_OPT_ID;
    325 	if (cfg_loop_nodata)
    326 		opt |= SOF_TIMESTAMPING_OPT_TSONLY;
    327 
    328 	if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING,
    329 		       (char *) &opt, sizeof(opt)))
    330 		error(1, 0, "setsockopt timestamping");
    331 
    332 	for (i = 0; i < cfg_num_pkts; i++) {
    333 		memset(&ts_prev, 0, sizeof(ts_prev));
    334 		memset(buf, 'a' + i, total_len);
    335 
    336 		if (cfg_proto == SOCK_RAW) {
    337 			struct udphdr *udph;
    338 			int off = 0;
    339 
    340 			if (cfg_ipproto == IPPROTO_RAW) {
    341 				struct iphdr *iph = (void *) buf;
    342 
    343 				memset(iph, 0, sizeof(*iph));
    344 				iph->ihl      = 5;
    345 				iph->version  = 4;
    346 				iph->ttl      = 2;
    347 				iph->daddr    = daddr.sin_addr.s_addr;
    348 				iph->protocol = IPPROTO_UDP;
    349 				/* kernel writes saddr, csum, len */
    350 
    351 				off = sizeof(*iph);
    352 			}
    353 
    354 			udph = (void *) buf + off;
    355 			udph->source = ntohs(9000); 	/* random spoof */
    356 			udph->dest   = ntohs(dest_port);
    357 			udph->len    = ntohs(sizeof(*udph) + cfg_payload_len);
    358 			udph->check  = 0;	/* not allowed for IPv6 */
    359 		}
    360 
    361 		print_timestamp_usr();
    362 		if (cfg_proto != SOCK_STREAM) {
    363 			if (family == PF_INET)
    364 				val = sendto(fd, buf, total_len, 0, (void *) &daddr, sizeof(daddr));
    365 			else
    366 				val = sendto(fd, buf, total_len, 0, (void *) &daddr6, sizeof(daddr6));
    367 		} else {
    368 			val = send(fd, buf, cfg_payload_len, 0);
    369 		}
    370 		if (val != total_len)
    371 			error(1, errno, "send");
    372 
    373 		/* wait for all errors to be queued, else ACKs arrive OOO */
    374 		usleep(50 * 1000);
    375 
    376 		__poll(fd);
    377 
    378 		while (!recv_errmsg(fd)) {}
    379 	}
    380 
    381 	if (close(fd))
    382 		error(1, errno, "close");
    383 
    384 	free(buf);
    385 	usleep(400 * 1000);
    386 }
    387 
    388 static void __attribute__((noreturn)) usage(const char *filepath)
    389 {
    390 	fprintf(stderr, "\nUsage: %s [options] hostname\n"
    391 			"\nwhere options are:\n"
    392 			"  -4:   only IPv4\n"
    393 			"  -6:   only IPv6\n"
    394 			"  -h:   show this message\n"
    395 			"  -I:   request PKTINFO\n"
    396 			"  -l N: send N bytes at a time\n"
    397 			"  -n:   set no-payload option\n"
    398 			"  -r:   use raw\n"
    399 			"  -R:   use raw (IP_HDRINCL)\n"
    400 			"  -p N: connect to port N\n"
    401 			"  -u:   use udp\n"
    402 			"  -x:   show payload (up to 70 bytes)\n",
    403 			filepath);
    404 	exit(1);
    405 }
    406 
    407 static void parse_opt(int argc, char **argv)
    408 {
    409 	int proto_count = 0;
    410 	char c;
    411 
    412 	while ((c = getopt(argc, argv, "46hIl:np:rRux")) != -1) {
    413 		switch (c) {
    414 		case '4':
    415 			do_ipv6 = 0;
    416 			break;
    417 		case '6':
    418 			do_ipv4 = 0;
    419 			break;
    420 		case 'I':
    421 			cfg_do_pktinfo = true;
    422 			break;
    423 		case 'n':
    424 			cfg_loop_nodata = true;
    425 			break;
    426 		case 'r':
    427 			proto_count++;
    428 			cfg_proto = SOCK_RAW;
    429 			cfg_ipproto = IPPROTO_UDP;
    430 			break;
    431 		case 'R':
    432 			proto_count++;
    433 			cfg_proto = SOCK_RAW;
    434 			cfg_ipproto = IPPROTO_RAW;
    435 			break;
    436 		case 'u':
    437 			proto_count++;
    438 			cfg_proto = SOCK_DGRAM;
    439 			cfg_ipproto = IPPROTO_UDP;
    440 			break;
    441 		case 'l':
    442 			cfg_payload_len = strtoul(optarg, NULL, 10);
    443 			break;
    444 		case 'p':
    445 			dest_port = strtoul(optarg, NULL, 10);
    446 			break;
    447 		case 'x':
    448 			cfg_show_payload = true;
    449 			break;
    450 		case 'h':
    451 		default:
    452 			usage(argv[0]);
    453 		}
    454 	}
    455 
    456 	if (!cfg_payload_len)
    457 		error(1, 0, "payload may not be nonzero");
    458 	if (cfg_proto != SOCK_STREAM && cfg_payload_len > 1472)
    459 		error(1, 0, "udp packet might exceed expected MTU");
    460 	if (!do_ipv4 && !do_ipv6)
    461 		error(1, 0, "pass -4 or -6, not both");
    462 	if (proto_count > 1)
    463 		error(1, 0, "pass -r, -R or -u, not multiple");
    464 
    465 	if (optind != argc - 1)
    466 		error(1, 0, "missing required hostname argument");
    467 }
    468 
    469 static void resolve_hostname(const char *hostname)
    470 {
    471 	struct addrinfo *addrs, *cur;
    472 	int have_ipv4 = 0, have_ipv6 = 0;
    473 
    474 	if (getaddrinfo(hostname, NULL, NULL, &addrs))
    475 		error(1, errno, "getaddrinfo");
    476 
    477 	cur = addrs;
    478 	while (cur && !have_ipv4 && !have_ipv6) {
    479 		if (!have_ipv4 && cur->ai_family == AF_INET) {
    480 			memcpy(&daddr, cur->ai_addr, sizeof(daddr));
    481 			daddr.sin_port = htons(dest_port);
    482 			have_ipv4 = 1;
    483 		}
    484 		else if (!have_ipv6 && cur->ai_family == AF_INET6) {
    485 			memcpy(&daddr6, cur->ai_addr, sizeof(daddr6));
    486 			daddr6.sin6_port = htons(dest_port);
    487 			have_ipv6 = 1;
    488 		}
    489 		cur = cur->ai_next;
    490 	}
    491 	if (addrs)
    492 		freeaddrinfo(addrs);
    493 
    494 	do_ipv4 &= have_ipv4;
    495 	do_ipv6 &= have_ipv6;
    496 }
    497 
    498 static void do_main(int family)
    499 {
    500 	fprintf(stderr, "family:       %s\n",
    501 			family == PF_INET ? "INET" : "INET6");
    502 
    503 	fprintf(stderr, "test SND\n");
    504 	do_test(family, SOF_TIMESTAMPING_TX_SOFTWARE);
    505 
    506 	fprintf(stderr, "test ENQ\n");
    507 	do_test(family, SOF_TIMESTAMPING_TX_SCHED);
    508 
    509 	fprintf(stderr, "test ENQ + SND\n");
    510 	do_test(family, SOF_TIMESTAMPING_TX_SCHED |
    511 			SOF_TIMESTAMPING_TX_SOFTWARE);
    512 
    513 	if (cfg_proto == SOCK_STREAM) {
    514 		fprintf(stderr, "\ntest ACK\n");
    515 		do_test(family, SOF_TIMESTAMPING_TX_ACK);
    516 
    517 		fprintf(stderr, "\ntest SND + ACK\n");
    518 		do_test(family, SOF_TIMESTAMPING_TX_SOFTWARE |
    519 				SOF_TIMESTAMPING_TX_ACK);
    520 
    521 		fprintf(stderr, "\ntest ENQ + SND + ACK\n");
    522 		do_test(family, SOF_TIMESTAMPING_TX_SCHED |
    523 				SOF_TIMESTAMPING_TX_SOFTWARE |
    524 				SOF_TIMESTAMPING_TX_ACK);
    525 	}
    526 }
    527 
    528 const char *sock_names[] = { NULL, "TCP", "UDP", "RAW" };
    529 
    530 int main(int argc, char **argv)
    531 {
    532 	if (argc == 1)
    533 		usage(argv[0]);
    534 
    535 	parse_opt(argc, argv);
    536 	resolve_hostname(argv[argc - 1]);
    537 
    538 	fprintf(stderr, "protocol:     %s\n", sock_names[cfg_proto]);
    539 	fprintf(stderr, "payload:      %u\n", cfg_payload_len);
    540 	fprintf(stderr, "server port:  %u\n", dest_port);
    541 	fprintf(stderr, "\n");
    542 
    543 	if (do_ipv4)
    544 		do_main(PF_INET);
    545 	if (do_ipv6)
    546 		do_main(PF_INET6);
    547 
    548 	return 0;
    549 }
    550