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