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