1 /* ping for ip v4 and v6 */ 2 #include <u.h> 3 #include <libc.h> 4 #include <ctype.h> 5 #include <ip.h> 6 #include <bio.h> 7 #include <ndb.h> 8 #include "icmp.h" 9 10 enum { 11 MAXMSG = 32, 12 SLEEPMS = 1000, 13 14 SECOND = 1000000000LL, 15 MINUTE = 60*SECOND, 16 }; 17 18 typedef struct Req Req; 19 struct Req 20 { 21 ushort seq; /* sequence number */ 22 vlong time; /* time sent */ 23 vlong rtt; 24 int ttl; 25 int replied; 26 Req *next; 27 }; 28 29 typedef struct { 30 int version; 31 char *net; 32 int echocmd; 33 int echoreply; 34 unsigned iphdrsz; 35 36 void (*prreply)(Req *r, void *v); 37 void (*prlost)(ushort seq, void *v); 38 } Proto; 39 40 41 Req *first; /* request list */ 42 Req *last; /* ... */ 43 Lock listlock; 44 45 char *argv0; 46 47 int addresses; 48 int debug; 49 int done; 50 int flood; 51 int lostmsgs; 52 int lostonly; 53 int quiet; 54 int rcvdmsgs; 55 int rint; 56 ushort firstseq; 57 vlong sum; 58 int waittime = 5000; 59 60 static char *network, *target; 61 62 void lost(Req*, void*); 63 void reply(Req*, void*); 64 65 static void 66 usage(void) 67 { 68 fprint(2, 69 "usage: %s [-6alq] [-s msgsize] [-i millisecs] [-n #pings] dest\n", 70 argv0); 71 exits("usage"); 72 } 73 74 static void 75 catch(void *a, char *msg) 76 { 77 USED(a); 78 if(strstr(msg, "alarm")) 79 noted(NCONT); 80 else if(strstr(msg, "die")) 81 exits("errors"); 82 else 83 noted(NDFLT); 84 } 85 86 static void 87 prlost4(ushort seq, void *v) 88 { 89 Ip4hdr *ip4 = v; 90 91 print("lost %ud: %V -> %V\n", seq, ip4->src, ip4->dst); 92 } 93 94 static void 95 prlost6(ushort seq, void *v) 96 { 97 Ip6hdr *ip6 = v; 98 99 print("lost %ud: %I -> %I\n", seq, ip6->src, ip6->dst); 100 } 101 102 static void 103 prreply4(Req *r, void *v) 104 { 105 Ip4hdr *ip4 = v; 106 107 print("%ud: %V -> %V rtt %lld µs, avg rtt %lld µs, ttl = %d\n", 108 r->seq - firstseq, ip4->src, ip4->dst, r->rtt, sum/rcvdmsgs, 109 r->ttl); 110 } 111 112 static void 113 prreply6(Req *r, void *v) 114 { 115 Ip6hdr *ip6 = v; 116 117 print("%ud: %I -> %I rtt %lld µs, avg rtt %lld µs, ttl = %d\n", 118 r->seq - firstseq, ip6->src, ip6->dst, r->rtt, sum/rcvdmsgs, 119 r->ttl); 120 } 121 122 static Proto v4pr = { 123 4, "icmp", 124 EchoRequest, EchoReply, 125 IPV4HDR_LEN, 126 prreply4, prlost4, 127 }; 128 static Proto v6pr = { 129 6, "icmpv6", 130 EchoRequestV6, EchoReplyV6, 131 IPV6HDR_LEN, 132 prreply6, prlost6, 133 }; 134 135 static Proto *proto = &v4pr; 136 137 138 Icmphdr * 139 geticmp(void *v) 140 { 141 char *p = v; 142 143 return (Icmphdr *)(p + proto->iphdrsz); 144 } 145 146 void 147 clean(ushort seq, vlong now, void *v) 148 { 149 int ttl; 150 Req **l, *r; 151 152 ttl = 0; 153 if (v) { 154 if (proto->version == 4) 155 ttl = ((Ip4hdr *)v)->ttl; 156 else 157 ttl = ((Ip6hdr *)v)->ttl; 158 } 159 lock(&listlock); 160 last = nil; 161 for(l = &first; *l; ){ 162 r = *l; 163 164 if(v && r->seq == seq){ 165 r->rtt = now-r->time; 166 r->ttl = ttl; 167 reply(r, v); 168 } 169 170 if(now-r->time > MINUTE){ 171 *l = r->next; 172 r->rtt = now-r->time; 173 if(v) 174 r->ttl = ttl; 175 if(r->replied == 0) 176 lost(r, v); 177 free(r); 178 }else{ 179 last = r; 180 l = &r->next; 181 } 182 } 183 unlock(&listlock); 184 } 185 186 static uchar loopbacknet[IPaddrlen] = { 187 0, 0, 0, 0, 188 0, 0, 0, 0, 189 0, 0, 0xff, 0xff, 190 127, 0, 0, 0 191 }; 192 static uchar loopbackmask[IPaddrlen] = { 193 0xff, 0xff, 0xff, 0xff, 194 0xff, 0xff, 0xff, 0xff, 195 0xff, 0xff, 0xff, 0xff, 196 0xff, 0, 0, 0 197 }; 198 199 /* 200 * find first ip addr suitable for proto and 201 * that isn't the friggin loopback address. 202 * deprecate link-local and multicast addresses. 203 */ 204 static int 205 myipvnaddr(uchar *ip, Proto *proto, char *net) 206 { 207 int ipisv4, wantv4; 208 Ipifc *nifc; 209 Iplifc *lifc; 210 uchar mynet[IPaddrlen], linklocal[IPaddrlen]; 211 static Ipifc *ifc; 212 213 ipmove(linklocal, IPnoaddr); 214 wantv4 = proto->version == 4; 215 ifc = readipifc(net, ifc, -1); 216 for(nifc = ifc; nifc; nifc = nifc->next) 217 for(lifc = nifc->lifc; lifc; lifc = lifc->next){ 218 maskip(lifc->ip, loopbackmask, mynet); 219 if(ipcmp(mynet, loopbacknet) == 0) 220 continue; 221 if(ISIPV6MCAST(lifc->ip) || ISIPV6LINKLOCAL(lifc->ip)) { 222 ipmove(linklocal, lifc->ip); 223 continue; 224 } 225 ipisv4 = isv4(lifc->ip) != 0; 226 if(ipcmp(lifc->ip, IPnoaddr) != 0 && wantv4 == ipisv4){ 227 ipmove(ip, lifc->ip); 228 return 0; 229 } 230 } 231 /* no global unicast addrs found, fall back to link-local, if any */ 232 ipmove(ip, linklocal); 233 return ipcmp(ip, IPnoaddr) == 0? -1: 0; 234 } 235 236 void 237 sender(int fd, int msglen, int interval, int n) 238 { 239 int i, extra; 240 ushort seq; 241 char buf[64*1024+512]; 242 uchar me[IPaddrlen], mev4[IPv4addrlen]; 243 Icmphdr *icmp; 244 Req *r; 245 246 srand(time(0)); 247 firstseq = seq = rand(); 248 249 icmp = geticmp(buf); 250 memset(buf, 0, proto->iphdrsz + ICMP_HDRSIZE); 251 for(i = proto->iphdrsz + ICMP_HDRSIZE; i < msglen; i++) 252 buf[i] = i; 253 icmp->type = proto->echocmd; 254 icmp->code = 0; 255 256 /* arguably the kernel should fill in the right src addr. */ 257 myipvnaddr(me, proto, network); 258 if (proto->version == 4) { 259 v6tov4(mev4, me); 260 memmove(((Ip4hdr *)buf)->src, mev4, IPv4addrlen); 261 } else 262 ipmove(((Ip6hdr *)buf)->src, me); 263 if (addresses) 264 print("\t%I -> %s\n", me, target); 265 266 if(rint != 0 && interval <= 0) 267 rint = 0; 268 extra = 0; 269 for(i = 0; i < n; i++){ 270 if(i != 0){ 271 if(rint != 0) 272 extra = nrand(interval); 273 sleep(interval + extra); 274 } 275 r = malloc(sizeof *r); 276 if (r == nil) 277 continue; 278 hnputs(icmp->seq, seq); 279 r->seq = seq; 280 r->next = nil; 281 r->replied = 0; 282 r->time = nsec(); /* avoid early free in reply! */ 283 lock(&listlock); 284 if(first == nil) 285 first = r; 286 else 287 last->next = r; 288 last = r; 289 unlock(&listlock); 290 r->time = nsec(); 291 if(write(fd, buf, msglen) < msglen){ 292 fprint(2, "%s: write failed: %r\n", argv0); 293 return; 294 } 295 seq++; 296 } 297 done = 1; 298 } 299 300 void 301 rcvr(int fd, int msglen, int interval, int nmsg) 302 { 303 int i, n, munged; 304 ushort x; 305 vlong now; 306 uchar buf[64*1024+512]; 307 Icmphdr *icmp; 308 Req *r; 309 310 sum = 0; 311 while(lostmsgs+rcvdmsgs < nmsg){ 312 alarm((nmsg-lostmsgs-rcvdmsgs)*interval+waittime); 313 n = read(fd, buf, sizeof buf); 314 alarm(0); 315 now = nsec(); 316 if(n <= 0){ /* read interrupted - time to go */ 317 clean(0, now+MINUTE, nil); 318 continue; 319 } 320 if(n < msglen){ 321 print("bad len %d/%d\n", n, msglen); 322 continue; 323 } 324 icmp = geticmp(buf); 325 munged = 0; 326 for(i = proto->iphdrsz + ICMP_HDRSIZE; i < msglen; i++) 327 if(buf[i] != (uchar)i) 328 munged++; 329 if(munged) 330 print("corrupted reply\n"); 331 x = nhgets(icmp->seq); 332 if(icmp->type != proto->echoreply || icmp->code != 0) { 333 print("bad type/code/sequence %d/%d/%d (want %d/%d/%d)\n", 334 icmp->type, icmp->code, x, 335 proto->echoreply, 0, x); 336 continue; 337 } 338 clean(x, now, buf); 339 } 340 341 lock(&listlock); 342 for(r = first; r; r = r->next) 343 if(r->replied == 0) 344 lostmsgs++; 345 unlock(&listlock); 346 347 if(!quiet && lostmsgs) 348 print("%d out of %d messages lost\n", lostmsgs, 349 lostmsgs+rcvdmsgs); 350 } 351 352 static int 353 isdottedquad(char *name) 354 { 355 int dot = 0, digit = 0; 356 357 for (; *name != '\0'; name++) 358 if (*name == '.') 359 dot++; 360 else if (isdigit(*name)) 361 digit++; 362 else 363 return 0; 364 return dot && digit; 365 } 366 367 static int 368 isv6lit(char *name) 369 { 370 int colon = 0, hex = 0; 371 372 for (; *name != '\0'; name++) 373 if (*name == ':') 374 colon++; 375 else if (isxdigit(*name)) 376 hex++; 377 else 378 return 0; 379 return colon; 380 } 381 382 /* from /sys/src/libc/9sys/dial.c */ 383 384 enum 385 { 386 Maxstring = 128, 387 Maxpath = 256, 388 }; 389 390 typedef struct DS DS; 391 struct DS { 392 /* dist string */ 393 char buf[Maxstring]; 394 char *netdir; 395 char *proto; 396 char *rem; 397 398 /* other args */ 399 char *local; 400 char *dir; 401 int *cfdp; 402 }; 403 404 /* 405 * parse a dial string 406 */ 407 static void 408 _dial_string_parse(char *str, DS *ds) 409 { 410 char *p, *p2; 411 412 strncpy(ds->buf, str, Maxstring); 413 ds->buf[Maxstring-1] = 0; 414 415 p = strchr(ds->buf, '!'); 416 if(p == 0) { 417 ds->netdir = 0; 418 ds->proto = "net"; 419 ds->rem = ds->buf; 420 } else { 421 if(*ds->buf != '/' && *ds->buf != '#'){ 422 ds->netdir = 0; 423 ds->proto = ds->buf; 424 } else { 425 for(p2 = p; *p2 != '/'; p2--) 426 ; 427 *p2++ = 0; 428 ds->netdir = ds->buf; 429 ds->proto = p2; 430 } 431 *p = 0; 432 ds->rem = p + 1; 433 } 434 } 435 436 /* end excerpt from /sys/src/libc/9sys/dial.c */ 437 438 /* side effect: sets network & target */ 439 static int 440 isv4name(char *name) 441 { 442 int r = 1; 443 char *root, *ip, *pr; 444 DS ds; 445 446 _dial_string_parse(name, &ds); 447 448 /* cope with leading /net.alt/icmp! and the like */ 449 root = nil; 450 if (ds.netdir != nil) { 451 pr = strrchr(ds.netdir, '/'); 452 if (pr == nil) 453 pr = ds.netdir; 454 else { 455 *pr++ = '\0'; 456 root = ds.netdir; 457 network = strdup(root); 458 } 459 if (strcmp(pr, v4pr.net) == 0) 460 return 1; 461 if (strcmp(pr, v6pr.net) == 0) 462 return 0; 463 } 464 465 /* if it's a literal, it's obvious from syntax which proto it is */ 466 free(target); 467 target = strdup(ds.rem); 468 if (isdottedquad(ds.rem)) 469 return 1; 470 else if (isv6lit(ds.rem)) 471 return 0; 472 473 /* map name to ip and look at its syntax */ 474 ip = csgetvalue(root, "sys", ds.rem, "ip", nil); 475 if (ip == nil) 476 ip = csgetvalue(root, "dom", ds.rem, "ip", nil); 477 if (ip == nil) 478 ip = csgetvalue(root, "sys", ds.rem, "ipv6", nil); 479 if (ip == nil) 480 ip = csgetvalue(root, "dom", ds.rem, "ipv6", nil); 481 if (ip != nil) 482 r = isv4name(ip); 483 free(ip); 484 return r; 485 } 486 487 void 488 main(int argc, char **argv) 489 { 490 int fd, msglen, interval, nmsg; 491 char *ds; 492 493 nsec(); /* make sure time file is already open */ 494 495 fmtinstall('V', eipfmt); 496 fmtinstall('I', eipfmt); 497 498 msglen = interval = 0; 499 nmsg = MAXMSG; 500 ARGBEGIN { 501 case '6': 502 proto = &v6pr; 503 break; 504 case 'a': 505 addresses = 1; 506 break; 507 case 'd': 508 debug++; 509 break; 510 case 'f': 511 flood = 1; 512 break; 513 case 'i': 514 interval = atoi(EARGF(usage())); 515 if(interval < 0) 516 usage(); 517 break; 518 case 'l': 519 lostonly++; 520 break; 521 case 'n': 522 nmsg = atoi(EARGF(usage())); 523 if(nmsg < 0) 524 usage(); 525 break; 526 case 'q': 527 quiet = 1; 528 break; 529 case 'r': 530 rint = 1; 531 break; 532 case 's': 533 msglen = atoi(EARGF(usage())); 534 break; 535 case 'w': 536 waittime = atoi(EARGF(usage())); 537 if(waittime < 0) 538 usage(); 539 break; 540 default: 541 usage(); 542 break; 543 } ARGEND; 544 545 if(msglen < proto->iphdrsz + ICMP_HDRSIZE) 546 msglen = proto->iphdrsz + ICMP_HDRSIZE; 547 if(msglen < 64) 548 msglen = 64; 549 if(msglen >= 64*1024) 550 msglen = 64*1024-1; 551 if(interval <= 0 && !flood) 552 interval = SLEEPMS; 553 554 if(argc < 1) 555 usage(); 556 557 notify(catch); 558 559 if (!isv4name(argv[0])) 560 proto = &v6pr; 561 ds = netmkaddr(argv[0], proto->net, "1"); 562 fd = dial(ds, 0, 0, 0); 563 if(fd < 0){ 564 fprint(2, "%s: couldn't dial %s: %r\n", argv0, ds); 565 exits("dialing"); 566 } 567 568 if (!quiet) 569 print("sending %d %d byte messages %d ms apart to %s\n", 570 nmsg, msglen, interval, ds); 571 572 switch(rfork(RFPROC|RFMEM|RFFDG)){ 573 case -1: 574 fprint(2, "%s: can't fork: %r\n", argv0); 575 /* fallthrough */ 576 case 0: 577 rcvr(fd, msglen, interval, nmsg); 578 exits(0); 579 default: 580 sender(fd, msglen, interval, nmsg); 581 wait(); 582 exits(lostmsgs ? "lost messages" : ""); 583 } 584 } 585 586 void 587 reply(Req *r, void *v) 588 { 589 r->rtt /= 1000LL; 590 sum += r->rtt; 591 if(!r->replied) 592 rcvdmsgs++; 593 if(!quiet && !lostonly) 594 if(addresses) 595 (*proto->prreply)(r, v); 596 else 597 print("%ud: rtt %lld µs, avg rtt %lld µs, ttl = %d\n", 598 r->seq - firstseq, r->rtt, sum/rcvdmsgs, r->ttl); 599 r->replied = 1; 600 } 601 602 void 603 lost(Req *r, void *v) 604 { 605 if(!quiet) 606 if(addresses && v != nil) 607 (*proto->prlost)(r->seq - firstseq, v); 608 else 609 print("lost %ud\n", r->seq - firstseq); 610 lostmsgs++; 611 } 612