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