1/*
2 * tracepath.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 <linux/types.h>
17#include <linux/errqueue.h>
18#include <errno.h>
19#include <string.h>
20#include <netdb.h>
21#include <netinet/in.h>
22#include <resolv.h>
23#include <sys/time.h>
24#include <sys/uio.h>
25#include <arpa/inet.h>
26#ifdef USE_IDN
27#include <idna.h>
28#include <locale.h>
29#endif
30
31#ifndef IP_PMTUDISC_PROBE
32#define IP_PMTUDISC_PROBE	3
33#endif
34
35#define MAX_HOPS_LIMIT		255
36#define MAX_HOPS_DEFAULT	30
37
38struct hhistory
39{
40	int	hops;
41	struct timeval sendtime;
42};
43
44struct hhistory his[64];
45int hisptr;
46
47struct sockaddr_in target;
48__u16 base_port;
49int max_hops = MAX_HOPS_DEFAULT;
50
51const int overhead = 28;
52int mtu = 65535;
53void *pktbuf;
54int hops_to = -1;
55int hops_from = -1;
56int no_resolve = 0;
57int show_both = 0;
58
59#define HOST_COLUMN_SIZE	52
60
61struct probehdr
62{
63	__u32 ttl;
64	struct timeval tv;
65};
66
67void data_wait(int fd)
68{
69	fd_set fds;
70	struct timeval tv;
71	FD_ZERO(&fds);
72	FD_SET(fd, &fds);
73	tv.tv_sec = 1;
74	tv.tv_usec = 0;
75	select(fd+1, &fds, NULL, NULL, &tv);
76}
77
78void print_host(const char *a, const char *b, int both)
79{
80	int plen;
81	plen = printf("%s", a);
82	if (both)
83		plen += printf(" (%s)", b);
84	if (plen >= HOST_COLUMN_SIZE)
85		plen = HOST_COLUMN_SIZE - 1;
86	printf("%*s", HOST_COLUMN_SIZE - plen, "");
87}
88
89int recverr(int fd, int ttl)
90{
91	int res;
92	struct probehdr rcvbuf;
93	char cbuf[512];
94	struct iovec  iov;
95	struct msghdr msg;
96	struct cmsghdr *cmsg;
97	struct sock_extended_err *e;
98	struct sockaddr_in addr;
99	struct timeval tv;
100	struct timeval *rettv;
101	int slot;
102	int rethops;
103	int sndhops;
104	int progress = -1;
105	int broken_router;
106
107restart:
108	memset(&rcvbuf, -1, sizeof(rcvbuf));
109	iov.iov_base = &rcvbuf;
110	iov.iov_len = sizeof(rcvbuf);
111	msg.msg_name = (__u8*)&addr;
112	msg.msg_namelen = sizeof(addr);
113	msg.msg_iov = &iov;
114	msg.msg_iovlen = 1;
115	msg.msg_flags = 0;
116	msg.msg_control = cbuf;
117	msg.msg_controllen = sizeof(cbuf);
118
119	gettimeofday(&tv, NULL);
120	res = recvmsg(fd, &msg, MSG_ERRQUEUE);
121	if (res < 0) {
122		if (errno == EAGAIN)
123			return progress;
124		goto restart;
125	}
126
127	progress = mtu;
128
129	rethops = -1;
130	sndhops = -1;
131	e = NULL;
132	rettv = NULL;
133	slot = ntohs(addr.sin_port) - base_port;
134	if (slot>=0 && slot < 63 && his[slot].hops) {
135		sndhops = his[slot].hops;
136		rettv = &his[slot].sendtime;
137		his[slot].hops = 0;
138	}
139	broken_router = 0;
140	if (res == sizeof(rcvbuf)) {
141		if (rcvbuf.ttl == 0 || rcvbuf.tv.tv_sec == 0) {
142			broken_router = 1;
143		} else {
144			sndhops = rcvbuf.ttl;
145			rettv = &rcvbuf.tv;
146		}
147	}
148
149	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
150		if (cmsg->cmsg_level == SOL_IP) {
151			if (cmsg->cmsg_type == IP_RECVERR) {
152				e = (struct sock_extended_err *) CMSG_DATA(cmsg);
153			} else if (cmsg->cmsg_type == IP_TTL) {
154				memcpy(&rethops, CMSG_DATA(cmsg), sizeof(rethops));
155			} else {
156				printf("cmsg:%d\n ", cmsg->cmsg_type);
157			}
158		}
159	}
160	if (e == NULL) {
161		printf("no info\n");
162		return 0;
163	}
164	if (e->ee_origin == SO_EE_ORIGIN_LOCAL) {
165		printf("%2d?: %*s ", ttl, -(HOST_COLUMN_SIZE - 1), "[LOCALHOST]");
166	} else if (e->ee_origin == SO_EE_ORIGIN_ICMP) {
167		char abuf[128];
168		struct sockaddr_in *sin = (struct sockaddr_in*)(e+1);
169		struct hostent *h = NULL;
170		char *idn = NULL;
171
172		inet_ntop(AF_INET, &sin->sin_addr, abuf, sizeof(abuf));
173
174		if (sndhops>0)
175			printf("%2d:  ", sndhops);
176		else
177			printf("%2d?: ", ttl);
178
179		if (!no_resolve || show_both) {
180			fflush(stdout);
181			h = gethostbyaddr((char *) &sin->sin_addr, sizeof(sin->sin_addr), AF_INET);
182		}
183
184#ifdef USE_IDN
185		if (h && idna_to_unicode_lzlz(h->h_name, &idn, 0) != IDNA_SUCCESS)
186			idn = NULL;
187#endif
188		if (no_resolve)
189			print_host(abuf, h ? (idn ? idn : h->h_name) : abuf, show_both);
190		else
191			print_host(h ? (idn ? idn : h->h_name) : abuf, abuf, show_both);
192
193#ifdef USE_IDN
194		free(idn);
195#endif
196	}
197
198	if (rettv) {
199		int diff = (tv.tv_sec-rettv->tv_sec)*1000000+(tv.tv_usec-rettv->tv_usec);
200		printf("%3d.%03dms ", diff/1000, diff%1000);
201		if (broken_router)
202			printf("(This broken router returned corrupted payload) ");
203	}
204
205	switch (e->ee_errno) {
206	case ETIMEDOUT:
207		printf("\n");
208		break;
209	case EMSGSIZE:
210		printf("pmtu %d\n", e->ee_info);
211		mtu = e->ee_info;
212		progress = mtu;
213		break;
214	case ECONNREFUSED:
215		printf("reached\n");
216		hops_to = sndhops<0 ? ttl : sndhops;
217		hops_from = rethops;
218		return 0;
219	case EPROTO:
220		printf("!P\n");
221		return 0;
222	case EHOSTUNREACH:
223		if (e->ee_origin == SO_EE_ORIGIN_ICMP &&
224		    e->ee_type == 11 &&
225		    e->ee_code == 0) {
226			if (rethops>=0) {
227				if (rethops<=64)
228					rethops = 65-rethops;
229				else if (rethops<=128)
230					rethops = 129-rethops;
231				else
232					rethops = 256-rethops;
233				if (sndhops>=0 && rethops != sndhops)
234					printf("asymm %2d ", rethops);
235				else if (sndhops<0 && rethops != ttl)
236					printf("asymm %2d ", rethops);
237			}
238			printf("\n");
239			break;
240		}
241		printf("!H\n");
242		return 0;
243	case ENETUNREACH:
244		printf("!N\n");
245		return 0;
246	case EACCES:
247		printf("!A\n");
248		return 0;
249	default:
250		printf("\n");
251		errno = e->ee_errno;
252		perror("NET ERROR");
253		return 0;
254	}
255	goto restart;
256}
257
258int probe_ttl(int fd, int ttl)
259{
260	int i;
261	struct probehdr *hdr = pktbuf;
262
263	memset(pktbuf, 0, mtu);
264restart:
265	for (i=0; i<10; i++) {
266		int res;
267
268		hdr->ttl = ttl;
269		target.sin_port = htons(base_port + hisptr);
270		gettimeofday(&hdr->tv, NULL);
271		his[hisptr].hops = ttl;
272		his[hisptr].sendtime = hdr->tv;
273		if (sendto(fd, pktbuf, mtu-overhead, 0, (struct sockaddr*)&target, sizeof(target)) > 0)
274			break;
275		res = recverr(fd, ttl);
276		his[hisptr].hops = 0;
277		if (res==0)
278			return 0;
279		if (res > 0)
280			goto restart;
281	}
282	hisptr = (hisptr + 1)&63;
283
284	if (i<10) {
285		data_wait(fd);
286		if (recv(fd, pktbuf, mtu, MSG_DONTWAIT) > 0) {
287			printf("%2d?: reply received 8)\n", ttl);
288			return 0;
289		}
290		return recverr(fd, ttl);
291	}
292
293	printf("%2d:  send failed\n", ttl);
294	return 0;
295}
296
297static void usage(void) __attribute((noreturn));
298
299static void usage(void)
300{
301	fprintf(stderr, "Usage: tracepath [-n] [-b] [-l <len>] [-p port] <destination>\n");
302	exit(-1);
303}
304
305int
306main(int argc, char **argv)
307{
308	struct hostent *he;
309	int fd;
310	int on;
311	int ttl;
312	char *p;
313	int ch;
314#ifdef USE_IDN
315	int rc;
316	setlocale(LC_ALL, "");
317#endif
318
319	while ((ch = getopt(argc, argv, "nbh?l:m:p:")) != EOF) {
320		switch(ch) {
321		case 'n':
322			no_resolve = 1;
323			break;
324		case 'b':
325			show_both = 1;
326			break;
327		case 'l':
328			if ((mtu = atoi(optarg)) <= overhead) {
329				fprintf(stderr, "Error: pktlen must be > %d and <= %d.\n",
330					overhead, INT_MAX);
331				exit(1);
332			}
333			break;
334		case 'm':
335			max_hops = atoi(optarg);
336			if (max_hops < 0 || max_hops > MAX_HOPS_LIMIT) {
337				fprintf(stderr,
338					"Error: max hops must be 0 .. %d (inclusive).\n",
339					MAX_HOPS_LIMIT);
340			}
341			break;
342		case 'p':
343			base_port = atoi(optarg);
344			break;
345		default:
346			usage();
347		}
348	}
349
350	argc -= optind;
351	argv += optind;
352
353	if (argc != 1)
354		usage();
355
356	fd = socket(AF_INET, SOCK_DGRAM, 0);
357	if (fd < 0) {
358		perror("socket");
359		exit(1);
360	}
361	target.sin_family = AF_INET;
362
363	/* Backward compatiblity */
364	if (!base_port) {
365		p = strchr(argv[0], '/');
366		if (p) {
367			*p = 0;
368			base_port = atoi(p+1);
369		} else
370			base_port = 44444;
371	}
372
373	p = argv[0];
374#ifdef USE_IDN
375	rc = idna_to_ascii_lz(argv[0], &p, 0);
376	if (rc != IDNA_SUCCESS) {
377		fprintf(stderr, "IDNA encoding failed: %s\n", idna_strerror(rc));
378		exit(2);
379	}
380#endif
381
382	he = gethostbyname(p);
383	if (he == NULL) {
384		herror("gethostbyname");
385		exit(1);
386	}
387
388#ifdef USE_IDN
389	free(p);
390#endif
391
392	memcpy(&target.sin_addr, he->h_addr, 4);
393
394	on = IP_PMTUDISC_PROBE;
395	if (setsockopt(fd, SOL_IP, IP_MTU_DISCOVER, &on, sizeof(on)) &&
396	    (on = IP_PMTUDISC_DO,
397	     setsockopt(fd, SOL_IP, IP_MTU_DISCOVER, &on, sizeof(on)))) {
398		perror("IP_MTU_DISCOVER");
399		exit(1);
400	}
401	on = 1;
402	if (setsockopt(fd, SOL_IP, IP_RECVERR, &on, sizeof(on))) {
403		perror("IP_RECVERR");
404		exit(1);
405	}
406	if (setsockopt(fd, SOL_IP, IP_RECVTTL, &on, sizeof(on))) {
407		perror("IP_RECVTTL");
408		exit(1);
409	}
410
411	pktbuf = malloc(mtu);
412	if (!pktbuf) {
413		perror("malloc");
414		exit(1);
415	}
416
417	for (ttl = 1; ttl <= max_hops; ttl++) {
418		int res;
419		int i;
420
421		on = ttl;
422		if (setsockopt(fd, SOL_IP, IP_TTL, &on, sizeof(on))) {
423			perror("IP_TTL");
424			exit(1);
425		}
426
427restart:
428		for (i=0; i<3; i++) {
429			int old_mtu;
430
431			old_mtu = mtu;
432			res = probe_ttl(fd, ttl);
433			if (mtu != old_mtu)
434				goto restart;
435			if (res == 0)
436				goto done;
437			if (res > 0)
438				break;
439		}
440
441		if (res < 0)
442			printf("%2d:  no reply\n", ttl);
443	}
444	printf("     Too many hops: pmtu %d\n", mtu);
445done:
446	printf("     Resume: pmtu %d ", mtu);
447	if (hops_to>=0)
448		printf("hops %d ", hops_to);
449	if (hops_from>=0)
450		printf("back %d ", hops_from);
451	printf("\n");
452	exit(0);
453}
454