blob: 71853c9a01db725888cf0e66acbb85eaae2ecc9a [file] [log] [blame]
/* 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++;
}