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