• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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@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 
data_wait(int fd)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 
print_host(const char * a,const char * b,int both)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 
recverr(int fd,int ttl)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 
probe_ttl(int fd,int ttl)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 
usage(void)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 
main(int argc,char ** argv)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