diff options
Diffstat (limited to 'appl/cmd/ip/bootpd.b')
| -rw-r--r-- | appl/cmd/ip/bootpd.b | 662 |
1 files changed, 662 insertions, 0 deletions
diff --git a/appl/cmd/ip/bootpd.b b/appl/cmd/ip/bootpd.b new file mode 100644 index 00000000..bf3313b2 --- /dev/null +++ b/appl/cmd/ip/bootpd.b @@ -0,0 +1,662 @@ +implement Bootpd; + +# +# to do: +# DHCP +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "attrdb.m"; + attrdb: Attrdb; + Attr, Db, Dbentry, Tuples: import attrdb; + +include "ip.m"; + ip: IP; + IPaddr, Udphdr: import ip; + +include "ipattr.m"; + ipattr: IPattr; + +include "ether.m"; + ether: Ether; + +include "arg.m"; + +Bootpd: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; +debug: int; +sniff: int; +verbose: int; + +siaddr: IPaddr; +netmask: IPaddr; +myname: string; +progname := "bootpd"; +net := "/net"; +ndb: ref Db; +ndbfile := "/lib/ndb/local"; +mtime := 0; +testing := 0; + +Udphdrsize: con IP->OUdphdrlen; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + loadfail(Bufio->PATH); + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + loadfail(Attrdb->PATH); + attrdb->init(); + ip = load IP IP->PATH; + if(ip == nil) + loadfail(IP->PATH); + ip->init(); + ipattr = load IPattr IPattr->PATH; + if(ipattr == nil) + loadfail(IPattr->PATH); + ipattr->init(attrdb, ip); + ether = load Ether Ether->PATH; + if(ether == nil) + loadfail(Ether->PATH); + ether->init(); + + verbose = 1; + sniff = 0; + debug = 0; + arg := load Arg Arg->PATH; + if(arg == nil) + raise "fail: load Arg"; + arg->init(args); + arg->setusage("bootpd [-dsqv] [-f file] [-x network]"); + progname = arg->progname(); + while((o := arg->opt()) != 0) + case o { + 'd' => debug++; + 's' => sniff = 1; debug = 255; + 'q' => verbose = 0; + 'v' => verbose = 1; + 'x' => net = arg->earg(); + 'f' => ndbfile = arg->earg(); + 't' => testing = 1; debug = 1; verbose = 1; + * => arg->usage(); + } + args = arg->argv(); + if(args != nil) + arg->usage(); + arg = nil; + + sys->pctl(Sys->FORKFD|Sys->FORKNS, nil); + + if(!sniff && (err := dbread()) != nil) + error(err); + + myname = sysname(); + if(myname == nil) + error("system name not set"); + (siaddr, err) = csquery(myname); + if(err != nil) + error(sys->sprint("can't find IP address for %s: %s", myname, err)); + if(debug) + sys->fprint(stderr, "bootpd: local IP address is %s\n", siaddr.text()); + + addr := net+"/udp!*!67"; + if(testing) + addr = net+"/udp!*!499"; + if(debug) + sys->fprint(stderr, "bootpd: announcing %s\n", addr); + (ok, c) := sys->announce(addr); + if(ok < 0) + error(sys->sprint("can't announce %s: %r", addr)); + if(sys->fprint(c.cfd, "headers") < 0) + error(sys->sprint("can't set headers mode: %r")); + sys->fprint(c.cfd, "oldheaders"); + + if(debug) + sys->fprint(stderr, "bootpd: opening %s/data\n", c.dir); + c.dfd = sys->open(c.dir+"/data", sys->ORDWR); + if(c.dfd == nil) + error(sys->sprint("can't open %s/data: %r", c.dir)); + + spawn server(c); +} + +loadfail(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(stderr, "bootpd: %s\n", s); + raise "fail:error"; +} + +server(c: Sys->Connection) +{ + buf := array[2048] of byte; + badread := 0; + for(;;) { + n := sys->read(c.dfd, buf, len buf); + if(n <0) { + if (badread++ > 10) + break; + continue; + } + badread = 0; + if(n < Udphdrsize) { + if(debug) + sys->fprint(stderr, "bootpd: short Udphdr: %d bytes\n", n); + continue; + } + hdr := Udphdr.unpack(buf, Udphdrsize); + if(debug) + sys->fprint(stderr, "bootpd: received request from udp!%s!%d\n", hdr.raddr.text(), hdr.rport); + if(n < Udphdrsize+300) { + if(debug) + sys->fprint(stderr, "bootpd: short request of %d bytes\n", n - Udphdrsize); + continue; + } + + (bootp, err) := Bootp.unpack(buf[Udphdrsize:]); + if(err != nil) { + if(debug) + sys->fprint(stderr, "bootpd: can't unpack packet: %s\n", err); + continue; + } + if(debug >= 2) + sys->fprint(stderr, "bootpd: recvd {%s}\n", bootp.text()); + if(sniff) + continue; + if(bootp.htype != 1 || bootp.hlen != 6) { + # if it isn't ether, we don't do it + if(debug) + sys->fprint(stderr, "bootpd: hardware type not ether; ignoring.\n"); + continue; + } + if((err = dbread()) != nil) { + sys->fprint(stderr, "bootpd: getreply: dbread failed: %s\n", err); + continue; + } + rec := lookup(bootp); + if(rec == nil) { + # we can't answer this request + if(debug) + sys->fprint(stderr, "bootpd: cannot answer request.\n"); + continue; + } + if(debug) + sys->fprint(stderr, "bootpd: found a matching entry: {%s}\n", rec.text()); + mkreply(bootp, rec); + if(verbose) + sys->print("bootpd: %s -> %s %s\n", ether->text(rec.ha), rec.hostname, rec.ip.text()); + if(debug) + sys->fprint(stderr, "bootpd: reply {%s}\n", bootp.text()); + repl := bootp.pack(); + if(!testing) + arpenter(rec.ip.text(), ether->text(rec.ha)); + send(hdr, repl); + } + sys->fprint(stderr, "bootpd: %d read errors: %r\n", badread); +} + +arpenter(ip, ha: string) +{ + if(debug) + sys->fprint(stderr, "bootpd: arp: %s -> %s\n", ip, ha); + fd := sys->open(net+"/arp", Sys->OWRITE); + if(fd == nil) { + if(debug) + sys->fprint(stderr, "bootpd: arp open failed: %r\n"); + return; + } + if(sys->fprint(fd, "add %s %s", ip, ha) < 0){ + if(debug) + sys->fprint(stderr, "bootpd: error writing arp: %r\n"); + } +} + +sysname(): string +{ + t := rf("/dev/sysname"); + if(t != nil) + return t; + return rf("#e/sysname"); +} + +rf(name: string): string +{ + fd := sys->open(name, Sys->OREAD); + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + return nil; + return string buf[0:n]; +} + +csquery(name: string): (IPaddr, string) +{ + siaddr = ip->noaddr; + # get a local IP address by translating our sysname with cs(8) + csfile := net+"/cs"; + fd := sys->open(net+"/cs", Sys->ORDWR); + if(fd == nil) + return (ip->noaddr, sys->sprint("can't open %s/cs: %r", csfile)); + if(sys->fprint(fd, "net!%s!0", name) < 0) + return (ip->noaddr, sys->sprint("can't translate net!%s!0: %r", name)); + sys->seek(fd, big 0, 0); + a := array[1024] of byte; + n := sys->read(fd, a, len a); + if(n <= 0) + return (ip->noaddr, "no result from "+csfile); + reply := string a[0:n]; + (l, addr):= sys->tokenize(reply, " "); + if(l != 2) + return (ip->noaddr, "bad cs reply format"); + (l, addr) = sys->tokenize(hd tl addr, "!"); + if(l < 2) + return (ip->noaddr, "bad cs reply format"); + (ok, ipa) := IPaddr.parse(hd addr); + if(ok < 0 || !ipok(siaddr)) + return (ip->noaddr, "can't parse address: "+hd addr); + return (ipa, nil); +} + +Hostinfo: adt { + hostname: string; + + ha: array of byte; # hardware addr + ip: IPaddr; # client IP addr + bootf: string; # boot file path + netmask: IPaddr; # subnet mask + ipgw: IPaddr; # gateway IP addr + fs: IPaddr; # file server IP addr + auth: IPaddr; # authentication server IP addr + + text: fn(inf: self ref Hostinfo): string; +}; + +send(hdr: ref Udphdr, msg: array of byte) +{ + replyaddr := net+"/udp!255.255.255.255!68"; # TO DO: gateway + if(testing) + replyaddr = sys->sprint("udp!%s!%d", hdr.raddr.text(), hdr.rport); + lport := "67"; + if(testing) + lport = "499"; + (n, c) := sys->dial(replyaddr, lport); + if(n < 0) { + sys->fprint(stderr, "bootpd: can't dial %s for reply: %r\n", replyaddr); + return; + } + n = sys->write(c.dfd, msg, len msg); + if(n != len msg) + sys->fprint(stderr, "bootpd: udp write error: %r\n"); +} + +mkreply(bootp: ref Bootp, rec: ref Hostinfo) +{ + bootp.op = 2; # boot reply + bootp.yiaddr = rec.ip; + bootp.siaddr = siaddr; + bootp.giaddr = ip->noaddr; + bootp.sname = myname; + bootp.file = string rec.bootf; + bootp.vend = array of byte sys->sprint("p9 %s %s %s %s", rec.netmask.text(), rec.fs.text(), rec.auth.text(), rec.ipgw.text()); +} + +dbread(): string +{ + if(ndb == nil){ + ndb = Db.open(ndbfile); + if(ndb == nil) + return sys->sprint("cannot open %s: %r", ndbfile); + }else if(ndb.changed()) + ndb.reopen(); + return nil; +} + +ipok(a: IPaddr): int +{ + return a.isv4() && !(a.eq(ip->v4noaddr) || a.eq(ip->noaddr) || a.ismulticast()); +} + +lookup(bootp: ref Bootp): ref Hostinfo +{ + if(ndb == nil) + return nil; + inf: ref Hostinfo; + hwaddr := ether->text(bootp.chaddr); + if(ipok(bootp.ciaddr)){ + # client thinks it knows address; check match with MAC address + ipaddr := bootp.ciaddr.text(); + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = ndb.findbyattr(ptr, "ip", ipaddr, "ether"); + if(e == nil) + break; + # TO DO: check result + inf = matchandfill(e, "ip", ipaddr, "ether", hwaddr); + if(inf != nil) + return inf; + } + } + # look up an ip address associated with given MAC address + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = ndb.findbyattr(ptr, "ether", hwaddr, "ip"); + if(e == nil) + break; + # TO DO: check right net etc. + inf = matchandfill(e, "ether", hwaddr, "ip", nil); + if(inf != nil) + return inf; + } + return nil; +} + +matchandfill(e: ref Dbentry, attr: string, val: string, rattr: string, rval: string): ref Hostinfo +{ + matches := e.findbyattr(attr, val, rattr); + for(; matches != nil; matches = tl matches){ + (line, attrs) := hd matches; + for(; attrs != nil; attrs = tl attrs){ + if(rval == nil || (hd attrs).val == rval){ + inf := fillup(line, e); + if(inf != nil) + return inf; + break; + } + } + } + return nil; +} + +fillup(line: ref Tuples, e: ref Dbentry): ref Hostinfo +{ + ok: int; + inf := ref Hostinfo; + inf.netmask = ip->noaddr; + inf.ipgw = ip->noaddr; + inf.fs = ip->v4noaddr; + inf.auth = ip->v4noaddr; + inf.hostname = find(line, e, "sys"); + s := find(line, e, "ether"); + if(s != nil) + inf.ha = ether->parse(s); + s = find(line, e, "ip"); + if(s == nil) + return nil; + (ok, inf.ip) = IPaddr.parse(s); + if(ok < 0) + return nil; + (results, err) := ipattr->findnetattrs(ndb, "ip", s, list of{"ipmask", "ipgw", "fs", "FILESERVER", "SIGNER", "auth", "bootf"}); + if(err != nil) + return nil; + for(; results != nil; results = tl results){ + (a, nattrs) := hd results; + if(!a.eq(inf.ip)) + continue; # different network + for(; nattrs != nil; nattrs = tl nattrs){ + na := hd nattrs; + case na.name { + "ipmask" => + inf.netmask = takeipmask(na.pairs, inf.netmask); + "ipgw" => + inf.ipgw = takeipattr(na.pairs, inf.ipgw); + "fs" or "FILESERVER" => + inf.fs = takeipattr(na.pairs, inf.fs); + "auth" or "SIGNER" => + inf.auth = takeipattr(na.pairs, inf.auth); + "bootf" => + inf.bootf = takeattr(na.pairs, inf.bootf); + } + } + } + return inf; +} + +takeattr(pairs: list of ref Attr, s: string): string +{ + if(s != nil || pairs == nil) + return s; + return (hd pairs).val; +} + +takeipattr(pairs: list of ref Attr, a: IPaddr): IPaddr +{ + if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr))) + return a; + (ok, na) := parseip((hd pairs).val); + if(ok < 0) + return a; + return na; +} + +takeipmask(pairs: list of ref Attr, a: IPaddr): IPaddr +{ + if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr))) + return a; + (ok, na) := IPaddr.parsemask((hd pairs).val); + if(ok < 0) + return a; + return na; +} + +findip(line: ref Tuples, e: ref Dbentry, attr: string): (int, IPaddr) +{ + s := find(line, e, attr); + if(s == nil) + return (-1, ip->noaddr); + return parseip(s); +} + +parseip(s: string): (int, IPaddr) +{ + (ok, a) := IPaddr.parse(s); + if(ok < 0){ + # look it up if it's a system name + s = findbyattr("sys", s, "ip"); + (ok, a) = IPaddr.parse(s); + } + return (ok, a); +} + +find(line: ref Tuples, e: ref Dbentry, attr: string): string +{ + if(line != nil){ + a := line.find(attr); + if(a != nil) + return (hd a).val; + } + if(e != nil){ + for(matches := e.find(attr); matches != nil; matches = tl matches){ + (nil, a) := hd matches; + if(a != nil) + return (hd a).val; + } + } + return nil; +} + +findbyattr(attr: string, val: string, rattr: string): string +{ + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = ndb.findbyattr(ptr, attr, val, rattr); + if(e == nil) + break; + rvl := e.find(rattr); + if(rvl != nil){ + (nil, al) := hd rvl; + return (hd al).val; + } + } + return nil; +} + +missing(rec: ref Hostinfo): string +{ + s := ""; + if(rec.ha == nil) + s += " hardware address"; + if(rec.ip.eq(ip->noaddr)) + s += " IP address"; + if(rec.bootf == nil) + s += " bootfile"; + if(rec.netmask.eq(ip->noaddr)) + s += " subnet mask"; + if(rec.ipgw.eq(ip->noaddr)) + s += " gateway"; + if(rec.fs.eq(ip->noaddr)) + s += " file server"; + if(rec.auth.eq(ip->noaddr)) + s += " authentication server"; + if(s != "") + return s[1:]; + return nil; +} + +dtoa(data: array of byte): string +{ + if(data == nil) + return nil; + result: string; + for(i:=0; i < len data; i++) + result += sys->sprint(".%d", int data[i]); + return result[1:]; +} + +magic(cookie: array of byte): string +{ + if(eqa(cookie, array[] of { byte 'p', byte '9', byte ' ', byte ' ' })) + return "plan9"; + if(eqa(cookie, array[] of { byte 99, byte 130, byte 83, byte 99 })) + return "rfc1048"; + if(eqa(cookie, array[] of { byte 'C', byte 'M', byte 'U', byte 0 })) + return "cmu"; + return dtoa(cookie); +} + +eqa(a1: array of byte, a2: array of byte): int +{ + if(len a1 != len a2) + return 0; + for(i := 0; i < len a1; i++) + if(a1[i] != a2[i]) + return 0; + return 1; +} + +Hostinfo.text(rec: self ref Hostinfo): string +{ + return sys->sprint("ha=%s ip=%s bf=%s sm=%s gw=%s fs=%s au=%s", + ether->text(rec.ha), rec.ip.text(), rec.bootf, rec.netmask.masktext(), rec.ipgw.text(), rec.fs.text(), rec.auth.text()); +} + +Bootp: adt +{ + op: int; # opcode [1] + htype: int; # hardware type[1] + hlen: int; # hardware address length [1] + hops: int; # gateway hops [1] + xid: int; # random number [4] + secs: int; # seconds elapsed since client started booting [2] + flags: int; # flags[2] + ciaddr: IPaddr; # client ip address (client->server)[4] + yiaddr: IPaddr; # your ip address (server->client)[4] + siaddr: IPaddr; # server's ip address [4] + giaddr: IPaddr; # gateway ip address [4] + chaddr: array of byte; # client hardware (mac) address [16] + sname: string; # server host name [64] + file: string; # boot file name [128] + vend: array of byte; # vendor-specific [128] + + unpack: fn(a: array of byte): (ref Bootp, string); + pack: fn(bp: self ref Bootp): array of byte; + text: fn(bp: self ref Bootp): string; +}; + +Bootp.unpack(data: array of byte): (ref Bootp, string) +{ + if(len data < 300) + return (nil, "too short"); + + bp := ref Bootp; + bp.op = int data[0]; + bp.htype = int data[1]; + bp.hlen = int data[2]; + if(bp.hlen > 16) + return (nil, "length error"); + bp.hops = int data[3]; + bp.xid = ip->get4(data, 4); + bp.secs = ip->get2(data, 8); + bp.flags = ip->get2(data, 10); + bp.ciaddr = IPaddr.newv4(data[12:16]); + bp.yiaddr = IPaddr.newv4(data[16:20]); + bp.siaddr = IPaddr.newv4(data[20:24]); + bp.giaddr = IPaddr.newv4(data[24:28]); + bp.chaddr = data[28:28+bp.hlen]; + bp.sname = ctostr(data[44:108]); + bp.file = ctostr(data[108:236]); + bp.vend = data[236:300]; + return (bp, nil); +} + +Bootp.pack(bp: self ref Bootp): array of byte +{ + data := array[364] of { * => byte 0 }; + data[0] = byte bp.op; + data[1] = byte bp.htype; + data[2] = byte bp.hlen; + data[3] = byte bp.hops; + ip->put4(data, 4, bp.xid); + ip->put2(data, 8, bp.secs); + ip->put2(data, 10, bp.flags); + data[12:] = bp.ciaddr.v4(); + data[16:] = bp.yiaddr.v4(); + data[20:] = bp.siaddr.v4(); + data[24:] = bp.giaddr.v4(); + data[28:] = bp.chaddr; + data[44:] = array of byte bp.sname; + data[108:] = array of byte bp.file; + data[236:] = bp.vend; + return data; +} + +ctostr(cstr: array of byte): string +{ + for(i:=0; i<len cstr; i++) + if(cstr[i] == byte 0) + break; + return string cstr[0:i]; +} + +Bootp.text(bp: self ref Bootp): string +{ + s := sys->sprint("op=%d htype=%d hlen=%d hops=%d xid=%ud secs=%ud ciaddr=%s yiaddr=%s", + int bp.op, bp.htype, bp.hlen, bp.hops, bp.xid, bp.secs, bp.ciaddr.text(), bp.yiaddr.text()); + s += sys->sprint(" server=%s gateway=%s hwaddr=%q host=%q file=%q magic=%q", + bp.siaddr.text(), bp.giaddr.text(), ether->text(bp.chaddr), bp.sname, bp.file, magic(bp.vend[0:4])); + if(magic(bp.vend[0:4]) == "plan9") + s += "("+ctostr(bp.vend)+")"; + return s; +} |
