| /* This file is part of the UCB release of Plan 9. It is subject to the license |
| * terms in the LICENSE file found in the top-level directory of this |
| * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No |
| * part of the UCB release of Plan 9, including this file, may be copied, |
| * modified, propagated, or distributed except according to the terms contained |
| * in the LICENSE file. */ |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <parlib/parlib.h> |
| #include <unistd.h> |
| #include <signal.h> |
| #include <iplib/iplib.h> |
| #include <iplib/icmp.h> |
| #include <ctype.h> |
| #include <pthread.h> |
| #include <parlib/spinlock.h> |
| #include <parlib/timing.h> |
| #include <parlib/tsc-compat.h> |
| #include <parlib/printf-ext.h> |
| #include <parlib/stdio.h> |
| #include <parlib/alarm.h> |
| #include <ndblib/ndb.h> |
| |
| #define NR_MSG 4 |
| #define SLEEPMS 1000 |
| #define SECONDTSC (get_tsc_freq()) |
| #define MINUTETSC (60 * SECONDTSC) |
| #define BUFSIZE (64 * 1024 + 512) |
| |
| typedef struct Req Req; |
| struct Req |
| { |
| uint16_t seq; /* sequence number */ |
| uint64_t tsctime; /* time sent */ |
| int64_t rtt; |
| int ttl; |
| int replied; |
| Req *next; |
| }; |
| |
| struct proto { |
| int version; |
| char *net; |
| int echocmd; |
| int echoreply; |
| unsigned iphdrsz; |
| |
| void (*prreply)(Req *r, void *v); |
| void (*prlost)(uint16_t seq, void *v); |
| }; |
| |
| |
| Req *first; /* request list */ |
| Req *last; /* ... */ |
| struct spin_pdr_lock listlock = SPINPDR_INITIALIZER; |
| |
| char *argv0; |
| |
| int addresses; |
| int debug; |
| int done; |
| int flood; |
| int lostmsgs; |
| int lostonly; |
| int quiet; |
| int rcvdmsgs; |
| int pingrint; |
| uint16_t firstseq; |
| int64_t sum; |
| int waittime = 5000; |
| |
| static char *network, *target; |
| |
| void lost(Req*, void*); |
| void reply(Req*, void*); |
| |
| static void usage(void) |
| { |
| fprintf(stderr, |
| "usage: %s [-6alq] [-s msgsize] [-i millisecs] [-n #pings] dest\n", |
| argv0); |
| exit(1); |
| } |
| |
| static void prlost4(uint16_t seq, void *v) |
| { |
| struct ip4hdr *ip4 = v; |
| |
| printf("lost %u: %i -> %i\n", seq, ip4->src, ip4->dst); |
| } |
| |
| static void prlost6(uint16_t seq, void *v) |
| { |
| struct ip6hdr *ip6 = v; |
| |
| printf("lost %u: %i -> %i\n", seq, ip6->src, ip6->dst); |
| } |
| |
| static void prreply4(Req *r, void *v) |
| { |
| struct ip4hdr *ip4 = v; |
| |
| printf("%u: %i -> %i rtt %lld µs, avg rtt %lld µs, ttl = %d\n", |
| r->seq - firstseq, ip4->src, ip4->dst, r->rtt, sum/rcvdmsgs, |
| r->ttl); |
| } |
| |
| static void prreply6(Req *r, void *v) |
| { |
| struct ip6hdr *ip6 = v; |
| |
| printf("%u: %i -> %i rtt %lld µs, avg rtt %lld µs, ttl = %d\n", |
| r->seq - firstseq, ip6->src, ip6->dst, r->rtt, sum/rcvdmsgs, |
| r->ttl); |
| } |
| |
| static struct proto v4pr = { |
| 4, "icmp", |
| EchoRequest, EchoReply, |
| IPV4HDR_LEN, |
| prreply4, prlost4, |
| }; |
| static struct proto v6pr = { |
| 6, "icmpv6", |
| EchoRequestV6, EchoReplyV6, |
| IPV6HDR_LEN, |
| prreply6, prlost6, |
| }; |
| |
| static struct proto *proto = &v4pr; |
| |
| |
| struct icmphdr *geticmp(void *v) |
| { |
| char *p = v; |
| |
| return (struct icmphdr *)(p + proto->iphdrsz); |
| } |
| |
| void clean(uint16_t seq, int64_t now, void *v) |
| { |
| int ttl; |
| Req **l, *r; |
| |
| ttl = 0; |
| if (v) { |
| if (proto->version == 4) |
| ttl = ((struct ip4hdr *)v)->ttl; |
| else |
| ttl = ((struct ip6hdr *)v)->ttl; |
| } |
| spin_pdr_lock(&listlock); |
| last = NULL; |
| for(l = &first; *l; ){ |
| r = *l; |
| if(v && r->seq == seq){ |
| r->rtt = ndiff(r->tsctime, now); |
| r->ttl = ttl; |
| reply(r, v); |
| } |
| if (now - r->tsctime > MINUTETSC) { |
| *l = r->next; |
| r->rtt = ndiff(r->tsctime, now); |
| if(v) |
| r->ttl = ttl; |
| if(r->replied == 0) |
| lost(r, v); |
| free(r); |
| }else{ |
| last = r; |
| l = &r->next; |
| } |
| } |
| spin_pdr_unlock(&listlock); |
| } |
| |
| static uint8_t loopbacknet[IPaddrlen] = { |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0xff, 0xff, |
| 127, 0, 0, 0 |
| }; |
| static uint8_t loopbackmask[IPaddrlen] = { |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0, 0, 0 |
| }; |
| |
| /* |
| * find first ip addr suitable for proto and |
| * that isn't the friggin loopback address. |
| * deprecate link-local and multicast addresses. |
| */ |
| static int myipvnaddr(uint8_t *ip, struct proto *proto, char *net) |
| { |
| int ipisv4, wantv4; |
| struct ipifc *nifc; |
| struct iplifc *lifc; |
| uint8_t mynet[IPaddrlen], linklocal[IPaddrlen]; |
| static struct ipifc *ifc; |
| |
| ipmove(linklocal, IPnoaddr); |
| wantv4 = proto->version == 4; |
| ifc = readipifc(net, ifc, -1); |
| for(nifc = ifc; nifc; nifc = nifc->next) |
| for(lifc = nifc->lifc; lifc; lifc = lifc->next){ |
| maskip(lifc->ip, loopbackmask, mynet); |
| if(ipcmp(mynet, loopbacknet) == 0) |
| continue; |
| if(ISIPV6MCAST(lifc->ip) || ISIPV6LINKLOCAL(lifc->ip)) { |
| ipmove(linklocal, lifc->ip); |
| continue; |
| } |
| ipisv4 = isv4(lifc->ip) != 0; |
| if(ipcmp(lifc->ip, IPnoaddr) != 0 && wantv4 == ipisv4){ |
| ipmove(ip, lifc->ip); |
| return 0; |
| } |
| } |
| /* no global unicast addrs found, fall back to link-local, if any */ |
| ipmove(ip, linklocal); |
| return ipcmp(ip, IPnoaddr) == 0? -1: 0; |
| } |
| |
| void sender(int fd, int msglen, int interval, int n) |
| { |
| int i, extra; |
| uint16_t seq; |
| char *buf = malloc(BUFSIZE); |
| uint8_t me[IPaddrlen], mev4[IPv4addrlen]; |
| struct icmphdr *icmp; |
| Req *r; |
| |
| firstseq = seq = rand(); |
| |
| icmp = geticmp(buf); |
| memset(buf, 0, proto->iphdrsz + ICMP_HDRSIZE); |
| for(i = proto->iphdrsz + ICMP_HDRSIZE; i < msglen; i++) |
| buf[i] = i; |
| icmp->type = proto->echocmd; |
| icmp->code = 0; |
| |
| /* arguably the kernel should fill in the right src addr. */ |
| myipvnaddr(me, proto, network); |
| if (proto->version == 4) { |
| v6tov4(mev4, me); |
| memmove(((struct ip4hdr *)buf)->src, mev4, IPv4addrlen); |
| } else |
| ipmove(((struct ip6hdr *)buf)->src, me); |
| if (addresses) |
| printf("\t%i -> %s\n", me, target); |
| |
| if(pingrint != 0 && interval <= 0) |
| pingrint = 0; |
| extra = 0; |
| for(i = 0; i < n; i++){ |
| if(i != 0){ |
| if(pingrint != 0) |
| extra = rand(); |
| /* uth_sleep takes seconds, interval is in ms */ |
| uthread_usleep((interval + extra) * 1000); |
| } |
| r = calloc(sizeof *r, 1); |
| if (r == NULL){ |
| printf("out of memory? \n"); |
| break; |
| } |
| hnputs(icmp->seq, seq); |
| r->seq = seq; |
| r->next = NULL; |
| r->replied = 0; |
| r->tsctime = read_tsc(); /* avoid early free in reply! */ |
| spin_pdr_lock(&listlock); |
| if(first == NULL) |
| first = r; |
| else |
| last->next = r; |
| last = r; |
| spin_pdr_unlock(&listlock); |
| r->tsctime = read_tsc(); |
| if(write(fd, buf, msglen) < msglen){ |
| fprintf(stderr, "%s: write failed: %r\n", argv0); |
| return; |
| } |
| seq++; |
| } |
| done = 1; |
| } |
| |
| void rcvr(int fd, int msglen, int interval, int nmsg) |
| { |
| int i, n, munged; |
| uint16_t x; |
| int64_t now; |
| uint8_t *buf = malloc(BUFSIZE); |
| struct icmphdr *icmp; |
| Req *r; |
| struct alarm_waiter waiter; |
| |
| init_awaiter(&waiter, alarm_abort_sysc); |
| waiter.data = current_uthread; |
| |
| sum = 0; |
| while(lostmsgs+rcvdmsgs < nmsg){ |
| /* arm to wake ourselves if the read doesn't connect in time */ |
| set_awaiter_rel(&waiter, 1000 * |
| ((nmsg - lostmsgs - rcvdmsgs) * interval |
| + waittime)); |
| set_alarm(&waiter); |
| n = read(fd, buf, BUFSIZE); |
| /* cancel immediately, so future syscalls don't get aborted */ |
| unset_alarm(&waiter); |
| |
| now = read_tsc(); |
| if(n <= 0){ /* read interrupted - time to go */ |
| /* Faking time being a minute in the future, so clean |
| * marks our message as lost. Note this will also end |
| * up cancelling any other pending replies that would |
| * have expired by then. Whatever. */ |
| clean(0, now + MINUTETSC, NULL); |
| continue; |
| } |
| if(n < msglen){ |
| printf("bad len %d/%d\n", n, msglen); |
| continue; |
| } |
| icmp = geticmp(buf); |
| munged = 0; |
| for(i = proto->iphdrsz + ICMP_HDRSIZE; i < msglen; i++) |
| if(buf[i] != (uint8_t)i) |
| munged++; |
| if(munged) |
| printf("corrupted reply\n"); |
| x = nhgets(icmp->seq); |
| if(icmp->type != proto->echoreply || icmp->code != 0) { |
| printf("bad type/code/seq %d/%d/%d (want %d/%d/%d)\n", |
| icmp->type, icmp->code, x, |
| proto->echoreply, 0, x); |
| continue; |
| } |
| clean(x, now, buf); |
| } |
| |
| spin_pdr_lock(&listlock); |
| for(r = first; r; r = r->next) |
| if(r->replied == 0) |
| lostmsgs++; |
| spin_pdr_unlock(&listlock); |
| |
| if(!quiet && lostmsgs) |
| printf("%d out of %d messages lost\n", lostmsgs, |
| lostmsgs+rcvdmsgs); |
| } |
| |
| static int |
| isdottedquad(char *name) |
| { |
| int dot = 0, digit = 0; |
| |
| for (; *name != '\0'; name++) |
| if (*name == '.') |
| dot++; |
| else if (isdigit(*name)) |
| digit++; |
| else |
| return 0; |
| return dot && digit; |
| } |
| |
| static int |
| isv6lit(char *name) |
| { |
| int colon = 0, hex = 0; |
| |
| for (; *name != '\0'; name++) |
| if (*name == ':') |
| colon++; |
| else if (isxdigit(*name)) |
| hex++; |
| else |
| return 0; |
| return colon; |
| } |
| |
| /* from /sys/src/libc/9sys/dial.c */ |
| |
| enum |
| { |
| Maxstring = 128, |
| Maxpath = 256, |
| }; |
| |
| typedef struct DS DS; |
| struct DS { |
| /* dist string */ |
| char buf[Maxstring]; |
| char *netdir; |
| char *proto; |
| char *rem; |
| |
| /* other args */ |
| char *local; |
| char *dir; |
| int *cfdp; |
| }; |
| |
| /* |
| * parse a dial string |
| */ |
| static void |
| _dial_string_parse(char *str, DS *ds) |
| { |
| char *p, *p2; |
| |
| strncpy(ds->buf, str, Maxstring); |
| ds->buf[Maxstring-1] = 0; |
| |
| p = strchr(ds->buf, '!'); |
| if(p == 0) { |
| ds->netdir = 0; |
| ds->proto = "net"; |
| ds->rem = ds->buf; |
| } else { |
| if(*ds->buf != '/' && *ds->buf != '#'){ |
| ds->netdir = 0; |
| ds->proto = ds->buf; |
| } else { |
| for(p2 = p; *p2 != '/'; p2--) |
| ; |
| *p2++ = 0; |
| ds->netdir = ds->buf; |
| ds->proto = p2; |
| } |
| *p = 0; |
| ds->rem = p + 1; |
| } |
| } |
| |
| /* end excerpt from /sys/src/libc/9sys/dial.c */ |
| |
| /* side effect: sets network & target */ |
| static int |
| isv4name(char *name) |
| { |
| int r = 1; |
| char *root, *ip, *pr; |
| DS ds; |
| |
| _dial_string_parse(name, &ds); |
| |
| /* cope with leading /net.alt/icmp! and the like */ |
| root = NULL; |
| if (ds.netdir != NULL) { |
| pr = strrchr(ds.netdir, '/'); |
| if (pr == NULL) |
| pr = ds.netdir; |
| else { |
| *pr++ = '\0'; |
| root = ds.netdir; |
| network = strdup(root); |
| } |
| if (strcmp(pr, v4pr.net) == 0) |
| return 1; |
| if (strcmp(pr, v6pr.net) == 0) |
| return 0; |
| } |
| |
| /* if it's a literal, it's obvious from syntax which proto it is */ |
| free(target); |
| target = strdup(ds.rem); |
| if (isdottedquad(ds.rem)) |
| return 1; |
| else if (isv6lit(ds.rem)) |
| return 0; |
| /*we don't have cs.*/ |
| /* map name to ip and look at its syntax */ |
| ip = csgetvalue(root, "sys", ds.rem, "ip", NULL); |
| if (ip == NULL) |
| ip = csgetvalue(root, "dom", ds.rem, "ip", NULL); |
| if (ip == NULL) |
| ip = csgetvalue(root, "sys", ds.rem, "ipv6", NULL); |
| if (ip == NULL) |
| ip = csgetvalue(root, "dom", ds.rem, "ipv6", NULL); |
| if (ip != NULL) |
| r = isv4name(ip); |
| free(ip); |
| return r; |
| } |
| |
| /* Feel free to put these in a struct or something */ |
| int fd, msglen, interval, nmsg; |
| |
| void *rcvr_thread(void* arg) |
| { |
| rcvr(fd, msglen, interval, nmsg); |
| printd(lostmsgs ? "lost messages" : ""); |
| return 0; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| char *ds, ds_store[256]; |
| int pid; |
| pthread_t rcvr; |
| |
| register_printf_specifier('i', printf_ipaddr, printf_ipaddr_info); |
| |
| msglen = interval = 0; |
| nmsg = NR_MSG; |
| |
| argv0 = argv[0]; |
| if (argc <= 1) |
| usage(); |
| argc--, argv++; |
| while (**argv == '-'){ |
| switch(argv[0][1]){ |
| case '6': |
| proto = &v6pr; |
| break; |
| case 'a': |
| addresses = 1; |
| break; |
| case 'd': |
| debug++; |
| break; |
| case 'f': |
| flood = 1; |
| break; |
| case 'i': |
| argc--,argv++; |
| interval = atoi(*argv); |
| if(interval < 0) |
| usage(); |
| break; |
| case 'l': |
| lostonly++; |
| break; |
| case 'n': |
| argc--,argv++; |
| nmsg = atoi(*argv); |
| if(nmsg < 0) |
| usage(); |
| break; |
| case 'q': |
| quiet = 1; |
| break; |
| case 'r': |
| pingrint = 1; |
| break; |
| case 's': |
| argc--,argv++; |
| msglen = atoi(*argv); |
| break; |
| case 'w': |
| argc--,argv++; |
| waittime = atoi(*argv); |
| if(waittime < 0) |
| usage(); |
| break; |
| default: |
| usage(); |
| break; |
| } |
| |
| argc--,argv++; |
| } |
| |
| if(msglen < proto->iphdrsz + ICMP_HDRSIZE) |
| msglen = proto->iphdrsz + ICMP_HDRSIZE; |
| if(msglen < 64) |
| msglen = 64; |
| if(msglen >= 64*1024) |
| msglen = 64*1024-1; |
| if(interval <= 0 && !flood) |
| interval = SLEEPMS; |
| |
| if(argc < 1) |
| usage(); |
| |
| /* TODO: consider catching ctrl-c and other signals. */ |
| |
| if (!isv4name(argv[0])) |
| proto = &v6pr; |
| ds = netmkaddr(argv[0], proto->net, "1", ds_store, sizeof(ds_store)); |
| printf("ping: dial %s\n", ds); |
| fd = dial9(ds, 0, 0, 0, 0); |
| if(fd < 0){ |
| fprintf(stderr, "%s: couldn't dial %s: %r\n", argv0, ds); |
| exit(1); |
| } |
| |
| if (!quiet) |
| printf("sending %d %d byte messages %d ms apart to %s\n", |
| nmsg, msglen, interval, ds); |
| |
| /* Spawning the receiver on a separate thread, possibly separate core */ |
| if (pthread_create(&rcvr, NULL, &rcvr_thread, NULL)) { |
| perror("Failed to create recevier"); |
| exit(-1); |
| } |
| sender(fd, msglen, interval, nmsg); |
| /* races with prints from the rcvr. either lock, or live with it! */ |
| printd("Sent, now joining\n"); |
| pthread_join(rcvr, NULL); |
| return 0; |
| } |
| |
| /* Note: this gets called from clean, which happens when we complete a loop and |
| * got a reply in the receiver. One problem is that we call printf from the |
| * receive loop, blocking all future receivers, so that their times include the |
| * printf time. This isn't a huge issue, since the sender sleeps between each |
| * one, and hopefully the print is done when the sender fires again. */ |
| void reply(Req *r, void *v) |
| { |
| r->rtt /= 1000LL; |
| sum += r->rtt; |
| if(!r->replied) |
| rcvdmsgs++; |
| if(!quiet && !lostonly) |
| if(addresses) |
| (*proto->prreply)(r, v); |
| else |
| printf("%3d: rtt %5lld usec, avg rtt %5lld usec, ttl = %d\n", |
| r->seq - firstseq, r->rtt, sum/rcvdmsgs, |
| r->ttl); |
| r->replied = 1; |
| } |
| |
| void lost(Req *r, void *v) |
| { |
| if(!quiet) |
| if(addresses && v != NULL) |
| (*proto->prlost)(r->seq - firstseq, v); |
| else |
| printf("%3d: lost\n", r->seq - firstseq); |
| lostmsgs++; |
| } |