akaros/tests/ping.c
<<
>>
Prefs
   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
  31typedef struct Req Req;
  32struct 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
  42struct 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
  54Req     *first;         /* request list */
  55Req     *last;          /* ... */
  56struct spin_pdr_lock listlock = SPINPDR_INITIALIZER;
  57
  58char *argv0;
  59
  60int addresses;
  61int debug;
  62int done;
  63int flood;
  64int lostmsgs;
  65int lostonly;
  66int quiet;
  67int rcvdmsgs;
  68int pingrint;
  69uint16_t firstseq;
  70int64_t sum;
  71int waittime = 5000;
  72
  73static char *network, *target;
  74
  75void lost(Req*, void*);
  76void reply(Req*, void*);
  77
  78static 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
  86static 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
  93static 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
 100static 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
 109static 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
 118static struct proto v4pr = {
 119        4,              "icmp",
 120        EchoRequest,    EchoReply,
 121        IPV4HDR_LEN,
 122        prreply4,       prlost4,
 123};
 124static struct proto v6pr = {
 125        6,              "icmpv6",
 126        EchoRequestV6,  EchoReplyV6,
 127        IPV6HDR_LEN,
 128        prreply6,       prlost6,
 129};
 130
 131static struct proto *proto = &v4pr;
 132
 133
 134struct icmphdr *geticmp(void *v)
 135{
 136        char *p = v;
 137
 138        return (struct icmphdr *)(p + proto->iphdrsz);
 139}
 140
 141void 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
 178static 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};
 184static 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 */
 196static 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
 227void 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
 292void 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
 357static int
 358isdottedquad(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
 372static int
 373isv6lit(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
 389enum
 390{
 391        Maxstring       = 128,
 392        Maxpath         = 256,
 393};
 394
 395typedef struct DS DS;
 396struct 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 */
 412static 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 */
 444static int
 445isv4name(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 */
 493int fd, msglen, interval, nmsg;
 494
 495void *rcvr_thread(void* arg)
 496{       
 497        rcvr(fd, msglen, interval, nmsg);
 498        printd(lostmsgs ? "lost messages" : "");
 499        return 0;
 500}
 501
 502int 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. */
 615void 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
 631void 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}
 640