ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/ip/ping.b/
implement Ping;
include "sys.m";
sys: Sys;
include "draw.m";
include "ip.m";
ip: IP;
IPaddr: import ip;
include "timers.m";
timers: Timers;
Timer: import timers;
include "rand.m";
rand: Rand;
include "dial.m";
dial: Dial;
include "arg.m";
Ping: module
{
init: fn(nil: ref Draw->Context, nil: list of string);
};
Icmp: adt
{
ttl: int; # time to live
src: IPaddr;
dst: IPaddr;
ptype: int;
code: int;
seq: int;
munged: int;
time: big;
unpack: fn(b: array of byte): ref Icmp;
};
# packet types
EchoReply: con 0;
Unreachable: con 3;
SrcQuench: con 4;
EchoRequest: con 8;
TimeExceed: con 11;
Timestamp: con 13;
TimestampReply: con 14;
InfoRequest: con 15;
InfoReply: con 16;
Nmsg: con 32;
Interval: con 1000; # ms
Req: adt
{
seq: int; # sequence number
time: big; # time sent
rtt: big;
ttl: int;
replied: int;
};
debug := 0;
quiet := 0;
lostonly := 0;
lostmsgs := 0;
rcvdmsgs := 0;
sum := big 0;
firstseq := 0;
addresses := 0;
init(nil: ref Draw->Context, args: list of string)
{
sys = load Sys Sys->PATH;
rand = load Rand Rand->PATH;
timers = load Timers Timers->PATH;
dial = load Dial Dial->PATH;
ip = load IP IP->PATH;
ip->init();
msglen := interval := 0;
nmsg := Nmsg;
arg := load Arg Arg->PATH;
arg->init(args);
arg->setusage("ip/ping [-alq] [-s msgsize] [-i millisecs] [-n #pings] destination");
while((o := arg->opt()) != 0)
case o {
'l' =>
lostonly++;
'd' =>
debug++;
's' =>
msglen = int arg->earg();
'i' =>
interval = int arg->earg();
'n' =>
nmsg = int arg->earg();
'a' =>
addresses = 1;
'q' =>
quiet = 1;
}
if(msglen < 32)
msglen = 64;
if(msglen >= 65*1024)
msglen = 65*1024-1;
if(interval <= 0)
interval = Interval;
args = arg->argv();
if(args == nil)
arg->usage();
arg = nil;
sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
opentime();
rand->init(int(nsec()/big 1000));
addr := dial->netmkaddr(hd args, "icmp", "1");
c := dial->dial(addr, nil);
if(c == nil){
sys->fprint(sys->fildes(2), "ip/ping: can't dial %s: %r\n", addr);
raise "fail:dial";
}
sys->print("sending %d %d byte messages %d ms apart\n", nmsg, msglen, interval);
done := chan of int;
reqs := chan of ref Req;
spawn sender(c.dfd, msglen, interval, nmsg, done, reqs);
spid := <-done;
pids := chan of int;
replies := chan [8] of ref Icmp;
spawn reader(c.dfd, msglen, replies, pids);
rpid := <-pids;
tpid := 0;
timeout := chan of int;
requests: list of ref Req;
Work:
for(;;) alt{
r := <-reqs =>
requests = r :: requests;
ic := <-replies =>
if(ic == nil){
rpid = 0;
break Work;
}
if(ic.munged)
sys->print("corrupted reply\n");
if(ic.ptype != EchoReply || ic.code != 0){
sys->print("bad type/code %d/%d seq %d\n",
ic.ptype, ic.code, ic.seq);
continue;
}
requests = clean(requests, ic);
if(lostmsgs+rcvdmsgs == nmsg)
break Work;
<-done =>
spid = 0;
# must be at least one message outstanding; wait for it
tpid = timers->init(Timers->Sec);
timeout = Timer.start((nmsg-lostmsgs-rcvdmsgs)*interval+5*Timers->Sec).timeout;
<-timeout =>
break Work;
}
kill(rpid);
kill(spid);
kill(tpid);
for(; requests != nil; requests = tl requests)
if((hd requests).replied == 0)
lostmsgs++;
if(lostmsgs){
sys->print("%d out of %d message(s) lost\n", lostmsgs, lostmsgs+rcvdmsgs);
raise "fail:lost messages";
}
}
kill(pid: int)
{
if(pid)
sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill");
}
SECOND: con big 1000000000; # nanoseconds
MINUTE: con big 60*SECOND;
clean(l: list of ref Req, ip: ref Icmp): list of ref Req
{
left: list of ref Req;
for(; l != nil; l = tl l){
r := hd l;
if(ip.seq == r.seq){
r.rtt = ip.time-r.time;
r.ttl = ip.ttl;
reply(r, ip);
}
if(ip.time-r.time > MINUTE){
r.rtt = ip.time-r.time;
r.ttl = ip.ttl;
if(!r.replied)
lost(r, ip);
}else
left = r :: left;
}
return left;
}
sender(fd: ref Sys->FD, msglen: int, interval: int, n: int, done: chan of int, reqs: chan of ref Req)
{
done <-= sys->pctl(0, nil);
firstseq = rand->rand(65536) - n; # -n to ensure we don't exceed 16 bits
if(firstseq < 0)
firstseq = 0;
buf := array[64*1024+512] of {* => byte 0};
for(i := Odata; i < msglen; i++)
buf[i] = byte i;
buf[Otype] = byte EchoRequest;
buf[Ocode] = byte 0;
seq := firstseq;
for(i = 0; i < n; i++){
if(i != 0)
sys->sleep(interval);
ip->put2(buf, Oseq, seq); # order?
r := ref Req;
r.seq = seq;
r.replied = 0;
r.time = nsec();
reqs <-= r;
if(sys->write(fd, buf, msglen) < msglen){
sys->fprint(sys->fildes(2), "ping: write failed: %r\n");
break;
}
seq++;
}
done <-= 1;
}
reader(fd: ref Sys->FD, msglen: int, out: chan of ref Icmp, pid: chan of int)
{
pid <-= sys->pctl(0, nil);
buf := array[64*1024+512] of byte;
while((n := sys->read(fd, buf, len buf)) > 0){
now := nsec();
if(n < msglen){
sys->print("bad len %d/%d\n", n, msglen);
continue;
}
ic := Icmp.unpack(buf[0:n]);
ic.munged = 0;
for(i := Odata; i < msglen; i++)
if(buf[i] != byte i)
ic.munged++;
ic.time = now;
out <-= ic;
}
sys->print("read: %r\n");
out <-= nil;
}
reply(r: ref Req, ic: ref Icmp)
{
rcvdmsgs++;
r.rtt /= big 1000;
sum += r.rtt;
if(!quiet && !lostonly){
if(addresses)
sys->print("%ud: %s->%s rtt %bd µs, avg rtt %bd µs, ttl = %d\n",
r.seq-firstseq,
ic.src.text(), ic.dst.text(),
r.rtt, sum/big rcvdmsgs, r.ttl);
else
sys->print("%ud: rtt %bd µs, avg rtt %bd µs, ttl = %d\n",
r.seq-firstseq,
r.rtt, sum/big rcvdmsgs, r.ttl);
}
r.replied = 1; # TO DO: duplicates might be interesting
}
lost(r: ref Req, ic: ref Icmp)
{
if(!quiet){
if(addresses)
sys->print("lost %ud: %s->%s avg rtt %bd µs\n",
r.seq-firstseq,
ic.src.text(), ic.dst.text(),
sum/big rcvdmsgs);
else
sys->print("lost %ud: avg rtt %bd µs\n",
r.seq-firstseq,
sum/big rcvdmsgs);
}
lostmsgs++;
}
Ovihl: con 0;
Otos: con 1;
Olength: con 2;
Oid: con Olength+2;
Ofrag: con Oid+2;
Ottl: con Ofrag+2;
Oproto: con Ottl+1;
Oipcksum: con Oproto+1;
Osrc: con Oipcksum+2;
Odst: con Osrc+4;
Otype: con Odst+4;
Ocode: con Otype+1;
Ocksum: con Ocode+1;
Oicmpid: con Ocksum+2;
Oseq: con Oicmpid+2;
Odata: con Oseq+2;
Icmp.unpack(b: array of byte): ref Icmp
{
ic := ref Icmp;
ic.ttl = int b[Ottl];
ic.src = IPaddr.newv4(b[Osrc:]);
ic.dst = IPaddr.newv4(b[Odst:]);
ic.ptype = int b[Otype];
ic.code = int b[Ocode];
ic.seq = ip->get2(b, Oseq);
ic.munged = 0;
ic.time = big 0;
return ic;
}
timefd: ref Sys->FD;
opentime()
{
timefd = sys->open("/dev/time", Sys->OREAD);
if(timefd == nil){
sys->fprint(sys->fildes(2), "ping: can't open /dev/time: %r\n");
raise "fail:no time";
}
}
nsec(): big
{
buf := array[64] of byte;
n := sys->pread(timefd, buf, len buf, big 0);
if(n <= 0)
return big 0;
return big string buf[0:n] * big 1000;
}