summaryrefslogtreecommitdiff
path: root/appl/cmd/ip/ping.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/ip/ping.b')
-rw-r--r--appl/cmd/ip/ping.b369
1 files changed, 369 insertions, 0 deletions
diff --git a/appl/cmd/ip/ping.b b/appl/cmd/ip/ping.b
new file mode 100644
index 00000000..a148c1e6
--- /dev/null
+++ b/appl/cmd/ip/ping.b
@@ -0,0 +1,369 @@
+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 "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;
+ 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 := netmkaddr(hd args, "icmp", "1");
+ (ok, c) := sys->dial(addr, nil);
+ if(ok < 0){
+ 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;
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
+
+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;
+}