]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/ping.c
merge
[plan9front.git] / sys / src / cmd / ip / ping.c
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         char err[ERRMAX];
307         uchar buf[64*1024+512];
308         Icmphdr *icmp;
309         Req *r;
310
311         sum = 0;
312         while(lostmsgs+rcvdmsgs < nmsg){
313                 alarm((nmsg-lostmsgs-rcvdmsgs)*interval+waittime);
314                 n = read(fd, buf, sizeof buf);
315                 alarm(0);
316                 if(n == 0)
317                         strcpy(err, "got eof");
318                 else if(n < 0)
319                         rerrstr(err, sizeof(err));
320                 now = nsec();
321                 if(n <= 0){
322                         print("%s\n", err);
323                         clean(0, now+MINUTE, nil);
324                         if(strstr(err, "interrupted") == nil)
325                                 sleep(waittime);
326                         continue;
327                 }
328                 if(n < msglen){
329                         print("bad len %d/%d\n", n, msglen);
330                         continue;
331                 }
332                 icmp = geticmp(buf);
333                 munged = 0;
334                 for(i = proto->iphdrsz + ICMP_HDRSIZE; i < msglen; i++)
335                         if(buf[i] != (uchar)i)
336                                 munged++;
337                 if(munged)
338                         print("corrupted reply\n");
339                 x = nhgets(icmp->seq);
340                 if(icmp->type != proto->echoreply || icmp->code != 0) {
341                         print("bad type/code/sequence %d/%d/%d (want %d/%d/%d)\n",
342                                 icmp->type, icmp->code, x,
343                                 proto->echoreply, 0, x);
344                         continue;
345                 }
346                 clean(x, now, buf);
347         }
348
349         lock(&listlock);
350         for(r = first; r; r = r->next)
351                 if(r->replied == 0)
352                         lostmsgs++;
353         unlock(&listlock);
354
355         if(!quiet && lostmsgs)
356                 print("%d out of %d messages lost\n", lostmsgs,
357                         lostmsgs+rcvdmsgs);
358 }
359
360 static int
361 isdottedquad(char *name)
362 {
363         int dot = 0, digit = 0;
364
365         for (; *name != '\0'; name++)
366                 if (*name == '.')
367                         dot++;
368                 else if (isdigit(*name))
369                         digit++;
370                 else
371                         return 0;
372         return dot && digit;
373 }
374
375 static int
376 isv6lit(char *name)
377 {
378         int colon = 0, hex = 0;
379
380         for (; *name != '\0'; name++)
381                 if (*name == ':')
382                         colon++;
383                 else if (isxdigit(*name))
384                         hex++;
385                 else
386                         return 0;
387         return colon;
388 }
389
390 /* from /sys/src/libc/9sys/dial.c */
391
392 enum
393 {
394         Maxstring       = 128,
395         Maxpath         = 256,
396 };
397
398 typedef struct DS DS;
399 struct DS {
400         /* dist string */
401         char    buf[Maxstring];
402         char    *netdir;
403         char    *proto;
404         char    *rem;
405
406         /* other args */
407         char    *local;
408         char    *dir;
409         int     *cfdp;
410 };
411
412 /*
413  *  parse a dial string
414  */
415 static void
416 _dial_string_parse(char *str, DS *ds)
417 {
418         char *p, *p2;
419
420         strncpy(ds->buf, str, Maxstring);
421         ds->buf[Maxstring-1] = 0;
422
423         p = strchr(ds->buf, '!');
424         if(p == 0) {
425                 ds->netdir = 0;
426                 ds->proto = "net";
427                 ds->rem = ds->buf;
428         } else {
429                 if(*ds->buf != '/' && *ds->buf != '#'){
430                         ds->netdir = 0;
431                         ds->proto = ds->buf;
432                 } else {
433                         for(p2 = p; *p2 != '/'; p2--)
434                                 ;
435                         *p2++ = 0;
436                         ds->netdir = ds->buf;
437                         ds->proto = p2;
438                 }
439                 *p = 0;
440                 ds->rem = p + 1;
441         }
442 }
443
444 /* end excerpt from /sys/src/libc/9sys/dial.c */
445
446 /* side effect: sets network & target */
447 static int
448 isv4name(char *name)
449 {
450         int r = 1;
451         char *root, *ip, *pr;
452         DS ds;
453
454         _dial_string_parse(name, &ds);
455
456         /* cope with leading /net.alt/icmp! and the like */
457         root = nil;
458         if (ds.netdir != nil) {
459                 pr = strrchr(ds.netdir, '/');
460                 if (pr == nil)
461                         pr = ds.netdir;
462                 else {
463                         *pr++ = '\0';
464                         root = ds.netdir;
465                         network = strdup(root);
466                 }
467                 if (strcmp(pr, v4pr.net) == 0)
468                         return 1;
469                 if (strcmp(pr, v6pr.net) == 0)
470                         return 0;
471         }
472
473         /* if it's a literal, it's obvious from syntax which proto it is */
474         free(target);
475         target = strdup(ds.rem);
476         if (isdottedquad(ds.rem))
477                 return 1;
478         else if (isv6lit(ds.rem))
479                 return 0;
480
481         /* map name to ip and look at its syntax */
482         ip = csgetvalue(root, "sys", ds.rem, "ip", nil);
483         if (ip == nil)
484                 ip = csgetvalue(root, "dom", ds.rem, "ip", nil);
485         if (ip == nil)
486                 ip = csgetvalue(root, "sys", ds.rem, "ipv6", nil);
487         if (ip == nil)
488                 ip = csgetvalue(root, "dom", ds.rem, "ipv6", nil);
489         if (ip != nil)
490                 r = isv4name(ip);
491         free(ip);
492         return r;
493 }
494
495 void
496 main(int argc, char **argv)
497 {
498         int fd, msglen, interval, nmsg;
499         char *ds;
500
501         nsec();         /* make sure time file is already open */
502
503         fmtinstall('V', eipfmt);
504         fmtinstall('I', eipfmt);
505
506         msglen = interval = 0;
507         nmsg = MAXMSG;
508         ARGBEGIN {
509         case '6':
510                 proto = &v6pr;
511                 break;
512         case 'a':
513                 addresses = 1;
514                 break;
515         case 'd':
516                 debug++;
517                 break;
518         case 'f':
519                 flood = 1;
520                 break;
521         case 'i':
522                 interval = atoi(EARGF(usage()));
523                 if(interval < 0)
524                         usage();
525                 break;
526         case 'l':
527                 lostonly++;
528                 break;
529         case 'n':
530                 nmsg = atoi(EARGF(usage()));
531                 if(nmsg < 0)
532                         usage();
533                 break;
534         case 'q':
535                 quiet = 1;
536                 break;
537         case 'r':
538                 rint = 1;
539                 break;
540         case 's':
541                 msglen = atoi(EARGF(usage()));
542                 break;
543         case 'w':
544                 waittime = atoi(EARGF(usage()));
545                 if(waittime < 0)
546                         usage();
547                 break;
548         default:
549                 usage();
550                 break;
551         } ARGEND;
552
553         if(msglen < proto->iphdrsz + ICMP_HDRSIZE)
554                 msglen = proto->iphdrsz + ICMP_HDRSIZE;
555         if(msglen < 64)
556                 msglen = 64;
557         if(msglen >= 64*1024)
558                 msglen = 64*1024-1;
559         if(interval <= 0 && !flood)
560                 interval = SLEEPMS;
561
562         if(argc < 1)
563                 usage();
564
565         notify(catch);
566
567         if (!isv4name(argv[0]))
568                 proto = &v6pr;
569         ds = netmkaddr(argv[0], proto->net, "1");
570         fd = dial(ds, 0, 0, 0);
571         if(fd < 0){
572                 fprint(2, "%s: couldn't dial %s: %r\n", argv0, ds);
573                 exits("dialing");
574         }
575
576         if (!quiet)
577                 print("sending %d %d byte messages %d ms apart to %s\n",
578                         nmsg, msglen, interval, ds);
579
580         switch(rfork(RFPROC|RFMEM|RFFDG)){
581         case -1:
582                 fprint(2, "%s: can't fork: %r\n", argv0);
583                 exits("forking");
584         case 0:
585                 rcvr(fd, msglen, interval, nmsg);
586                 exits(0);
587         default:
588                 sender(fd, msglen, interval, nmsg);
589                 wait();
590                 exits(lostmsgs ? "lost messages" : "");
591         }
592 }
593
594 void
595 reply(Req *r, void *v)
596 {
597         r->rtt /= 1000LL;
598         sum += r->rtt;
599         if(!r->replied)
600                 rcvdmsgs++;
601         if(!quiet && !lostonly)
602                 if(addresses)
603                         (*proto->prreply)(r, v);
604                 else
605                         print("%ud: rtt %lld µs, avg rtt %lld µs, ttl = %d\n",
606                                 r->seq - firstseq, r->rtt, sum/rcvdmsgs, r->ttl);
607         r->replied = 1;
608 }
609
610 void
611 lost(Req *r, void *v)
612 {
613         if(!quiet)
614                 if(addresses && v != nil)
615                         (*proto->prlost)(r->seq - firstseq, v);
616                 else
617                         print("lost %ud\n", r->seq - firstseq);
618         lostmsgs++;
619 }