1 #include <u.h> 2 #include <libc.h> 3 #include <ctype.h> 4 #include <bio.h> 5 #include <ndb.h> 6 #include <ip.h> 7 #include "icmp.h" 8 9 enum{ 10 Maxstring= 128, 11 Maxpath= 256, 12 }; 13 14 typedef struct DS DS; 15 struct DS { 16 /* dial string */ 17 char buf[Maxstring]; 18 char *netdir; 19 char *proto; 20 char *rem; 21 }; 22 23 char *argv0; 24 int debug; 25 26 void histogram(long *t, int n, int buckets, long lo, long hi); 27 28 void 29 usage(void) 30 { 31 fprint(2, 32 "usage: %s [-n][-a tries][-h buckets][-t ttl][-x net] [protocol!]destination\n", 33 argv0); 34 exits("usage"); 35 } 36 37 static int 38 csquery(DS *ds, char *clone, char *dest) 39 { 40 int n, fd; 41 char *p, buf[Maxstring]; 42 43 /* 44 * open connection server 45 */ 46 snprint(buf, sizeof(buf), "%s/cs", ds->netdir); 47 fd = open(buf, ORDWR); 48 if(fd < 0){ 49 if(!isdigit(*dest)){ 50 werrstr("can't translate"); 51 return -1; 52 } 53 54 /* no connection server, don't translate */ 55 snprint(clone, sizeof(clone), "%s/%s/clone", ds->netdir, ds->proto); 56 strcpy(dest, ds->rem); 57 return 0; 58 } 59 60 /* 61 * ask connection server to translate 62 */ 63 sprint(buf, "%s!%s", ds->proto, ds->rem); 64 if(write(fd, buf, strlen(buf)) < 0){ 65 close(fd); 66 return -1; 67 } 68 69 /* 70 * get an address. 71 */ 72 seek(fd, 0, 0); 73 n = read(fd, buf, sizeof(buf) - 1); 74 close(fd); 75 if(n <= 0){ 76 werrstr("problem with cs"); 77 return -1; 78 } 79 80 buf[n] = 0; 81 p = strchr(buf, ' '); 82 if(p == 0){ 83 werrstr("problem with cs"); 84 return -1; 85 } 86 87 *p++ = 0; 88 strcpy(clone, buf); 89 strcpy(dest, p); 90 return 0; 91 } 92 93 /* 94 * call the dns process and have it try to resolve the mx request 95 */ 96 static int 97 dodnsquery(DS *ds, char *ip, char *dom) 98 { 99 char *p; 100 Ndbtuple *t, *nt; 101 102 p = strchr(ip, '!'); 103 if(p) 104 *p = 0; 105 106 t = dnsquery(ds->netdir, ip, "ptr"); 107 for(nt = t; nt != nil; nt = nt->entry) 108 if(strcmp(nt->attr, "dom") == 0){ 109 strcpy(dom, nt->val); 110 ndbfree(t); 111 return 0; 112 } 113 ndbfree(t); 114 return -1; 115 } 116 117 /* for connection oriented protocols (il, tcp) we just need 118 * to try dialing. resending is up to it. 119 */ 120 static int 121 tcpilprobe(int cfd, int dfd, char *dest, int interval) 122 { 123 int n; 124 char msg[Maxstring]; 125 126 USED(dfd); 127 128 n = snprint(msg, sizeof msg, "connect %s", dest); 129 alarm(interval); 130 n = write(cfd, msg, n); 131 alarm(0); 132 return n; 133 } 134 135 /* 136 * for udp, we keep sending to an improbable port 137 * till we timeout or someone complains 138 */ 139 static int 140 udpprobe(int cfd, int dfd, char *dest, int interval) 141 { 142 int n, i, rv; 143 char msg[Maxstring]; 144 char err[Maxstring]; 145 146 seek(cfd, 0, 0); 147 n = snprint(msg, sizeof msg, "connect %s", dest); 148 if(write(cfd, msg, n)< 0) 149 return -1; 150 151 rv = -1; 152 for(i = 0; i < 3; i++){ 153 alarm(interval/3); 154 if(write(dfd, "boo hoo ", 8) < 0) 155 break; 156 /* 157 * a hangup due to an error looks like 3 eofs followed 158 * by a real error. this is a qio.c qbread() strangeness 159 * done for pipes. 160 */ 161 do { 162 n = read(dfd, msg, sizeof(msg)-1); 163 } while(n == 0); 164 alarm(0); 165 if(n > 0){ 166 rv = 0; 167 break; 168 } 169 errstr(err, sizeof err); 170 if(strstr(err, "alarm") == 0){ 171 werrstr(err); 172 break; 173 } 174 werrstr(err); 175 } 176 alarm(0); 177 return rv; 178 } 179 180 #define MSG "traceroute probe" 181 #define MAGIC 0xdead 182 183 /* ICMPv4 only */ 184 static int 185 icmpprobe(int cfd, int dfd, char *dest, int interval) 186 { 187 int x, i, n, len, rv; 188 char buf[512]; 189 Icmp *ip; 190 char msg[Maxstring]; 191 char err[Maxstring]; 192 193 seek(cfd, 0, 0); 194 n = snprint(msg, sizeof msg, "connect %s", dest); 195 if(write(cfd, msg, n)< 0) 196 return -1; 197 198 rv = -1; 199 ip = (Icmp*)buf; 200 for(i = 0; i < 3; i++){ 201 alarm(interval/3); 202 ip->type = EchoRequest; 203 ip->code = 0; 204 strcpy((char*)ip->data, MSG); 205 ip->seq[0] = MAGIC; 206 ip->seq[1] = MAGIC>>8; 207 len = IPV4HDR_LEN+ICMP_HDRSIZE+sizeof(MSG); 208 209 /* send a request */ 210 if(write(dfd, buf, len) < len) 211 break; 212 213 /* wait for reply */ 214 n = read(dfd, buf, sizeof(buf)); 215 alarm(0); 216 if(n < 0){ 217 errstr(err, sizeof err); 218 if(strstr(err, "alarm") == 0){ 219 werrstr(err); 220 break; 221 } 222 werrstr(err); 223 continue; 224 } 225 x = (ip->seq[1]<<8)|ip->seq[0]; 226 if(n >= len) 227 if(ip->type == EchoReply) 228 if(x == MAGIC) 229 if(strcmp((char*)ip->data, MSG) == 0){ 230 rv = 0; 231 break; 232 } 233 } 234 alarm(0); 235 return rv; 236 } 237 238 static void 239 catch(void *a, char *msg) 240 { 241 USED(a); 242 if(strstr(msg, "alarm")) 243 noted(NCONT); 244 else 245 noted(NDFLT); 246 } 247 248 static int 249 call(DS *ds, char *clone, char *dest, int ttl, long *interval) 250 { 251 int cfd, dfd, rv, n; 252 char msg[Maxstring]; 253 char file[Maxstring]; 254 vlong start; 255 256 notify(catch); 257 258 /* start timing */ 259 start = nsec()/1000; 260 rv = -1; 261 262 cfd = open(clone, ORDWR); 263 if(cfd < 0){ 264 werrstr("%s: %r", clone); 265 return -1; 266 } 267 dfd = -1; 268 269 /* get conversation number */ 270 n = read(cfd, msg, sizeof(msg)-1); 271 if(n <= 0) 272 goto out; 273 msg[n] = 0; 274 275 /* open data file */ 276 sprint(file, "%s/%s/%s/data", ds->netdir, ds->proto, msg); 277 dfd = open(file, ORDWR); 278 if(dfd < 0) 279 goto out; 280 281 /* set ttl */ 282 if(ttl) 283 fprint(cfd, "ttl %d", ttl); 284 285 /* probe */ 286 if(strcmp(ds->proto, "udp") == 0) 287 rv = udpprobe(cfd, dfd, dest, 3000); 288 else if(strcmp(ds->proto, "icmp") == 0) 289 rv = icmpprobe(cfd, dfd, dest, 3000); 290 else /* il and tcp */ 291 rv = tcpilprobe(cfd, dfd, dest, 3000); 292 out: 293 /* turn off alarms */ 294 alarm(0); 295 *interval = nsec()/1000 - start; 296 close(cfd); 297 close(dfd); 298 return rv; 299 } 300 301 /* 302 * parse a dial string. default netdir is /net. 303 * default proto is tcp. 304 */ 305 static void 306 dial_string_parse(char *str, DS *ds) 307 { 308 char *p, *p2; 309 310 strncpy(ds->buf, str, Maxstring); 311 ds->buf[Maxstring-3] = 0; 312 313 p = strchr(ds->buf, '!'); 314 if(p == 0) { 315 ds->netdir = 0; 316 ds->proto = "tcp"; 317 ds->rem = ds->buf; 318 } else { 319 if(*ds->buf != '/'){ 320 ds->netdir = 0; 321 ds->proto = ds->buf; 322 } else { 323 for(p2 = p; *p2 != '/'; p2--) 324 ; 325 *p2++ = 0; 326 ds->netdir = ds->buf; 327 ds->proto = p2; 328 } 329 *p = 0; 330 ds->rem = p + 1; 331 } 332 if(strchr(ds->rem, '!') == 0) 333 strcat(ds->rem, "!32767"); 334 } 335 336 void 337 main(int argc, char **argv) 338 { 339 int buckets, ttl, j, done, tries, notranslate; 340 long lo, hi, sum, x; 341 long *t; 342 char *net, *p; 343 char clone[Maxpath], dest[Maxstring], hop[Maxstring], dom[Maxstring]; 344 char err[Maxstring]; 345 DS ds; 346 347 buckets = 0; 348 tries = 3; 349 notranslate = 0; 350 net = "/net"; 351 ttl = 1; 352 ARGBEGIN{ 353 case 'a': 354 tries = atoi(EARGF(usage())); 355 break; 356 case 'd': 357 debug++; 358 break; 359 case 'h': 360 buckets = atoi(EARGF(usage())); 361 break; 362 case 'n': 363 notranslate++; 364 break; 365 case 't': 366 ttl = atoi(EARGF(usage())); 367 break; 368 case 'x': 369 net = EARGF(usage()); 370 break; 371 default: 372 usage(); 373 }ARGEND; 374 375 if(argc < 1) 376 usage(); 377 378 t = malloc(tries*sizeof(ulong)); 379 380 dial_string_parse(argv[0], &ds); 381 382 if(ds.netdir == 0) 383 ds.netdir = net; 384 if(csquery(&ds, clone, dest) < 0){ 385 fprint(2, "%s: %s: %r\n", argv0, argv[0]); 386 exits(0); 387 } 388 print("trying %s/%s!%s\n\n", ds.netdir, ds.proto, dest); 389 print(" round trip times in µs\n"); 390 print(" low avg high\n"); 391 print(" --------------------------\n"); 392 393 done = 0; 394 for(; ttl < 32; ttl++){ 395 for(j = 0; j < tries; j++){ 396 if(call(&ds, clone, dest, ttl, &t[j]) >= 0){ 397 if(debug) 398 print("%ld %s\n", t[j], dest); 399 strcpy(hop, dest); 400 done = 1; 401 continue; 402 } 403 errstr(err, sizeof err); 404 if(strstr(err, "refused")){ 405 strcpy(hop, dest); 406 p = strchr(hop, '!'); 407 if(p) 408 *p = 0; 409 done = 1; 410 } else if(strstr(err, "unreachable")){ 411 snprint(hop, sizeof(hop), "%s", err); 412 p = strchr(hop, '!'); 413 if(p) 414 *p = 0; 415 done = 1; 416 } else if(strncmp(err, "ttl exceeded at ", 16) == 0) 417 strcpy(hop, err+16); 418 else { 419 strcpy(hop, "*"); 420 break; 421 } 422 if(debug) 423 print("%ld %s\n", t[j], hop); 424 } 425 if(strcmp(hop, "*") == 0){ 426 print("*\n"); 427 continue; 428 } 429 lo = 10000000; 430 hi = 0; 431 sum = 0; 432 for(j = 0; j < tries; j++){ 433 x = t[j]; 434 sum += x; 435 if(x < lo) 436 lo = x; 437 if(x > hi) 438 hi = x; 439 } 440 if(notranslate == 1 || dodnsquery(&ds, hop, dom) < 0) 441 dom[0] = 0; 442 /* don't truncate: ipv6 addresses can be quite long */ 443 print("%-18s %8ld %8ld %8ld %s\n", hop, lo, sum/tries, hi, dom); 444 if(buckets) 445 histogram(t, tries, buckets, lo, hi); 446 if(done) 447 break; 448 } 449 exits(0); 450 } 451 452 char *order = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 453 454 void 455 histogram(long *t, int n, int buckets, long lo, long hi) 456 { 457 int i, j, empty; 458 long span; 459 static char *bar; 460 char *p; 461 char x[64]; 462 463 if(bar == nil) 464 bar = malloc(n+1); 465 466 print("+++++++++++++++++++++++\n"); 467 span = (hi-lo)/buckets; 468 span++; 469 empty = 0; 470 for(i = 0; i < buckets; i++){ 471 p = bar; 472 for(j = 0; j < n; j++) 473 if(t[j] >= lo+i*span && t[j] <= lo+(i+1)*span) 474 *p++ = order[j]; 475 *p = 0; 476 if(p != bar){ 477 snprint(x, sizeof x, "[%ld-%ld]", lo+i*span, lo+(i+1)*span); 478 print("%-16s %s\n", x, bar); 479 empty = 0; 480 } else if(!empty){ 481 print("...\n"); 482 empty = 1; 483 } 484 } 485 print("+++++++++++++++++++++++\n"); 486 } 487