summaryrefslogtreecommitdiff
path: root/appl/cmd/ip/rip.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/ip/rip.b')
-rw-r--r--appl/cmd/ip/rip.b620
1 files changed, 620 insertions, 0 deletions
diff --git a/appl/cmd/ip/rip.b b/appl/cmd/ip/rip.b
new file mode 100644
index 00000000..90c1b6ce
--- /dev/null
+++ b/appl/cmd/ip/rip.b
@@ -0,0 +1,620 @@
+implement Rip;
+
+# basic RIP implementation
+# understands v2, sends v1
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "ip.m";
+ ip: IP;
+ IPaddr, Ifcaddr, Udphdr: import ip;
+
+include "attrdb.m";
+ attrdb: Attrdb;
+
+include "arg.m";
+
+Rip: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+# rip header:
+# op[1] version[1] pad[2]
+
+Oop: con 0; # op: byte
+Oversion: con 1; # version: byte
+Opad: con 2; # 2 byte pad
+Riphdrlen: con Opad+2; # op[1] version[1] mbz[2]
+
+# rip route entry:
+# type[2] tag[2] addr[4] mask[4] nexthop[4] metric[4]
+
+Otype: con 0; # type[2]
+Otag: con Otype+2; # tag[2] v2 or mbz v1
+Oaddr: con Otag+2; # addr[4]
+Omask: con Oaddr+4; # mask[4] v2 or mbz v1
+Onexthop: con Omask+4;
+Ometric: con Onexthop+4; # metric[4]
+Ipdestlen: con Ometric+4;
+
+Maxripmsg: con 512;
+
+# operations
+OpRequest: con 1; # want route
+OpReply: con 2; # all or part of route table
+
+HopLimit: con 16; # defined by protocol as `infinity'
+RoutesInPkt: con 25; # limit defined by protocol
+RIPport: con 520;
+
+Expired: con 180;
+Discard: con 240;
+
+OutputRate: con 60; # seconds between routing table transmissions
+
+NetworkCost: con 1; # assume the simple case
+
+Gateway: adt {
+ dest: IPaddr;
+ mask: IPaddr;
+ gateway: IPaddr;
+ metric: int;
+ valid: int;
+ changed: int;
+ local: int;
+ time: int;
+
+ contains: fn(g: self ref Gateway, a: IPaddr): int;
+};
+
+netfd: ref Sys->FD;
+routefd: ref Sys->FD;
+AF_INET: con 2;
+
+routes: array of ref Gateway;
+Routeinc: con 50;
+defroute: ref Gateway;
+debug := 0;
+nochange := 0;
+quiet := 1;
+myversion := 1; # default protocol version
+logfile := "iproute";
+netdir := "/net";
+now: int;
+nets: list of ref Ifcaddr;
+addrs: list of IPaddr;
+
+syslog(nil: int, nil: string, s: string)
+{
+ sys->print("rip: %s\n", s);
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ ip = load IP IP->PATH;
+ ip->init();
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("ip/rip [-d] [-r]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => debug++;
+ 'b' => quiet = 0;
+ '2' => myversion = 2;
+ 'n' => nochange = 1;
+ 'x' => netdir = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil)
+ quiet = 0;
+ for(; args != nil; args = tl args){
+ (ok, a) := IPaddr.parse(hd args);
+ if(ok < 0)
+ fatal(sys->sprint("invalid address: %s", hd args));
+ addrs = a :: addrs;
+ }
+ arg = nil;
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKNS, nil);
+
+ whereami();
+ addlocal();
+
+ routefd = sys->open(sys->sprint("%s/iproute", netdir), Sys->ORDWR);
+ if(routefd == nil)
+ fatal(sys->sprint("can't open %s/iproute: %r", netdir));
+ readroutes();
+
+ syslog(0, logfile, "started");
+
+ netfd = riplisten();
+
+ # broadcast request for all routes
+
+ if(!quiet){
+ sendall(OpRequest, 0);
+ spawn sender();
+ }
+
+ # read routing requests
+
+ buf := array[8192] of byte;
+ while((nb := sys->read(netfd, buf, len buf)) > 0){
+ nb -= Riphdrlen + IP->Udphdrlen;
+ if(nb < 0)
+ continue;
+ uh := Udphdr.unpack(buf, IP->Udphdrlen);
+ hdr := buf[IP->Udphdrlen:];
+ version := int hdr[Oversion];
+ if(version < 1)
+ continue;
+ bp := buf[IP->Udphdrlen + Riphdrlen:];
+ case int hdr[Oop] {
+ OpRequest =>
+ # TO DO: transmit in response to request? only if something interesting to say...
+ ;
+
+ OpReply =>
+ # wrong source port?
+ if(uh.rport != RIPport)
+ continue;
+ # my own broadcast?
+ if(ismyaddr(uh.raddr))
+ continue;
+ now = daytime->now();
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "from %s:\n", uh.raddr.text());
+ for(; (nb -= Ipdestlen) >= 0; bp = bp[Ipdestlen:])
+ unpackroute(bp, version, uh.raddr);
+ * =>
+ if(debug)
+ sys->print("rip: unexpected op: %d\n", int hdr[Oop]);
+ }
+ }
+}
+
+whereami()
+{
+ for(ifcs := ip->readipifc(netdir, -1).t0; ifcs != nil; ifcs = tl ifcs)
+ for(al := (hd ifcs).addrs; al != nil; al = tl al){
+ ifa := hd al;
+ if(!ifa.ip.isv4())
+ continue;
+ # how to tell broadcast? must be told? actually, it's in /net/iproute
+ nets = ifa :: nets;
+ }
+}
+
+ismyaddr(a: IPaddr): int
+{
+ for(l := nets; l != nil; l = tl l)
+ if((hd l).ip.eq(a))
+ return 1;
+ return 0;
+}
+
+addlocal()
+{
+ for(l := nets; l != nil; l = tl l){
+ ifc := hd l;
+ g := lookup(ifc.net);
+ g.valid = 1;
+ g.local = 1;
+ g.gateway = ifc.ip;
+ g.mask = ifc.mask;
+ g.metric = NetworkCost;
+ g.time = 0;
+ g.changed = 1;
+ if(debug)
+ syslog(0, logfile, sys->sprint("Existing: %s & %s -> %s", g.dest.text(), g.mask.masktext(), g.gateway.text()));
+ }
+}
+
+#
+# record any existing routes
+#
+readroutes()
+{
+ now = daytime->now();
+ b := bufio->fopen(routefd, Sys->OREAD);
+ while((l := b.gets('\n')) != nil){
+ (nf, flds) := sys->tokenize(l, " \t");
+ if(nf >= 5){
+ flags := hd tl tl tl flds;
+ if(flags == nil || flags[0] != '4' || contains(flags, "ibum"))
+ continue;
+ g := lookup(parseip(hd flds));
+ g.mask = parsemask(hd tl flds);
+ g.gateway = parseip(hd tl tl flds);
+ g.metric = HopLimit;
+ g.time = now;
+ g.changed = 1;
+ if(debug)
+ syslog(0, logfile, sys->sprint("Existing: %s & %s -> %s", g.dest.text(), g.mask.masktext(), g.gateway.text()));
+ if(iszero(g.dest) && iszero(g.mask)){
+ defroute = g;
+ g.local = 1;
+ }else if(defroute != nil && g.dest.eq(defroute.gateway))
+ continue;
+ else
+ g.local = !ismyaddr(g.gateway);
+ }
+ }
+}
+
+unpackroute(b: array of byte, version: int, gwa: IPaddr)
+{
+ # check that it's an IP route, valid metric, MBZ fields zero
+
+ if(b[0] != byte 0 || b[1] != byte AF_INET){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t-- unknown address type %x,%x\n", int b[0], int b[1]);
+ return;
+ }
+ dest := IPaddr.newv4(b[Oaddr:]);
+ mask: IPaddr;
+ if(version == 1){
+ # check MBZ fields
+ if(ip->get2(b, 2) | ip->get4(b, Omask) | ip->get4(b, Onexthop)){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t-- non-zero MBZ\n");
+ return;
+ }
+ mask = maskgen(dest);
+ }else if(version == 2){
+ if(ip->get4(b, Omask))
+ mask = IPaddr.newv4(b[Omask:]);
+ else
+ mask = maskgen(dest);
+ if(ip->get4(b, Onexthop))
+ gwa = IPaddr.newv4(b[Onexthop:]);
+ }
+ metric := ip->get4(b, Ometric);
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t%s %d\n", dest.text(), metric);
+ if(metric <= 0 || metric > HopLimit)
+ return;
+
+ # 1058/3.4.2: response processing
+ # ignore route if IP address is:
+ # class D or E
+ # net 0 (except perhaps 0.0.0.0)
+ # net 127
+ # broadcast address (all 1s host part)
+ # we allow host routes
+
+ if(dest.ismulticast() || dest.a[0] == byte 0 || dest.a[0] == byte 16r7F){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t%s %d invalid addr\n", dest.text(), metric);
+ return;
+ }
+ if(isbroadcast(dest, mask)){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t%s & %s -> broadcast\n", dest.text(), mask.masktext());
+ return;
+ }
+
+ # update the metric min(metric+NetworkCost, HopLimit)
+
+ metric += NetworkCost;
+ if(metric > HopLimit)
+ metric = HopLimit;
+
+ updateroute(dest, mask, gwa, metric);
+}
+
+updateroute(dest, mask, gwa: IPaddr, metric: int)
+{
+ # RFC1058 rules page 27-28, with optional replacement of expiring routes
+ r := lookup(dest);
+ if(r.valid){
+ if(r.local)
+ return; # local, don't touch
+ if(r.gateway.eq(gwa)){
+ if(metric != HopLimit){
+ r.metric = metric;
+ r.time = now;
+ }else{
+ # metric == HopLimit
+ if(r.metric != HopLimit){
+ r.metric = metric;
+ r.changed = 1;
+ r.time = now - (Discard-120);
+ delroute(r); # don't use it for routing
+ # route remains valid but advertised with metric HopLimit
+ } else if(now >= r.time+Discard){
+ delroute(r); # finally dead
+ r.valid = 0;
+ r.changed = 1;
+ }
+ }
+ }else if(metric < r.metric ||
+ metric != HopLimit && metric == r.metric && now > r.time+Expired/2){
+ delroute(r);
+ r.metric = metric;
+ r.gateway = gwa;
+ r.time = now;
+ addroute(r);
+ }
+ } else if(metric < HopLimit){ # new entry
+
+ # 1058/3.4.2: don't add route-to-host if host is on net/subnet
+ # for which we have at least as good a route
+
+ if(!mask.eq(ip->allbits) ||
+ ((pr := findroute(dest)) == nil || metric <= pr.metric)){
+ r.valid = 1;
+ r.changed = 1;
+ r.time = now;
+ r.metric = metric;
+ r.dest = dest;
+ r.mask = mask;
+ r.gateway = gwa;
+ addroute(r);
+ }
+ }
+}
+
+sender()
+{
+ for(;;){
+ sys->sleep(OutputRate*1000); # could add some random fizz
+ sendall(OpReply, 1);
+ }
+}
+
+onlist(a: IPaddr, l: list of IPaddr): int
+{
+ for(; l != nil; l = tl l)
+ if(a.eq(hd l))
+ return 1;
+ return 0;
+}
+
+sendall(op: int, changes: int)
+{
+ for(l := nets; l != nil; l = tl l){
+ if(addrs != nil && !onlist((hd l).net, addrs))
+ continue;
+ a := (hd l).net.copy();
+ b := (ip->allbits).maskn((hd l).mask);
+ for(i := 0; i < len a.a; i++)
+ a.a[i] |= b.a[i];
+ sendroutes(hd l, a, op, changes);
+ }
+ for(i := 0; i < len routes; i++)
+ if((r := routes[i]) != nil)
+ r.changed = 0;
+}
+
+zeroentry := array[Ipdestlen] of {* => byte 0};
+
+sendroutes(ifc: ref Ifcaddr, dst: IPaddr, op: int, changes: int)
+{
+ if(debug > 1)
+ sys->print("rip: send %s\n", dst.text());
+ buf := array[Maxripmsg+IP->Udphdrlen] of byte;
+ hdr := Udphdr.new();
+ hdr.lport = hdr.rport = RIPport;
+ hdr.raddr = dst; # needn't copy
+ hdr.pack(buf, IP->Udphdrlen);
+ o := IP->Udphdrlen;
+ buf[o] = byte op;
+ buf[o+1] = byte myversion;
+ buf[o+2] = byte 0;
+ buf[o+3] = byte 0;
+ o += Riphdrlen;
+ rips := buf[IP->Udphdrlen+Riphdrlen:];
+ if(op == OpRequest){
+ buf[o:] = zeroentry;
+ ip->put4(buf, o+Ometric, HopLimit);
+ o += Ipdestlen;
+ } else {
+ # send routes
+ for(i:=0; i<len routes; i++){
+ r := routes[i];
+ if(r == nil || !r.valid || changes && !r.changed)
+ continue;
+ if(r == defroute)
+ continue;
+ if(r.dest.eq(ifc.net) || isonnet(r.dest, ifc))
+ continue;
+ netmask := r.dest.classmask();
+ subnet := !r.mask.eq(netmask);
+ if(myversion < 2 && !r.mask.eq(ip->allbits)){
+ # if not a host route, don't let a subnet route leave its net
+ if(subnet && !netmask.eq(ifc.ip.classmask()))
+ continue;
+ }
+ if(o+Ipdestlen > IP->Udphdrlen+Maxripmsg){
+ if(sys->write(netfd, buf, o) < 0)
+ sys->fprint(sys->fildes(2), "RIP write failed: %r\n");
+ o = IP->Udphdrlen + Riphdrlen;
+ }
+ buf[o:] = zeroentry;
+ ip->put2(buf, o+Otype, AF_INET);
+ buf[o+Oaddr:] = r.dest.v4();
+ ip->put4(buf, o+Ometric, r.metric);
+ if(myversion == 2 && subnet)
+ buf[o+Omask:] = r.mask.v4();
+ o += Ipdestlen;
+ }
+ }
+ if(o > IP->Udphdrlen+Riphdrlen && sys->write(netfd, buf, o) < 0)
+ sys->fprint(sys->fildes(2), "rip: network write to %s failed: %r\n", dst.text());
+}
+
+lookup(addr: IPaddr): ref Gateway
+{
+ avail := -1;
+ for(i:=0; i<len routes; i++){
+ g := routes[i];
+ if(g == nil || !g.valid){
+ if(avail < 0)
+ avail = i;
+ continue;
+ }
+ if(g.dest.eq(addr))
+ return g;
+ }
+ if(avail < 0){
+ avail = len routes;
+ a := array[len routes+Routeinc] of ref Gateway;
+ a[0:] = routes;
+ routes = a;
+ }
+ if((g := routes[avail]) == nil){
+ g = ref Gateway;
+ routes[avail] = g;
+ g.valid = 0;
+ }
+ g.dest = addr;
+ return g;
+}
+
+findroute(a: IPaddr): ref Gateway
+{
+ pr: ref Gateway;
+ for(i:=0; i<len routes; i++){
+ r := routes[i];
+ if(r == nil || !r.valid)
+ continue;
+ if(r.contains(a) && (pr == nil || !maskle(r.mask, pr.mask)))
+ pr = r; # more specific mask
+ }
+ return pr;
+}
+
+maskgen(addr: IPaddr): IPaddr
+{
+ net: ref Ifcaddr;
+ for(l := nets; l != nil; l = tl l){
+ ifc := hd l;
+ if(isonnet(addr, ifc) &&
+ (net == nil || maskle(ifc.mask, net.mask))) # less specific mask?
+ net = ifc;
+ }
+ if(net != nil)
+ return net.mask;
+ return addr.classmask();
+}
+
+isonnet(a: IPaddr, n: ref Ifcaddr): int
+{
+ return a.mask(n.mask).eq(n.net);
+}
+
+isbroadcast(a: IPaddr, mask: IPaddr): int
+{
+ h := a.maskn(mask); # host part
+ hm := (ip->allbits).maskn(mask); # host part of mask
+ return h.eq(hm);
+}
+
+iszero(a: IPaddr): int
+{
+ return a.eq(ip->v4noaddr) || a.eq(ip->noaddr);
+}
+
+maskle(a, b: IPaddr): int
+{
+ return a.mask(b).eq(a);
+}
+
+#
+# add ipdest mask gateway
+# add 0.0.0.0 0.0.0.0 gateway (default)
+# delete ipdest mask
+#
+addroute(g: ref Gateway)
+{
+ if(iszero(g.mask) && iszero(g.dest))
+ g.valid = 0; # don't change default route
+ else if(defroute != nil && defroute.gateway.eq(g.gateway)){
+ if(debug)
+ syslog(0, logfile, sys->sprint("default %s %s", g.dest.text(), g.mask.text())); # don't need a new entry
+ g.valid = 1;
+ g.changed = 1;
+ } else {
+ if(debug)
+ syslog(0, logfile, sys->sprint("add %s %s %s", g.dest.text(), g.mask.text(), g.gateway.text()));
+ if(nochange || sys->fprint(routefd, "add %s %s %s", g.dest.text(), g.mask.text(), g.gateway.text()) > 0){
+ g.valid = 1;
+ g.changed = 1;
+ }
+ }
+}
+
+delroute(g: ref Gateway)
+{
+ if(debug)
+ syslog(0, logfile, sys->sprint("delete %s %s", g.dest.text(), g.mask.text()));
+ if(!nochange)
+ sys->fprint(routefd, "delete %s %s", g.dest.text(), g.mask.text());
+}
+
+parseip(s: string): IPaddr
+{
+ (ok, a) := IPaddr.parse(s);
+ if(ok < 0)
+ raise "bad route";
+ return a;
+}
+
+parsemask(s: string): IPaddr
+{
+ (ok, a) := IPaddr.parsemask(s);
+ if(ok < 0)
+ raise "bad route";
+ return a;
+}
+
+contains(s: string, t: string): int
+{
+ for(i := 0; i < len s; i++)
+ for(j := 0; j < len t; j++)
+ if(s[i] == t[j])
+ return 1;
+ return 0;
+}
+
+Gateway.contains(g: self ref Gateway, a: IPaddr): int
+{
+ return g.dest.eq(a.mask(g.mask));
+}
+
+riplisten(): ref Sys->FD
+{
+ addr := sys->sprint("%s/udp!*!rip", netdir);
+ (ok, c) := sys->announce(addr);
+ if(ok < 0)
+ fatal(sys->sprint("can't announce %s: %r", addr));
+ if(sys->fprint(c.cfd, "headers") < 0)
+ fatal(sys->sprint("can't set udp headers: %r"));
+ fd := sys->open(c.dir+"/data", Sys->ORDWR);
+ if(fd == nil)
+ fatal(sys->sprint("can't open %s: %r", c.dir+"/data"));
+ return fd;
+}
+
+fatal(s: string)
+{
+ syslog(0, logfile, s);
+ raise "fail:error";
+}