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