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
48struct hhistory
49{
50	int	hops;
51	struct timeval sendtime;
52};
53
54struct hhistory his[64];
55int hisptr;
56
57sa_family_t family = AF_INET6;
58struct sockaddr_storage target;
59socklen_t targetlen;
60__u16 base_port;
61int max_hops = MAX_HOPS_DEFAULT;
62
63int overhead;
64int mtu;
65void *pktbuf;
66int hops_to = -1;
67int hops_from = -1;
68int no_resolve = 0;
69int show_both = 0;
70int mapped;
71
72#define HOST_COLUMN_SIZE	52
73
74struct probehdr
75{
76	__u32 ttl;
77	struct timeval tv;
78};
79
80void 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
91void 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
102int 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
120restart:
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
320int probe_ttl(int fd, int ttl)
321{
322	int i;
323	struct probehdr *hdr = pktbuf;
324
325	memset(pktbuf, 0, mtu);
326restart:
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
367static void usage(void) __attribute((noreturn));
368
369static void usage(void)
370{
371	fprintf(stderr, "Usage: tracepath6 [-n] [-b] [-l <len>] [-p port] <destination>\n");
372	exit(-1);
373}
374
375
376int 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
556restart:
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
575done:
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
584pktlen_error:
585	fprintf(stderr, "Error: pktlen must be > %d and <= %d\n",
586		overhead, INT_MAX);
587	exit(1);
588}
589