xref: /inferno-os/appl/cmd/ip/rip.b (revision 9274481003af38a88988b4e9a3a2c3e0df206bee)
1implement Rip;
2
3# basic RIP implementation
4#	understands v2, sends v1
5
6include "sys.m";
7	sys: Sys;
8
9include "draw.m";
10
11include "bufio.m";
12	bufio: Bufio;
13	Iobuf: import bufio;
14
15include "daytime.m";
16	daytime: Daytime;
17
18include "dial.m";
19	dial: Dial;
20
21
22include "ip.m";
23	ip: IP;
24	IPaddr, Ifcaddr, Udphdr: import ip;
25
26include "attrdb.m";
27	attrdb: Attrdb;
28
29include "arg.m";
30
31Rip: module
32{
33	init:	fn(nil: ref Draw->Context, nil: list of string);
34};
35
36# rip header:
37#	op[1] version[1] pad[2]
38
39Oop: con 0;	# op: byte
40Oversion: con 1;	# version: byte
41Opad: con 2;	# 2 byte pad
42Riphdrlen: con	Opad+2;	# op[1] version[1] mbz[2]
43
44# rip route entry:
45#	type[2] tag[2] addr[4] mask[4] nexthop[4] metric[4]
46
47Otype: con 0;	# type[2]
48Otag: con Otype+2;	# tag[2] v2 or mbz v1
49Oaddr: con Otag+2;	# addr[4]
50Omask: con Oaddr+4;	# mask[4] v2 or mbz v1
51Onexthop: con Omask+4;
52Ometric: con Onexthop+4;	# metric[4]
53Ipdestlen: con Ometric+4;
54
55Maxripmsg: con 512;
56
57# operations
58OpRequest: con 1;		# want route
59OpReply: con 2;		# all or part of route table
60
61HopLimit: con 16;		# defined by protocol as `infinity'
62RoutesInPkt: con 25; 	# limit defined by protocol
63RIPport: con 520;
64
65Expired: con 180;
66Discard: con 240;
67
68OutputRate: con 60;	# seconds between routing table transmissions
69
70NetworkCost: con 1;	# assume the simple case
71
72Gateway: adt {
73	dest:	IPaddr;
74	mask:	IPaddr;
75	gateway:	IPaddr;
76	metric:	int;
77	valid:	int;
78	changed:	int;
79	local:	int;
80	time:	int;
81
82	contains:	fn(g: self ref Gateway, a: IPaddr): int;
83};
84
85netfd:	ref Sys->FD;
86routefd:	ref Sys->FD;
87AF_INET:	con 2;
88
89routes: array of ref Gateway;
90Routeinc: con 50;
91defroute: ref Gateway;
92debug := 0;
93nochange := 0;
94quiet := 1;
95myversion := 1;	# default protocol version
96logfile := "iproute";
97netdir := "/net";
98now: int;
99nets: list of ref Ifcaddr;
100addrs: list of IPaddr;
101
102syslog(nil: int, nil: string, s: string)
103{
104	sys->print("rip: %s\n", s);
105}
106
107init(nil: ref Draw->Context, args: list of string)
108{
109	sys = load Sys Sys->PATH;
110	bufio = load Bufio Bufio->PATH;
111	daytime = load Daytime Daytime->PATH;
112	dial = load Dial Dial->PATH;
113	ip = load IP IP->PATH;
114	ip->init();
115
116	arg := load Arg Arg->PATH;
117	arg->init(args);
118	arg->setusage("ip/rip [-d] [-r]");
119	while((o := arg->opt()) != 0)
120		case o {
121		'd' =>	debug++;
122		'b' =>	quiet = 0;
123		'2' =>	myversion = 2;
124		'n' =>	nochange = 1;
125		'x' =>	netdir = arg->earg();
126		* =>	arg->usage();
127		}
128	args = arg->argv();
129	if(args != nil)
130		quiet = 0;
131	for(; args != nil; args = tl args){
132		(ok, a) := IPaddr.parse(hd args);
133		if(ok < 0)
134			fatal(sys->sprint("invalid address: %s", hd args));
135		addrs = a :: addrs;
136	}
137	arg = nil;
138
139	sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKNS, nil);
140
141	whereami();
142	addlocal();
143
144	routefd = sys->open(sys->sprint("%s/iproute", netdir), Sys->ORDWR);
145	if(routefd == nil)
146		fatal(sys->sprint("can't open %s/iproute: %r", netdir));
147	readroutes();
148
149	syslog(0, logfile, "started");
150
151	netfd = riplisten();
152
153	# broadcast request for all routes
154
155	if(!quiet){
156		sendall(OpRequest, 0);
157		spawn sender();
158	}
159
160	# read routing requests
161
162	buf := array[8192] of byte;
163	while((nb := sys->read(netfd, buf, len buf)) > 0){
164		nb -= Riphdrlen + IP->Udphdrlen;
165		if(nb < 0)
166			continue;
167		uh := Udphdr.unpack(buf, IP->Udphdrlen);
168		hdr := buf[IP->Udphdrlen:];
169		version := int hdr[Oversion];
170		if(version < 1)
171			continue;
172		bp := buf[IP->Udphdrlen + Riphdrlen:];
173		case int hdr[Oop] {
174		OpRequest =>
175			# TO DO: transmit in response to request?  only if something interesting to say...
176			;
177
178		OpReply =>
179			# wrong source port?
180			if(uh.rport != RIPport)
181				continue;
182			# my own broadcast?
183			if(ismyaddr(uh.raddr))
184				continue;
185			now = daytime->now();
186			if(debug > 1)
187				sys->fprint(sys->fildes(2), "from %s:\n", uh.raddr.text());
188			for(; (nb -= Ipdestlen) >= 0; bp = bp[Ipdestlen:])
189				unpackroute(bp, version, uh.raddr);
190		* =>
191			if(debug)
192				sys->print("rip: unexpected op: %d\n", int hdr[Oop]);
193		}
194	}
195}
196
197whereami()
198{
199	for(ifcs := ip->readipifc(netdir, -1).t0; ifcs != nil; ifcs = tl ifcs)
200		for(al := (hd ifcs).addrs; al != nil; al = tl al){
201			ifa := hd al;
202			if(!ifa.ip.isv4())
203				continue;
204			# how to tell broadcast? must be told? actually, it's in /net/iproute
205			nets = ifa :: nets;
206		}
207}
208
209ismyaddr(a: IPaddr): int
210{
211	for(l := nets; l != nil; l = tl l)
212		if((hd l).ip.eq(a))
213			return 1;
214	return 0;
215}
216
217addlocal()
218{
219	for(l := nets; l != nil; l = tl l){
220		ifc := hd l;
221		g := lookup(ifc.net);
222		g.valid = 1;
223		g.local = 1;
224		g.gateway = ifc.ip;
225		g.mask = ifc.mask;
226		g.metric = NetworkCost;
227		g.time = 0;
228		g.changed = 1;
229		if(debug)
230			syslog(0, logfile, sys->sprint("Existing: %s & %s -> %s", g.dest.text(), g.mask.masktext(), g.gateway.text()));
231	}
232}
233
234#
235# record any existing routes
236#
237readroutes()
238{
239	now = daytime->now();
240	b := bufio->fopen(routefd, Sys->OREAD);
241	while((l := b.gets('\n')) != nil){
242		(nf, flds) := sys->tokenize(l, " \t");
243		if(nf >= 5){
244			flags := hd tl tl tl flds;
245			if(flags == nil || flags[0] != '4' || contains(flags, "ibum"))
246				continue;
247			g := lookup(parseip(hd flds));
248			g.mask = parsemask(hd tl flds);
249			g.gateway = parseip(hd tl tl flds);
250			g.metric = HopLimit;
251			g.time = now;
252			g.changed = 1;
253			if(debug)
254				syslog(0, logfile, sys->sprint("Existing: %s & %s -> %s", g.dest.text(), g.mask.masktext(), g.gateway.text()));
255			if(iszero(g.dest) && iszero(g.mask)){
256				defroute = g;
257				g.local = 1;
258			}else if(defroute != nil && g.dest.eq(defroute.gateway))
259				continue;
260			else
261				g.local = !ismyaddr(g.gateway);
262		}
263	}
264}
265
266unpackroute(b: array of byte, version: int, gwa: IPaddr)
267{
268	# check that it's an IP route, valid metric, MBZ fields zero
269
270	if(b[0] != byte 0 || b[1] != byte AF_INET){
271		if(debug > 1)
272			sys->fprint(sys->fildes(2), "\t-- unknown address type %x,%x\n", int b[0], int b[1]);
273		return;
274	}
275	dest := IPaddr.newv4(b[Oaddr:]);
276	mask: IPaddr;
277	if(version == 1){
278		# check MBZ fields
279		if(ip->get2(b, 2) | ip->get4(b, Omask) | ip->get4(b, Onexthop)){
280			if(debug > 1)
281				sys->fprint(sys->fildes(2), "\t-- non-zero MBZ\n");
282			return;
283		}
284		mask = maskgen(dest);
285	}else if(version == 2){
286		if(ip->get4(b, Omask))
287			mask = IPaddr.newv4(b[Omask:]);
288		else
289			mask = maskgen(dest);
290		if(ip->get4(b, Onexthop))
291			gwa = IPaddr.newv4(b[Onexthop:]);
292	}
293	metric := ip->get4(b, Ometric);
294	if(debug > 1)
295		sys->fprint(sys->fildes(2), "\t%s %d\n", dest.text(), metric);
296	if(metric <= 0 || metric > HopLimit)
297		return;
298
299	# 1058/3.4.2: response processing
300	# ignore route if IP address is:
301	#	class D or E
302	#	net 0 (except perhaps 0.0.0.0)
303	#	net 127
304	#	broadcast address (all 1s host part)
305	# we allow host routes
306
307	if(dest.ismulticast() || dest.a[0] == byte 0 || dest.a[0] == byte 16r7F){
308		if(debug > 1)
309			sys->fprint(sys->fildes(2), "\t%s %d invalid addr\n", dest.text(), metric);
310		return;
311	}
312	if(isbroadcast(dest, mask)){
313		if(debug > 1)
314			sys->fprint(sys->fildes(2), "\t%s & %s -> broadcast\n", dest.text(), mask.masktext());
315		return;
316	}
317
318	# update the metric min(metric+NetworkCost, HopLimit)
319
320	metric += NetworkCost;
321	if(metric > HopLimit)
322		metric = HopLimit;
323
324	updateroute(dest, mask, gwa, metric);
325}
326
327updateroute(dest, mask, gwa: IPaddr, metric: int)
328{
329	# RFC1058 rules page 27-28, with optional replacement of expiring routes
330	r := lookup(dest);
331	if(r.valid){
332		if(r.local)
333			return;	# local, don't touch
334		if(r.gateway.eq(gwa)){
335			if(metric != HopLimit){
336				r.metric = metric;
337				r.time = now;
338			}else{
339				# metric == HopLimit
340				if(r.metric != HopLimit){
341					r.metric = metric;
342					r.changed = 1;
343					r.time = now - (Discard-120);
344					delroute(r);	# don't use it for routing
345					# route remains valid but advertised with metric HopLimit
346				} else if(now >= r.time+Discard){
347					delroute(r);	# finally dead
348					r.valid = 0;
349					r.changed = 1;
350				}
351			}
352		}else if(metric < r.metric ||
353			  metric != HopLimit && metric == r.metric && now > r.time+Expired/2){
354			delroute(r);
355			r.metric = metric;
356			r.gateway = gwa;
357			r.time = now;
358			addroute(r);
359		}
360	} else if(metric < HopLimit){	# new entry
361
362		# 1058/3.4.2: don't add route-to-host if host is on net/subnet
363		# for which we have at least as good a route
364
365		if(!mask.eq(ip->allbits) ||
366		   ((pr := findroute(dest)) == nil || metric <= pr.metric)){
367			r.valid = 1;
368			r.changed = 1;
369			r.time = now;
370			r.metric = metric;
371			r.dest = dest;
372			r.mask = mask;
373			r.gateway = gwa;
374			addroute(r);
375		}
376	}
377}
378
379sender()
380{
381	for(;;){
382		sys->sleep(OutputRate*1000);	# could add some random fizz
383		sendall(OpReply, 1);
384	}
385}
386
387onlist(a: IPaddr, l: list of IPaddr): int
388{
389	for(; l != nil; l = tl l)
390		if(a.eq(hd l))
391			return 1;
392	return 0;
393}
394
395sendall(op: int, changes: int)
396{
397	for(l := nets; l != nil; l = tl l){
398		if(addrs != nil && !onlist((hd l).net, addrs))
399			continue;
400		a := (hd l).net.copy();
401		b := (ip->allbits).maskn((hd l).mask);
402		for(i := 0; i < len a.a; i++)
403			a.a[i] |= b.a[i];
404		sendroutes(hd l, a, op, changes);
405	}
406	for(i := 0; i < len routes; i++)
407		if((r := routes[i]) != nil)
408			r.changed = 0;
409}
410
411zeroentry := array[Ipdestlen] of {* => byte 0};
412
413sendroutes(ifc: ref Ifcaddr, dst: IPaddr, op: int, changes: int)
414{
415	if(debug > 1)
416		sys->print("rip: send %s\n", dst.text());
417	buf := array[Maxripmsg+IP->Udphdrlen] of byte;
418	hdr := Udphdr.new();
419	hdr.lport = hdr.rport = RIPport;
420	hdr.raddr = dst;	# needn't copy
421	hdr.pack(buf, IP->Udphdrlen);
422	o := IP->Udphdrlen;
423	buf[o] = byte op;
424	buf[o+1] = byte myversion;
425	buf[o+2] = byte 0;
426	buf[o+3] = byte 0;
427	o += Riphdrlen;
428#	rips := buf[IP->Udphdrlen+Riphdrlen:];
429	if(op == OpRequest){
430		buf[o:] = zeroentry;
431		ip->put4(buf, o+Ometric, HopLimit);
432		o += Ipdestlen;
433	} else {
434		# send routes
435		for(i:=0; i<len routes; i++){
436			r := routes[i];
437			if(r == nil || !r.valid || changes && !r.changed)
438				continue;
439			if(r == defroute)
440				continue;
441			if(r.dest.eq(ifc.net) || isonnet(r.dest, ifc))
442				continue;
443			netmask := r.dest.classmask();
444			subnet := !r.mask.eq(netmask);
445			if(myversion < 2 && !r.mask.eq(ip->allbits)){
446				# if not a host route, don't let a subnet route leave its net
447				if(subnet && !netmask.eq(ifc.ip.classmask()))
448					continue;
449			}
450			if(o+Ipdestlen > IP->Udphdrlen+Maxripmsg){
451				if(sys->write(netfd, buf, o) < 0)
452					sys->fprint(sys->fildes(2), "RIP write failed: %r\n");
453				o = IP->Udphdrlen + Riphdrlen;
454			}
455			buf[o:] = zeroentry;
456			ip->put2(buf, o+Otype, AF_INET);
457			buf[o+Oaddr:] = r.dest.v4();
458			ip->put4(buf, o+Ometric, r.metric);
459			if(myversion == 2 && subnet)
460				buf[o+Omask:] = r.mask.v4();
461			o += Ipdestlen;
462		}
463	}
464	if(o > IP->Udphdrlen+Riphdrlen && sys->write(netfd, buf, o) < 0)
465		sys->fprint(sys->fildes(2), "rip: network write to %s failed: %r\n", dst.text());
466}
467
468lookup(addr: IPaddr): ref Gateway
469{
470	avail := -1;
471	for(i:=0; i<len routes; i++){
472		g := routes[i];
473		if(g == nil || !g.valid){
474			if(avail < 0)
475				avail = i;
476			continue;
477		}
478		if(g.dest.eq(addr))
479			return g;
480	}
481	if(avail < 0){
482		avail = len routes;
483		a := array[len routes+Routeinc] of ref Gateway;
484		a[0:] = routes;
485		routes = a;
486	}
487	if((g := routes[avail]) == nil){
488		g = ref Gateway;
489		routes[avail] = g;
490		g.valid = 0;
491	}
492	g.dest = addr;
493	return g;
494}
495
496findroute(a: IPaddr): ref Gateway
497{
498	pr: ref Gateway;
499	for(i:=0; i<len routes; i++){
500		r := routes[i];
501		if(r == nil || !r.valid)
502			continue;
503		if(r.contains(a) && (pr == nil || !maskle(r.mask, pr.mask)))
504			pr = r;	# more specific mask
505	}
506	return pr;
507}
508
509maskgen(addr: IPaddr): IPaddr
510{
511	net: ref Ifcaddr;
512	for(l := nets; l != nil; l = tl l){
513		ifc := hd l;
514		if(isonnet(addr, ifc) &&
515		   (net == nil || maskle(ifc.mask, net.mask)))	# less specific mask?
516			net = ifc;
517	}
518	if(net != nil)
519		return net.mask;
520	return addr.classmask();
521}
522
523isonnet(a: IPaddr, n: ref Ifcaddr): int
524{
525	return a.mask(n.mask).eq(n.net);
526}
527
528isbroadcast(a: IPaddr, mask: IPaddr): int
529{
530	h := a.maskn(mask);	# host part
531	hm := (ip->allbits).maskn(mask);	# host part of mask
532	return h.eq(hm);
533}
534
535iszero(a: IPaddr): int
536{
537	return a.eq(ip->v4noaddr) || a.eq(ip->noaddr);
538}
539
540maskle(a, b: IPaddr): int
541{
542	return a.mask(b).eq(a);
543}
544
545#
546# add ipdest mask gateway
547# add 0.0.0.0 0.0.0.0 gateway	(default)
548# delete ipdest mask
549#
550addroute(g: ref Gateway)
551{
552	if(iszero(g.mask) && iszero(g.dest))
553		g.valid = 0;	# don't change default route
554	else if(defroute != nil && defroute.gateway.eq(g.gateway)){
555		if(debug)
556			syslog(0, logfile, sys->sprint("default %s %s", g.dest.text(), g.mask.text()));	# don't need a new entry
557		g.valid = 1;
558		g.changed = 1;
559	} else {
560		if(debug)
561			syslog(0, logfile, sys->sprint("add %s %s %s", g.dest.text(), g.mask.text(), g.gateway.text()));
562		if(nochange || sys->fprint(routefd, "add %s %s %s", g.dest.text(), g.mask.text(), g.gateway.text()) > 0){
563			g.valid = 1;
564			g.changed = 1;
565		}
566	}
567}
568
569delroute(g: ref Gateway)
570{
571	if(debug)
572		syslog(0, logfile, sys->sprint("delete %s %s", g.dest.text(), g.mask.text()));
573	if(!nochange)
574		sys->fprint(routefd, "delete %s %s", g.dest.text(), g.mask.text());
575}
576
577parseip(s: string): IPaddr
578{
579	(ok, a) := IPaddr.parse(s);
580	if(ok < 0)
581		raise "bad route";
582	return a;
583}
584
585parsemask(s: string): IPaddr
586{
587	(ok, a) := IPaddr.parsemask(s);
588	if(ok < 0)
589		raise "bad route";
590	return a;
591}
592
593contains(s: string, t: string): int
594{
595	for(i := 0; i < len s; i++)
596		for(j := 0; j < len t; j++)
597			if(s[i] == t[j])
598				return 1;
599	return 0;
600}
601
602Gateway.contains(g: self ref Gateway, a: IPaddr): int
603{
604	return g.dest.eq(a.mask(g.mask));
605}
606
607riplisten(): ref Sys->FD
608{
609	addr := sys->sprint("%s/udp!*!rip", netdir);
610	c := dial->announce(addr);
611	if(c == nil)
612		fatal(sys->sprint("can't announce %s: %r", addr));
613	if(sys->fprint(c.cfd, "headers") < 0)
614		fatal(sys->sprint("can't set udp headers: %r"));
615	fd := sys->open(c.dir+"/data", Sys->ORDWR);
616	if(fd == nil)
617		fatal(sys->sprint("can't open %s: %r", c.dir+"/data"));
618	return fd;
619}
620
621fatal(s: string)
622{
623	syslog(0, logfile, s);
624	raise "fail:error";
625}
626