Home | History | Annotate | Download | only in iputils
      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 (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 <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 
     38 struct hhistory
     39 {
     40 	int	hops;
     41 	struct timeval sendtime;
     42 };
     43 
     44 struct hhistory his[64];
     45 int hisptr;
     46 
     47 struct sockaddr_in target;
     48 __u16 base_port;
     49 int max_hops = MAX_HOPS_DEFAULT;
     50 
     51 const int overhead = 28;
     52 int mtu = 65535;
     53 void *pktbuf;
     54 int hops_to = -1;
     55 int hops_from = -1;
     56 int no_resolve = 0;
     57 int show_both = 0;
     58 
     59 #define HOST_COLUMN_SIZE	52
     60 
     61 struct probehdr
     62 {
     63 	__u32 ttl;
     64 	struct timeval tv;
     65 };
     66 
     67 void 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 
     78 void 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 
     89 int 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 
    107 restart:
    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 
    258 int probe_ttl(int fd, int ttl)
    259 {
    260 	int i;
    261 	struct probehdr *hdr = pktbuf;
    262 
    263 	memset(pktbuf, 0, mtu);
    264 restart:
    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 
    297 static void usage(void) __attribute((noreturn));
    298 
    299 static void usage(void)
    300 {
    301 	fprintf(stderr, "Usage: tracepath [-n] [-b] [-l <len>] [-p port] <destination>\n");
    302 	exit(-1);
    303 }
    304 
    305 int
    306 main(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 
    427 restart:
    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);
    445 done:
    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