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; }