Charles.Forsyth20060303a828770d19 years agoHistory
369 lines · 7.0 KB · Brainfuck
RawDownload
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 typesEchoReply: 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;	# nanosecondsMINUTE: 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;}