summaryrefslogtreecommitdiff
path: root/appl/cmd/ip/bootpd.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/ip/bootpd.b')
-rw-r--r--appl/cmd/ip/bootpd.b662
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;
+}