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