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