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