Home | History | Annotate | Download | only in iputils
      1 /*
      2  * tracepath6.c
      3  *
      4  *		This program is free software; you can redistribute it and/or
      5  *		modify it under the terms of the GNU General Public License
      6  *		as published by the Free Software Foundation; either version
      7  *		2 of the License, or (at your option) any later version.
      8  *
      9  * Authors:	Alexey Kuznetsov, <kuznet (at) ms2.inr.ac.ru>
     10  */
     11 
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <unistd.h>
     15 #include <sys/socket.h>
     16 #include <netinet/in.h>
     17 #include <netinet/icmp6.h>
     18 
     19 #include <linux/types.h>
     20 #include <linux/errqueue.h>
     21 #include <errno.h>
     22 #include <string.h>
     23 #include <netdb.h>
     24 #include <resolv.h>
     25 #include <sys/time.h>
     26 #include <sys/uio.h>
     27 #include <arpa/inet.h>
     28 
     29 #ifdef USE_IDN
     30 #include <idna.h>
     31 #include <locale.h>
     32 #endif
     33 
     34 #ifndef SOL_IPV6
     35 #define SOL_IPV6 IPPROTO_IPV6
     36 #endif
     37 
     38 #ifndef IP_PMTUDISC_DO
     39 #define IP_PMTUDISC_DO		3
     40 #endif
     41 #ifndef IPV6_PMTUDISC_DO
     42 #define IPV6_PMTUDISC_DO	3
     43 #endif
     44 
     45 #define MAX_HOPS_LIMIT		255
     46 #define MAX_HOPS_DEFAULT	30
     47 
     48 struct hhistory
     49 {
     50 	int	hops;
     51 	struct timeval sendtime;
     52 };
     53 
     54 struct hhistory his[64];
     55 int hisptr;
     56 
     57 sa_family_t family = AF_INET6;
     58 struct sockaddr_storage target;
     59 socklen_t targetlen;
     60 __u16 base_port;
     61 int max_hops = MAX_HOPS_DEFAULT;
     62 
     63 int overhead;
     64 int mtu;
     65 void *pktbuf;
     66 int hops_to = -1;
     67 int hops_from = -1;
     68 int no_resolve = 0;
     69 int show_both = 0;
     70 int mapped;
     71 
     72 #define HOST_COLUMN_SIZE	52
     73 
     74 struct probehdr
     75 {
     76 	__u32 ttl;
     77 	struct timeval tv;
     78 };
     79 
     80 void data_wait(int fd)
     81 {
     82 	fd_set fds;
     83 	struct timeval tv;
     84 	FD_ZERO(&fds);
     85 	FD_SET(fd, &fds);
     86 	tv.tv_sec = 1;
     87 	tv.tv_usec = 0;
     88 	select(fd+1, &fds, NULL, NULL, &tv);
     89 }
     90 
     91 void print_host(const char *a, const char *b, int both)
     92 {
     93 	int plen;
     94 	plen = printf("%s", a);
     95 	if (both)
     96 		plen += printf(" (%s)", b);
     97 	if (plen >= HOST_COLUMN_SIZE)
     98 		plen = HOST_COLUMN_SIZE - 1;
     99 	printf("%*s", HOST_COLUMN_SIZE - plen, "");
    100 }
    101 
    102 int recverr(int fd, int ttl)
    103 {
    104 	int res;
    105 	struct probehdr rcvbuf;
    106 	char cbuf[512];
    107 	struct iovec  iov;
    108 	struct msghdr msg;
    109 	struct cmsghdr *cmsg;
    110 	struct sock_extended_err *e;
    111 	struct sockaddr_storage addr;
    112 	struct timeval tv;
    113 	struct timeval *rettv;
    114 	int slot = 0;
    115 	int rethops;
    116 	int sndhops;
    117 	int progress = -1;
    118 	int broken_router;
    119 
    120 restart:
    121 	memset(&rcvbuf, -1, sizeof(rcvbuf));
    122 	iov.iov_base = &rcvbuf;
    123 	iov.iov_len = sizeof(rcvbuf);
    124 	msg.msg_name = (caddr_t)&addr;
    125 	msg.msg_namelen = sizeof(addr);
    126 	msg.msg_iov = &iov;
    127 	msg.msg_iovlen = 1;
    128 	msg.msg_flags = 0;
    129 	msg.msg_control = cbuf;
    130 	msg.msg_controllen = sizeof(cbuf);
    131 
    132 	gettimeofday(&tv, NULL);
    133 	res = recvmsg(fd, &msg, MSG_ERRQUEUE);
    134 	if (res < 0) {
    135 		if (errno == EAGAIN)
    136 			return progress;
    137 		goto restart;
    138 	}
    139 
    140 	progress = mtu;
    141 
    142 	rethops = -1;
    143 	sndhops = -1;
    144 	e = NULL;
    145 	rettv = NULL;
    146 
    147 	slot = -base_port;
    148 	switch (family) {
    149 	case AF_INET6:
    150 		slot += ntohs(((struct sockaddr_in6 *)&addr)->sin6_port);
    151 		break;
    152 	case AF_INET:
    153 		slot += ntohs(((struct sockaddr_in *)&addr)->sin_port);
    154 		break;
    155 	}
    156 
    157 	if (slot >= 0 && slot < 63 && his[slot].hops) {
    158 		sndhops = his[slot].hops;
    159 		rettv = &his[slot].sendtime;
    160 		his[slot].hops = 0;
    161 	}
    162 	broken_router = 0;
    163 	if (res == sizeof(rcvbuf)) {
    164 		if (rcvbuf.ttl == 0 || rcvbuf.tv.tv_sec == 0)
    165 			broken_router = 1;
    166 		else {
    167 			sndhops = rcvbuf.ttl;
    168 			rettv = &rcvbuf.tv;
    169 		}
    170 	}
    171 
    172 	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
    173 		switch (cmsg->cmsg_level) {
    174 		case SOL_IPV6:
    175 			switch(cmsg->cmsg_type) {
    176 			case IPV6_RECVERR:
    177 				e = (struct sock_extended_err *)CMSG_DATA(cmsg);
    178 				break;
    179 			case IPV6_HOPLIMIT:
    180 #ifdef IPV6_2292HOPLIMIT
    181 			case IPV6_2292HOPLIMIT:
    182 #endif
    183 				memcpy(&rethops, CMSG_DATA(cmsg), sizeof(rethops));
    184 				break;
    185 			default:
    186 				printf("cmsg6:%d\n ", cmsg->cmsg_type);
    187 			}
    188 			break;
    189 		case SOL_IP:
    190 			switch(cmsg->cmsg_type) {
    191 			case IP_RECVERR:
    192 				e = (struct sock_extended_err *)CMSG_DATA(cmsg);
    193 				break;
    194 			case IP_TTL:
    195 				rethops = *(__u8*)CMSG_DATA(cmsg);
    196 				break;
    197 			default:
    198 				printf("cmsg4:%d\n ", cmsg->cmsg_type);
    199 			}
    200 		}
    201 	}
    202 	if (e == NULL) {
    203 		printf("no info\n");
    204 		return 0;
    205 	}
    206 	if (e->ee_origin == SO_EE_ORIGIN_LOCAL)
    207 		printf("%2d?: %-32s ", ttl, "[LOCALHOST]");
    208 	else if (e->ee_origin == SO_EE_ORIGIN_ICMP6 ||
    209 		 e->ee_origin == SO_EE_ORIGIN_ICMP) {
    210 		char abuf[NI_MAXHOST], hbuf[NI_MAXHOST];
    211 		struct sockaddr *sa = (struct sockaddr *)(e + 1);
    212 		socklen_t salen;
    213 
    214 		if (sndhops>0)
    215 			printf("%2d:  ", sndhops);
    216 		else
    217 			printf("%2d?: ", ttl);
    218 
    219 		switch (sa->sa_family) {
    220 		case AF_INET6:
    221 			salen = sizeof(struct sockaddr_in6);
    222 			break;
    223 		case AF_INET:
    224 			salen = sizeof(struct sockaddr_in);
    225 			break;
    226 		default:
    227 			salen = 0;
    228 		}
    229 
    230 		if (no_resolve || show_both) {
    231 			if (getnameinfo(sa, salen,
    232 					abuf, sizeof(abuf), NULL, 0,
    233 					NI_NUMERICHOST))
    234 				strcpy(abuf, "???");
    235 		} else
    236 			abuf[0] = 0;
    237 
    238 		if (!no_resolve || show_both) {
    239 			fflush(stdout);
    240 			if (getnameinfo(sa, salen,
    241 					hbuf, sizeof(hbuf), NULL, 0,
    242 					0
    243 #ifdef USE_IDN
    244 					| NI_IDN
    245 #endif
    246 				        ))
    247 				strcpy(hbuf, "???");
    248 		} else
    249 			hbuf[0] = 0;
    250 
    251 		if (no_resolve)
    252 			print_host(abuf, hbuf, show_both);
    253 		else
    254 			print_host(hbuf, abuf, show_both);
    255 	}
    256 
    257 	if (rettv) {
    258 		int diff = (tv.tv_sec-rettv->tv_sec)*1000000+(tv.tv_usec-rettv->tv_usec);
    259 		printf("%3d.%03dms ", diff/1000, diff%1000);
    260 		if (broken_router)
    261 			printf("(This broken router returned corrupted payload) ");
    262 	}
    263 
    264 	switch (e->ee_errno) {
    265 	case ETIMEDOUT:
    266 		printf("\n");
    267 		break;
    268 	case EMSGSIZE:
    269 		printf("pmtu %d\n", e->ee_info);
    270 		mtu = e->ee_info;
    271 		progress = mtu;
    272 		break;
    273 	case ECONNREFUSED:
    274 		printf("reached\n");
    275 		hops_to = sndhops<0 ? ttl : sndhops;
    276 		hops_from = rethops;
    277 		return 0;
    278 	case EPROTO:
    279 		printf("!P\n");
    280 		return 0;
    281 	case EHOSTUNREACH:
    282 		if ((e->ee_origin == SO_EE_ORIGIN_ICMP &&
    283 		     e->ee_type == 11 &&
    284 		     e->ee_code == 0) ||
    285 		    (e->ee_origin == SO_EE_ORIGIN_ICMP6 &&
    286 		     e->ee_type == 3 &&
    287 		     e->ee_code == 0)) {
    288 			if (rethops>=0) {
    289 				if (rethops<=64)
    290 					rethops = 65-rethops;
    291 				else if (rethops<=128)
    292 					rethops = 129-rethops;
    293 				else
    294 					rethops = 256-rethops;
    295 				if (sndhops>=0 && rethops != sndhops)
    296 					printf("asymm %2d ", rethops);
    297 				else if (sndhops<0 && rethops != ttl)
    298 					printf("asymm %2d ", rethops);
    299 			}
    300 			printf("\n");
    301 			break;
    302 		}
    303 		printf("!H\n");
    304 		return 0;
    305 	case ENETUNREACH:
    306 		printf("!N\n");
    307 		return 0;
    308 	case EACCES:
    309 		printf("!A\n");
    310 		return 0;
    311 	default:
    312 		printf("\n");
    313 		errno = e->ee_errno;
    314 		perror("NET ERROR");
    315 		return 0;
    316 	}
    317 	goto restart;
    318 }
    319 
    320 int probe_ttl(int fd, int ttl)
    321 {
    322 	int i;
    323 	struct probehdr *hdr = pktbuf;
    324 
    325 	memset(pktbuf, 0, mtu);
    326 restart:
    327 
    328 	for (i=0; i<10; i++) {
    329 		int res;
    330 
    331 		hdr->ttl = ttl;
    332 		switch (family) {
    333 		case AF_INET6:
    334 			((struct sockaddr_in6 *)&target)->sin6_port = htons(base_port + hisptr);
    335 			break;
    336 		case AF_INET:
    337 			((struct sockaddr_in *)&target)->sin_port = htons(base_port + hisptr);
    338 			break;
    339 		}
    340 		gettimeofday(&hdr->tv, NULL);
    341 		his[hisptr].hops = ttl;
    342 		his[hisptr].sendtime = hdr->tv;
    343 		if (sendto(fd, pktbuf, mtu-overhead, 0, (struct sockaddr *)&target, targetlen) > 0)
    344 			break;
    345 		res = recverr(fd, ttl);
    346 		his[hisptr].hops = 0;
    347 		if (res==0)
    348 			return 0;
    349 		if (res > 0)
    350 			goto restart;
    351 	}
    352 	hisptr = (hisptr + 1) & 63;
    353 
    354 	if (i<10) {
    355 		data_wait(fd);
    356 		if (recv(fd, pktbuf, mtu, MSG_DONTWAIT) > 0) {
    357 			printf("%2d?: reply received 8)\n", ttl);
    358 			return 0;
    359 		}
    360 		return recverr(fd, ttl);
    361 	}
    362 
    363 	printf("%2d:  send failed\n", ttl);
    364 	return 0;
    365 }
    366 
    367 static void usage(void) __attribute((noreturn));
    368 
    369 static void usage(void)
    370 {
    371 	fprintf(stderr, "Usage: tracepath6 [-n] [-b] [-l <len>] [-p port] <destination>\n");
    372 	exit(-1);
    373 }
    374 
    375 
    376 int main(int argc, char **argv)
    377 {
    378 	int fd;
    379 	int on;
    380 	int ttl;
    381 	char *p;
    382 	struct addrinfo hints, *ai, *ai0;
    383 	int ch;
    384 	int gai;
    385 	char pbuf[NI_MAXSERV];
    386 
    387 #ifdef USE_IDN
    388 	setlocale(LC_ALL, "");
    389 #endif
    390 
    391 	while ((ch = getopt(argc, argv, "nbh?l:m:p:")) != EOF) {
    392 		switch(ch) {
    393 		case 'n':
    394 			no_resolve = 1;
    395 			break;
    396 		case 'b':
    397 			show_both = 1;
    398 			break;
    399 		case 'l':
    400 			mtu = atoi(optarg);
    401 			break;
    402 		case 'm':
    403 			max_hops = atoi(optarg);
    404 			if (max_hops < 0 || max_hops > MAX_HOPS_LIMIT) {
    405 				fprintf(stderr,
    406 					"Error: max hops must be 0 .. %d (inclusive).\n",
    407 					MAX_HOPS_LIMIT);
    408 			}
    409 			break;
    410 		case 'p':
    411 			base_port = atoi(optarg);
    412 			break;
    413 		default:
    414 			usage();
    415 		}
    416 	}
    417 
    418 	argc -= optind;
    419 	argv += optind;
    420 
    421 	if (argc != 1)
    422 		usage();
    423 
    424 	/* Backward compatiblity */
    425 	if (!base_port) {
    426 		p = strchr(argv[0], '/');
    427 		if (p) {
    428 			*p = 0;
    429 			base_port = (unsigned)atoi(p+1);
    430 		} else {
    431 			base_port = 44444;
    432 		}
    433 	}
    434 	sprintf(pbuf, "%u", base_port);
    435 
    436 	memset(&hints, 0, sizeof(hints));
    437 	hints.ai_family = family;
    438 	hints.ai_socktype = SOCK_DGRAM;
    439 	hints.ai_protocol = IPPROTO_UDP;
    440 #ifdef USE_IDN
    441 	hints.ai_flags = AI_IDN;
    442 #endif
    443 	gai = getaddrinfo(argv[0], pbuf, &hints, &ai0);
    444 	if (gai) {
    445 		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai));
    446 		exit(1);
    447 	}
    448 
    449 	fd = -1;
    450 	for (ai = ai0; ai; ai = ai->ai_next) {
    451 		/* sanity check */
    452 		if (family && ai->ai_family != family)
    453 			continue;
    454 		if (ai->ai_family != AF_INET6 &&
    455 		    ai->ai_family != AF_INET)
    456 			continue;
    457 		family = ai->ai_family;
    458 		fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
    459 		if (fd < 0)
    460 			continue;
    461 		memcpy(&target, ai->ai_addr, sizeof(target));
    462 		targetlen = ai->ai_addrlen;
    463 		break;
    464 	}
    465 	if (fd < 0) {
    466 		perror("socket/connect");
    467 		exit(1);
    468 	}
    469 	freeaddrinfo(ai0);
    470 
    471 	switch (family) {
    472 	case AF_INET6:
    473 		overhead = 48;
    474 		if (!mtu)
    475 			mtu = 128000;
    476 		if (mtu <= overhead)
    477 			goto pktlen_error;
    478 
    479 		on = IPV6_PMTUDISC_DO;
    480 		if (setsockopt(fd, SOL_IPV6, IPV6_MTU_DISCOVER, &on, sizeof(on)) &&
    481 		    (on = IPV6_PMTUDISC_DO,
    482 		     setsockopt(fd, SOL_IPV6, IPV6_MTU_DISCOVER, &on, sizeof(on)))) {
    483 			perror("IPV6_MTU_DISCOVER");
    484 			exit(1);
    485 		}
    486 		on = 1;
    487 		if (setsockopt(fd, SOL_IPV6, IPV6_RECVERR, &on, sizeof(on))) {
    488 			perror("IPV6_RECVERR");
    489 			exit(1);
    490 		}
    491 		if (
    492 #ifdef IPV6_RECVHOPLIMIT
    493 		    setsockopt(fd, SOL_IPV6, IPV6_HOPLIMIT, &on, sizeof(on)) &&
    494 		    setsockopt(fd, SOL_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on))
    495 #else
    496 		    setsockopt(fd, SOL_IPV6, IPV6_HOPLIMIT, &on, sizeof(on))
    497 #endif
    498 		    ) {
    499 			perror("IPV6_HOPLIMIT");
    500 			exit(1);
    501 		}
    502 		if (!IN6_IS_ADDR_V4MAPPED(&(((struct sockaddr_in6 *)&target)->sin6_addr)))
    503 			break;
    504 		mapped = 1;
    505 		/*FALLTHROUGH*/
    506 	case AF_INET:
    507 		overhead = 28;
    508 		if (!mtu)
    509 			mtu = 65535;
    510 		if (mtu <= overhead)
    511 			goto pktlen_error;
    512 
    513 		on = IP_PMTUDISC_DO;
    514 		if (setsockopt(fd, SOL_IP, IP_MTU_DISCOVER, &on, sizeof(on))) {
    515 			perror("IP_MTU_DISCOVER");
    516 			exit(1);
    517 		}
    518 		on = 1;
    519 		if (setsockopt(fd, SOL_IP, IP_RECVERR, &on, sizeof(on))) {
    520 			perror("IP_RECVERR");
    521 			exit(1);
    522 		}
    523 		if (setsockopt(fd, SOL_IP, IP_RECVTTL, &on, sizeof(on))) {
    524 			perror("IP_RECVTTL");
    525 			exit(1);
    526 		}
    527 	}
    528 
    529 	pktbuf = malloc(mtu);
    530 	if (!pktbuf) {
    531 		perror("malloc");
    532 		exit(1);
    533 	}
    534 
    535 	for (ttl = 1; ttl <= max_hops; ttl++) {
    536 		int res;
    537 		int i;
    538 
    539 		on = ttl;
    540 		switch (family) {
    541 		case AF_INET6:
    542 			if (setsockopt(fd, SOL_IPV6, IPV6_UNICAST_HOPS, &on, sizeof(on))) {
    543 				perror("IPV6_UNICAST_HOPS");
    544 				exit(1);
    545 			}
    546 			if (!mapped)
    547 				break;
    548 			/*FALLTHROUGH*/
    549 		case AF_INET:
    550 			if (setsockopt(fd, SOL_IP, IP_TTL, &on, sizeof(on))) {
    551 				perror("IP_TTL");
    552 				exit(1);
    553 			}
    554 		}
    555 
    556 restart:
    557 		for (i=0; i<3; i++) {
    558 			int old_mtu;
    559 
    560 			old_mtu = mtu;
    561 			res = probe_ttl(fd, ttl);
    562 			if (mtu != old_mtu)
    563 				goto restart;
    564 			if (res == 0)
    565 				goto done;
    566 			if (res > 0)
    567 				break;
    568 		}
    569 
    570 		if (res < 0)
    571 			printf("%2d:  no reply\n", ttl);
    572 	}
    573 	printf("     Too many hops: pmtu %d\n", mtu);
    574 
    575 done:
    576 	printf("     Resume: pmtu %d ", mtu);
    577 	if (hops_to>=0)
    578 		printf("hops %d ", hops_to);
    579 	if (hops_from>=0)
    580 		printf("back %d ", hops_from);
    581 	printf("\n");
    582 	exit(0);
    583 
    584 pktlen_error:
    585 	fprintf(stderr, "Error: pktlen must be > %d and <= %d\n",
    586 		overhead, INT_MAX);
    587 	exit(1);
    588 }
    589