ping.c
[akaros.git] / tests / ping.c
1 /* This file is part of the UCB release of Plan 9. It is subject to the license
2  * terms in the LICENSE file found in the top-level directory of this
3  * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
4  * part of the UCB release of Plan 9, including this file, may be copied,
5  * modified, propagated, or distributed except according to the terms contained
6  * in the LICENSE file. */
7
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <parlib.h>
11 #include <unistd.h>
12 #include <signal.h>
13 #include <nixip.h>
14 #include <icmp.h>
15 #include <ctype.h>
16 #include <pthread.h>
17 #include <spinlock.h>
18 #include <timing.h>
19 #include <tsc-compat.h>
20 #include <printf-ext.h>
21 #include <alarm.h>
22
23 #define NR_MSG                          4
24 #define SLEEPMS                         1000
25 #define SECONDTSC                       (get_tsc_freq())
26 #define MINUTETSC                       (60 * SECONDTSC)
27 #define BUFSIZE                         (64 * 1024 + 512)
28
29 typedef struct Req Req;
30 struct Req
31 {
32         uint16_t        seq;    /* sequence number */
33         uint64_t        tsctime;        /* time sent */
34         int64_t rtt;
35         int     ttl;
36         int     replied;
37         Req      *next;
38 };
39
40 struct proto {
41         int     version;
42         char    *net;
43         int     echocmd;
44         int     echoreply;
45         unsigned iphdrsz;
46
47         void    (*prreply)(Req *r, void *v);
48         void    (*prlost)(uint16_t seq, void *v);
49 };
50
51
52 Req     *first;         /* request list */
53 Req     *last;          /* ... */
54 struct spin_pdr_lock listlock = SPINPDR_INITIALIZER;
55
56 char *argv0;
57
58 int addresses;
59 int debug;
60 int done;
61 int flood;
62 int lostmsgs;
63 int lostonly;
64 int quiet;
65 int rcvdmsgs;
66 int pingrint;
67 uint16_t firstseq;
68 int64_t sum;
69 int waittime = 5000;
70
71 static char *network, *target;
72
73 void lost(Req*, void*);
74 void reply(Req*, void*);
75
76 static void
77 usage(void)
78 {
79         fprintf(stderr,
80             "usage: %s [-6alq] [-s msgsize] [-i millisecs] [-n #pings] dest\n",
81                 argv0);
82         exit(1);
83 }
84
85 static void
86 prlost4(uint16_t seq, void *v)
87 {
88         struct ip4hdr *ip4 = v;
89
90         printf("lost %u: %i -> %i\n", seq, ip4->src, ip4->dst);
91 }
92
93 static void
94 prlost6(uint16_t seq, void *v)
95 {
96         struct ip6hdr *ip6 = v;
97
98         printf("lost %u: %i -> %i\n", seq, ip6->src, ip6->dst);
99 }
100
101 static void
102 prreply4(Req *r, void *v)
103 {
104         struct ip4hdr *ip4 = v;
105
106         printf("%u: %i -> %i 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         struct ip6hdr *ip6 = v;
115
116         printf("%u: %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 struct proto v4pr = {
122         4,              "icmp",
123         EchoRequest,    EchoReply,
124         IPV4HDR_LEN,
125         prreply4,       prlost4,
126 };
127 static struct proto v6pr = {
128         6,              "icmpv6",
129         EchoRequestV6,  EchoReplyV6,
130         IPV6HDR_LEN,
131         prreply6,       prlost6,
132 };
133
134 static struct proto *proto = &v4pr;
135
136
137 struct icmphdr *
138 geticmp(void *v)
139 {
140         char *p = v;
141
142         return (struct icmphdr *)(p + proto->iphdrsz);
143 }
144
145 void
146 clean(uint16_t seq, int64_t now, void *v)
147 {
148         int ttl;
149         Req **l, *r;
150
151         ttl = 0;
152         if (v) {
153                 if (proto->version == 4)
154                         ttl = ((struct ip4hdr *)v)->ttl;
155                 else
156                         ttl = ((struct ip6hdr *)v)->ttl;
157         }
158         spin_pdr_lock(&listlock);
159         last = NULL;
160         for(l = &first; *l; ){
161                 r = *l;
162                 if(v && r->seq == seq){
163                         r->rtt = ndiff(r->tsctime, now);
164                         r->ttl = ttl;
165                         reply(r, v);
166                 }
167                 if (now - r->tsctime > MINUTETSC) {
168                         *l = r->next;
169                         r->rtt = ndiff(r->tsctime, now);
170                         if(v)
171                                 r->ttl = ttl;
172                         if(r->replied == 0)
173                                 lost(r, v);
174                         free(r);
175                 }else{
176                         last = r;
177                         l = &r->next;
178                 }
179         }
180         spin_pdr_unlock(&listlock);
181 }
182
183 static uint8_t loopbacknet[IPaddrlen] = {
184         0, 0, 0, 0,
185         0, 0, 0, 0,
186         0, 0, 0xff, 0xff,
187         127, 0, 0, 0
188 };
189 static uint8_t loopbackmask[IPaddrlen] = {
190         0xff, 0xff, 0xff, 0xff,
191         0xff, 0xff, 0xff, 0xff,
192         0xff, 0xff, 0xff, 0xff,
193         0xff, 0, 0, 0
194 };
195
196 /*
197  * find first ip addr suitable for proto and
198  * that isn't the friggin loopback address.
199  * deprecate link-local and multicast addresses.
200  */
201 static int
202 myipvnaddr(uint8_t *ip, struct proto *proto, char *net)
203 {
204         int ipisv4, wantv4;
205         struct ipifc *nifc;
206         struct iplifc *lifc;
207         uint8_t mynet[IPaddrlen], linklocal[IPaddrlen];
208         static struct ipifc *ifc;
209
210         ipmove(linklocal, IPnoaddr);
211         wantv4 = proto->version == 4;
212         ifc = readipifc(net, ifc, -1);
213         for(nifc = ifc; nifc; nifc = nifc->next)
214                 for(lifc = nifc->lifc; lifc; lifc = lifc->next){
215                         maskip(lifc->ip, loopbackmask, mynet);
216                         if(ipcmp(mynet, loopbacknet) == 0)
217                                 continue;
218                         if(ISIPV6MCAST(lifc->ip) || ISIPV6LINKLOCAL(lifc->ip)) {
219                                 ipmove(linklocal, lifc->ip);
220                                 continue;
221                         }
222                         ipisv4 = isv4(lifc->ip) != 0;
223                         if(ipcmp(lifc->ip, IPnoaddr) != 0 && wantv4 == ipisv4){
224                                 ipmove(ip, lifc->ip);
225                                 return 0;
226                         }
227                 }
228         /* no global unicast addrs found, fall back to link-local, if any */
229         ipmove(ip, linklocal);
230         return ipcmp(ip, IPnoaddr) == 0? -1: 0;
231 }
232
233 void
234 sender(int fd, int msglen, int interval, int n)
235 {
236         int i, extra;
237         uint16_t seq;
238         char *buf = malloc(BUFSIZE);
239         uint8_t me[IPaddrlen], mev4[IPv4addrlen];
240         struct icmphdr *icmp;
241         Req *r;
242
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(((struct ip4hdr *)buf)->src, mev4, IPv4addrlen);
257         } else
258                 ipmove(((struct ip6hdr *)buf)->src, me);
259         if (addresses)
260                 printf("\t%i -> %s\n", me, target);
261
262         if(pingrint != 0 && interval <= 0)
263                 pingrint = 0;
264         extra = 0;
265         for(i = 0; i < n; i++){
266                 if(i != 0){
267                         if(pingrint != 0)
268                                 extra = rand();
269                         /* uth_sleep takes seconds, interval is in ms */
270                         uthread_sleep((interval + extra) / 1000);
271                 }
272                 r = calloc(sizeof *r, 1);
273                 if (r == NULL){
274                         printf("out of memory? \n");
275                         break;
276                 }
277                 hnputs(icmp->seq, seq);
278                 r->seq = seq;
279                 r->next = NULL;
280                 r->replied = 0;
281                 r->tsctime = read_tsc();        /* avoid early free in reply! */
282                 spin_pdr_lock(&listlock);
283                 if(first == NULL)
284                         first = r;
285                 else
286                         last->next = r;
287                 last = r;
288                 spin_pdr_unlock(&listlock);
289                 r->tsctime = read_tsc();
290                 if(write(fd, buf, msglen) < msglen){
291                         fprintf(stderr, "%s: write failed: %r\n", argv0);
292                         return;
293                 }
294                 seq++;
295         }
296         done = 1;
297 }
298
299 void
300 rcvr(int fd, int msglen, int interval, int nmsg)
301 {
302         int i, n, munged;
303         uint16_t x;
304         int64_t now;
305         uint8_t *buf = malloc(BUFSIZE);
306         struct icmphdr *icmp;
307         Req *r;
308         struct alarm_waiter waiter;
309
310         init_awaiter(&waiter, alarm_abort_sysc);
311         waiter.data = current_uthread;
312
313         sum = 0;
314         while(lostmsgs+rcvdmsgs < nmsg){
315                 /* arm to wake ourselves if the read doesn't connect in time */
316                 set_awaiter_rel(&waiter, 1000 *
317                                 ((nmsg - lostmsgs - rcvdmsgs) * interval + waittime));
318                 set_alarm(&waiter);
319                 n = read(fd, buf, BUFSIZE);
320                 /* cancel immediately, so future syscalls don't get aborted */
321                 unset_alarm(&waiter);
322
323                 now = read_tsc();
324                 if(n <= 0){     /* read interrupted - time to go */
325                         /* Faking time being a minute in the future, so clean marks our
326                          * message as lost.  Note this will also end up cancelling any other
327                          * pending replies that would have expired by then.  Whatever. */
328                         clean(0, now + MINUTETSC, NULL);
329                         continue;
330                 }
331                 if(n < msglen){
332                         printf("bad len %d/%d\n", n, msglen);
333                         continue;
334                 }
335                 icmp = geticmp(buf);
336                 munged = 0;
337                 for(i = proto->iphdrsz + ICMP_HDRSIZE; i < msglen; i++)
338                         if(buf[i] != (uint8_t)i)
339                                 munged++;
340                 if(munged)
341                         printf("corrupted reply\n");
342                 x = nhgets(icmp->seq);
343                 if(icmp->type != proto->echoreply || icmp->code != 0) {
344                         printf("bad type/code/sequence %d/%d/%d (want %d/%d/%d)\n",
345                                 icmp->type, icmp->code, x,
346                                 proto->echoreply, 0, x);
347                         continue;
348                 }
349                 clean(x, now, buf);
350         }
351
352         spin_pdr_lock(&listlock);
353         for(r = first; r; r = r->next)
354                 if(r->replied == 0)
355                         lostmsgs++;
356         spin_pdr_unlock(&listlock);
357
358         if(!quiet && lostmsgs)
359                 printf("%d out of %d messages lost\n", lostmsgs,
360                         lostmsgs+rcvdmsgs);
361 }
362
363 static int
364 isdottedquad(char *name)
365 {
366         int dot = 0, digit = 0;
367
368         for (; *name != '\0'; name++)
369                 if (*name == '.')
370                         dot++;
371                 else if (isdigit(*name))
372                         digit++;
373                 else
374                         return 0;
375         return dot && digit;
376 }
377
378 static int
379 isv6lit(char *name)
380 {
381         int colon = 0, hex = 0;
382
383         for (; *name != '\0'; name++)
384                 if (*name == ':')
385                         colon++;
386                 else if (isxdigit(*name))
387                         hex++;
388                 else
389                         return 0;
390         return colon;
391 }
392
393 /* from /sys/src/libc/9sys/dial.c */
394
395 enum
396 {
397         Maxstring       = 128,
398         Maxpath         = 256,
399 };
400
401 typedef struct DS DS;
402 struct DS {
403         /* dist string */
404         char    buf[Maxstring];
405         char    *netdir;
406         char    *proto;
407         char    *rem;
408
409         /* other args */
410         char    *local;
411         char    *dir;
412         int     *cfdp;
413 };
414
415 /*
416  *  parse a dial string
417  */
418 static void
419 _dial_string_parse(char *str, DS *ds)
420 {
421         char *p, *p2;
422
423         strncpy(ds->buf, str, Maxstring);
424         ds->buf[Maxstring-1] = 0;
425
426         p = strchr(ds->buf, '!');
427         if(p == 0) {
428                 ds->netdir = 0;
429                 ds->proto = "net";
430                 ds->rem = ds->buf;
431         } else {
432                 if(*ds->buf != '/' && *ds->buf != '#'){
433                         ds->netdir = 0;
434                         ds->proto = ds->buf;
435                 } else {
436                         for(p2 = p; *p2 != '/'; p2--)
437                                 ;
438                         *p2++ = 0;
439                         ds->netdir = ds->buf;
440                         ds->proto = p2;
441                 }
442                 *p = 0;
443                 ds->rem = p + 1;
444         }
445 }
446
447 /* end excerpt from /sys/src/libc/9sys/dial.c */
448
449 /* side effect: sets network & target */
450 static int
451 isv4name(char *name)
452 {
453         int r = 1;
454         char *root, *ip, *pr;
455         DS ds;
456
457         _dial_string_parse(name, &ds);
458
459         /* cope with leading /net.alt/icmp! and the like */
460         root = NULL;
461         if (ds.netdir != NULL) {
462                 pr = strrchr(ds.netdir, '/');
463                 if (pr == NULL)
464                         pr = ds.netdir;
465                 else {
466                         *pr++ = '\0';
467                         root = ds.netdir;
468                         network = strdup(root);
469                 }
470                 if (strcmp(pr, v4pr.net) == 0)
471                         return 1;
472                 if (strcmp(pr, v6pr.net) == 0)
473                         return 0;
474         }
475
476         /* if it's a literal, it's obvious from syntax which proto it is */
477         free(target);
478         target = strdup(ds.rem);
479         if (isdottedquad(ds.rem))
480                 return 1;
481         else if (isv6lit(ds.rem))
482                 return 0;
483 #warning "Fix me when we get /net/cs"
484 #if 0
485         /*we don't have cs.*/
486         /* map name to ip and look at its syntax */
487         ip = csgetvalue(root, "sys", ds.rem, "ip", NULL);
488         if (ip == NULL)
489                 ip = csgetvalue(root, "dom", ds.rem, "ip", NULL);
490         if (ip == NULL)
491                 ip = csgetvalue(root, "sys", ds.rem, "ipv6", NULL);
492         if (ip == NULL)
493                 ip = csgetvalue(root, "dom", ds.rem, "ipv6", NULL);
494         if (ip != NULL)
495                 r = isv4name(ip);
496 #endif
497         free(ip);
498         return r;
499 }
500
501 /* Feel free to put these in a struct or something */
502 int fd, msglen, interval, nmsg;
503
504 void *rcvr_thread(void* arg)
505 {       
506         rcvr(fd, msglen, interval, nmsg);
507         printd(lostmsgs ? "lost messages" : "");
508         return 0;
509 }
510
511 int main(int argc, char **argv)
512 {
513         char *ds;
514         int pid;
515         pthread_t rcvr;
516
517         register_printf_specifier('i', printf_ipaddr, printf_ipaddr_info);
518
519         msglen = interval = 0;
520         nmsg = NR_MSG;
521
522         argv0 = argv[0];
523         if (argc <= 1)
524                 usage();
525         argc--, argv++;
526         while (**argv == '-'){
527                 switch(argv[0][1]){
528                 case '6':
529                         proto = &v6pr;
530                         break;
531                 case 'a':
532                         addresses = 1;
533                         break;
534                 case 'd':
535                         debug++;
536                         break;
537                 case 'f':
538                         flood = 1;
539                         break;
540                 case 'i':
541                         argc--,argv++;
542                         interval = atoi(*argv);
543                         if(interval < 0)
544                                 usage();
545                         break;
546                 case 'l':
547                         lostonly++;
548                         break;
549                 case 'n':
550                         argc--,argv++;
551                         nmsg = atoi(*argv);
552                         if(nmsg < 0)
553                                 usage();
554                         break;
555                 case 'q':
556                         quiet = 1;
557                         break;
558                 case 'r':
559                         pingrint = 1;
560                         break;
561                 case 's':
562                         argc--,argv++;
563                         msglen = atoi(*argv);
564                         break;
565                 case 'w':
566                         argc--,argv++;
567                         waittime = atoi(*argv);
568                         if(waittime < 0)
569                                 usage();
570                         break;
571                 default:
572                         usage();
573                         break;
574                 }
575                 
576                 argc--,argv++;
577         }
578
579         if(msglen < proto->iphdrsz + ICMP_HDRSIZE)
580                 msglen = proto->iphdrsz + ICMP_HDRSIZE;
581         if(msglen < 64)
582                 msglen = 64;
583         if(msglen >= 64*1024)
584                 msglen = 64*1024-1;
585         if(interval <= 0 && !flood)
586                 interval = SLEEPMS;
587
588         if(argc < 1)
589                 usage();
590
591         /* TODO: consider catching ctrl-c and other signals. */
592
593         if (!isv4name(argv[0]))
594                 proto = &v6pr;
595         ds = netmkaddr(argv[0], proto->net, "1");
596         printf("ping: dial %s\n", ds);
597         fd = dial(ds, 0, 0, 0);
598         if(fd < 0){
599                 fprintf(stderr, "%s: couldn't dial %s: %r\n", argv0, ds);
600                 exit(1);
601         }
602
603         if (!quiet)
604                 printf("sending %d %d byte messages %d ms apart to %s\n",
605                         nmsg, msglen, interval, ds);
606
607         /* Spawning the receiver on a separate thread, possibly separate core */
608         if (pthread_create(&rcvr, NULL, &rcvr_thread, NULL)) {
609                 perror("Failed to create recevier");
610                 exit(-1);
611         }
612         sender(fd, msglen, interval, nmsg);
613         /* races with prints from the rcvr.  either lock, or live with it! */
614         printd("Sent, now joining\n");
615         pthread_join(rcvr, NULL);
616         return 0;
617 }
618
619 /* Note: this gets called from clean, which happens when we complete a loop and
620  * got a reply in the receiver.  One problem is that we call printf from the
621  * receive loop, blocking all future receivers, so that their times include the
622  * printf time.  This isn't a huge issue, since the sender sleeps between each
623  * one, and hopefully the print is done when the sender fires again. */
624 void
625 reply(Req *r, void *v)
626 {
627         r->rtt /= 1000LL;
628         sum += r->rtt;
629         if(!r->replied)
630                 rcvdmsgs++;
631         if(!quiet && !lostonly)
632                 if(addresses)
633                         (*proto->prreply)(r, v);
634                 else
635                         printf("%3d: rtt %5lld usec, avg rtt %5lld usec, ttl = %d\n",
636                                 r->seq - firstseq, r->rtt, sum/rcvdmsgs, r->ttl);
637         r->replied = 1;
638 }
639
640 void
641 lost(Req *r, void *v)
642 {
643         if(!quiet)
644                 if(addresses && v != NULL)
645                         (*proto->prlost)(r->seq - firstseq, v);
646                 else
647                         printf("%3d: lost\n", r->seq - firstseq);
648         lostmsgs++;
649 }