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
usage(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
catch(void * a,char * msg)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
prlost4(ushort seq,void * v)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
prlost6(ushort seq,void * v)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
prreply4(Req * r,void * v)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
prreply6(Req * r,void * v)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 *
geticmp(void * v)139 geticmp(void *v)
140 {
141 char *p = v;
142
143 return (Icmphdr *)(p + proto->iphdrsz);
144 }
145
146 void
clean(ushort seq,vlong now,void * v)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
myipvnaddr(uchar * ip,Proto * proto,char * net)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
sender(int fd,int msglen,int interval,int n)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
rcvr(int fd,int msglen,int interval,int nmsg)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
isdottedquad(char * name)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
isv6lit(char * name)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
_dial_string_parse(char * str,DS * ds)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
isv4name(char * name)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
main(int argc,char ** argv)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
reply(Req * r,void * v)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
lost(Req * r,void * v)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