1implement Bootpd; 2 3# 4# to do: 5# DHCP 6# 7 8include "sys.m"; 9 sys: Sys; 10 11include "draw.m"; 12 13include "bufio.m"; 14 bufio: Bufio; 15 Iobuf: import bufio; 16 17include "attrdb.m"; 18 attrdb: Attrdb; 19 Attr, Db, Dbentry, Tuples: import attrdb; 20 21include "dial.m"; 22 dial: Dial; 23 24include "ip.m"; 25 ip: IP; 26 IPaddr, Udphdr: import ip; 27 28include "ipattr.m"; 29 ipattr: IPattr; 30 31include "ether.m"; 32 ether: Ether; 33 34include "arg.m"; 35 36Bootpd: module 37{ 38 init: fn(nil: ref Draw->Context, argv: list of string); 39}; 40 41stderr: ref Sys->FD; 42debug: int; 43sniff: int; 44verbose: int; 45 46siaddr: IPaddr; 47netmask: IPaddr; 48myname: string; 49progname := "bootpd"; 50net := "/net"; 51ndb: ref Db; 52ndbfile := "/lib/ndb/local"; 53mtime := 0; 54testing := 0; 55 56Udphdrsize: con IP->Udphdrlen; 57 58init(nil: ref Draw->Context, args: list of string) 59{ 60 sys = load Sys Sys->PATH; 61 stderr = sys->fildes(2); 62 bufio = load Bufio Bufio->PATH; 63 if(bufio == nil) 64 loadfail(Bufio->PATH); 65 attrdb = load Attrdb Attrdb->PATH; 66 if(attrdb == nil) 67 loadfail(Attrdb->PATH); 68 attrdb->init(); 69 dial = load Dial Dial->PATH; 70 if(dial == nil) 71 loadfail(Dial->PATH); 72 ip = load IP IP->PATH; 73 if(ip == nil) 74 loadfail(IP->PATH); 75 ip->init(); 76 ipattr = load IPattr IPattr->PATH; 77 if(ipattr == nil) 78 loadfail(IPattr->PATH); 79 ipattr->init(attrdb, ip); 80 ether = load Ether Ether->PATH; 81 if(ether == nil) 82 loadfail(Ether->PATH); 83 ether->init(); 84 85 verbose = 1; 86 sniff = 0; 87 debug = 0; 88 arg := load Arg Arg->PATH; 89 if(arg == nil) 90 raise "fail: load Arg"; 91 arg->init(args); 92 arg->setusage("bootpd [-dsqv] [-f file] [-x network]"); 93 progname = arg->progname(); 94 while((o := arg->opt()) != 0) 95 case o { 96 'd' => debug++; 97 's' => sniff = 1; debug = 255; 98 'q' => verbose = 0; 99 'v' => verbose = 1; 100 'x' => net = arg->earg(); 101 'f' => ndbfile = arg->earg(); 102 't' => testing = 1; debug = 1; verbose = 1; 103 * => arg->usage(); 104 } 105 args = arg->argv(); 106 if(args != nil) 107 arg->usage(); 108 arg = nil; 109 110 sys->pctl(Sys->FORKFD|Sys->FORKNS, nil); 111 112 if(!sniff && (err := dbread()) != nil) 113 error(err); 114 115 myname = sysname(); 116 if(myname == nil) 117 error("system name not set"); 118 (siaddr, err) = csquery(myname); 119 if(err != nil) 120 error(sys->sprint("can't find IP address for %s: %s", myname, err)); 121 if(debug) 122 sys->fprint(stderr, "bootpd: local IP address is %s\n", siaddr.text()); 123 124 addr := net+"/udp!*!67"; 125 if(testing) 126 addr = net+"/udp!*!499"; 127 if(debug) 128 sys->fprint(stderr, "bootpd: announcing %s\n", addr); 129 c := dial->announce(addr); 130 if(c == nil) 131 error(sys->sprint("can't announce %s: %r", addr)); 132 if(sys->fprint(c.cfd, "headers") < 0) 133 error(sys->sprint("can't set headers mode: %r")); 134 135 if(debug) 136 sys->fprint(stderr, "bootpd: opening %s/data\n", c.dir); 137 c.dfd = sys->open(c.dir+"/data", sys->ORDWR); 138 if(c.dfd == nil) 139 error(sys->sprint("can't open %s/data: %r", c.dir)); 140 141 spawn server(c); 142} 143 144loadfail(s: string) 145{ 146 error(sys->sprint("can't load %s: %r", s)); 147} 148 149error(s: string) 150{ 151 sys->fprint(stderr, "bootpd: %s\n", s); 152 raise "fail:error"; 153} 154 155server(c: ref Sys->Connection) 156{ 157 buf := array[2048] of byte; 158 badread := 0; 159 for(;;) { 160 n := sys->read(c.dfd, buf, len buf); 161 if(n <0) { 162 if (badread++ > 10) 163 break; 164 continue; 165 } 166 badread = 0; 167 if(n < Udphdrsize) { 168 if(debug) 169 sys->fprint(stderr, "bootpd: short Udphdr: %d bytes\n", n); 170 continue; 171 } 172 hdr := Udphdr.unpack(buf, Udphdrsize); 173 if(debug) 174 sys->fprint(stderr, "bootpd: received request from udp!%s!%d\n", hdr.raddr.text(), hdr.rport); 175 if(n < Udphdrsize+300) { 176 if(debug) 177 sys->fprint(stderr, "bootpd: short request of %d bytes\n", n - Udphdrsize); 178 continue; 179 } 180 181 (bootp, err) := Bootp.unpack(buf[Udphdrsize:]); 182 if(err != nil) { 183 if(debug) 184 sys->fprint(stderr, "bootpd: can't unpack packet: %s\n", err); 185 continue; 186 } 187 if(debug >= 2) 188 sys->fprint(stderr, "bootpd: recvd {%s}\n", bootp.text()); 189 if(sniff) 190 continue; 191 if(bootp.htype != 1 || bootp.hlen != 6) { 192 # if it isn't ether, we don't do it 193 if(debug) 194 sys->fprint(stderr, "bootpd: hardware type not ether; ignoring.\n"); 195 continue; 196 } 197 if((err = dbread()) != nil) { 198 sys->fprint(stderr, "bootpd: getreply: dbread failed: %s\n", err); 199 continue; 200 } 201 rec := lookup(bootp); 202 if(rec == nil) { 203 # we can't answer this request 204 if(debug) 205 sys->fprint(stderr, "bootpd: cannot answer request.\n"); 206 continue; 207 } 208 if(debug) 209 sys->fprint(stderr, "bootpd: found a matching entry: {%s}\n", rec.text()); 210 mkreply(bootp, rec); 211 if(verbose) 212 sys->print("bootpd: %s -> %s %s\n", ether->text(rec.ha), rec.hostname, rec.ip.text()); 213 if(debug) 214 sys->fprint(stderr, "bootpd: reply {%s}\n", bootp.text()); 215 repl := bootp.pack(); 216 if(!testing) 217 arpenter(rec.ip.text(), ether->text(rec.ha)); 218 send(hdr, repl); 219 } 220 sys->fprint(stderr, "bootpd: %d read errors: %r\n", badread); 221} 222 223arpenter(ip, ha: string) 224{ 225 if(debug) 226 sys->fprint(stderr, "bootpd: arp: %s -> %s\n", ip, ha); 227 fd := sys->open(net+"/arp", Sys->OWRITE); 228 if(fd == nil) { 229 if(debug) 230 sys->fprint(stderr, "bootpd: arp open failed: %r\n"); 231 return; 232 } 233 if(sys->fprint(fd, "add %s %s", ip, ha) < 0){ 234 if(debug) 235 sys->fprint(stderr, "bootpd: error writing arp: %r\n"); 236 } 237} 238 239sysname(): string 240{ 241 t := rf("/dev/sysname"); 242 if(t != nil) 243 return t; 244 return rf("#e/sysname"); 245} 246 247rf(name: string): string 248{ 249 fd := sys->open(name, Sys->OREAD); 250 buf := array[Sys->NAMEMAX] of byte; 251 n := sys->read(fd, buf, len buf); 252 if(n <= 0) 253 return nil; 254 return string buf[0:n]; 255} 256 257csquery(name: string): (IPaddr, string) 258{ 259 siaddr = ip->noaddr; 260 # get a local IP address by translating our sysname with cs(8) 261 csfile := net+"/cs"; 262 fd := sys->open(net+"/cs", Sys->ORDWR); 263 if(fd == nil) 264 return (ip->noaddr, sys->sprint("can't open %s/cs: %r", csfile)); 265 if(sys->fprint(fd, "net!%s!0", name) < 0) 266 return (ip->noaddr, sys->sprint("can't translate net!%s!0: %r", name)); 267 sys->seek(fd, big 0, 0); 268 a := array[1024] of byte; 269 n := sys->read(fd, a, len a); 270 if(n <= 0) 271 return (ip->noaddr, "no result from "+csfile); 272 reply := string a[0:n]; 273 (l, addr):= sys->tokenize(reply, " "); 274 if(l != 2) 275 return (ip->noaddr, "bad cs reply format"); 276 (l, addr) = sys->tokenize(hd tl addr, "!"); 277 if(l < 2) 278 return (ip->noaddr, "bad cs reply format"); 279 (ok, ipa) := IPaddr.parse(hd addr); 280 if(ok < 0 || !ipok(siaddr)) 281 return (ip->noaddr, "can't parse address: "+hd addr); 282 return (ipa, nil); 283} 284 285Hostinfo: adt { 286 hostname: string; 287 288 ha: array of byte; # hardware addr 289 ip: IPaddr; # client IP addr 290 bootf: string; # boot file path 291 netmask: IPaddr; # subnet mask 292 ipgw: IPaddr; # gateway IP addr 293 fs: IPaddr; # file server IP addr 294 auth: IPaddr; # authentication server IP addr 295 296 text: fn(inf: self ref Hostinfo): string; 297}; 298 299send(hdr: ref Udphdr, msg: array of byte) 300{ 301 replyaddr := net+"/udp!255.255.255.255!68"; # TO DO: gateway 302 if(testing) 303 replyaddr = sys->sprint("udp!%s!%d", hdr.raddr.text(), hdr.rport); 304 lport := "67"; 305 if(testing) 306 lport = "499"; 307 c := dial->dial(replyaddr, lport); 308 if(c == nil) { 309 sys->fprint(stderr, "bootpd: can't dial %s for reply: %r\n", replyaddr); 310 return; 311 } 312 n := sys->write(c.dfd, msg, len msg); 313 if(n != len msg) 314 sys->fprint(stderr, "bootpd: udp write error: %r\n"); 315} 316 317mkreply(bootp: ref Bootp, rec: ref Hostinfo) 318{ 319 bootp.op = 2; # boot reply 320 bootp.yiaddr = rec.ip; 321 bootp.siaddr = siaddr; 322 bootp.giaddr = ip->noaddr; 323 bootp.sname = myname; 324 bootp.file = string rec.bootf; 325 bootp.vend = array of byte sys->sprint("p9 %s %s %s %s", rec.netmask.text(), rec.fs.text(), rec.auth.text(), rec.ipgw.text()); 326} 327 328dbread(): string 329{ 330 if(ndb == nil){ 331 ndb = Db.open(ndbfile); 332 if(ndb == nil) 333 return sys->sprint("cannot open %s: %r", ndbfile); 334 }else if(ndb.changed()) 335 ndb.reopen(); 336 return nil; 337} 338 339ipok(a: IPaddr): int 340{ 341 return a.isv4() && !(a.eq(ip->v4noaddr) || a.eq(ip->noaddr) || a.ismulticast()); 342} 343 344lookup(bootp: ref Bootp): ref Hostinfo 345{ 346 if(ndb == nil) 347 return nil; 348 inf: ref Hostinfo; 349 hwaddr := ether->text(bootp.chaddr); 350 if(ipok(bootp.ciaddr)){ 351 # client thinks it knows address; check match with MAC address 352 ipaddr := bootp.ciaddr.text(); 353 ptr: ref Attrdb->Dbptr; 354 for(;;){ 355 e: ref Dbentry; 356 (e, ptr) = ndb.findbyattr(ptr, "ip", ipaddr, "ether"); 357 if(e == nil) 358 break; 359 # TO DO: check result 360 inf = matchandfill(e, "ip", ipaddr, "ether", hwaddr); 361 if(inf != nil) 362 return inf; 363 } 364 } 365 # look up an ip address associated with given MAC address 366 ptr: ref Attrdb->Dbptr; 367 for(;;){ 368 e: ref Dbentry; 369 (e, ptr) = ndb.findbyattr(ptr, "ether", hwaddr, "ip"); 370 if(e == nil) 371 break; 372 # TO DO: check right net etc. 373 inf = matchandfill(e, "ether", hwaddr, "ip", nil); 374 if(inf != nil) 375 return inf; 376 } 377 return nil; 378} 379 380matchandfill(e: ref Dbentry, attr: string, val: string, rattr: string, rval: string): ref Hostinfo 381{ 382 matches := e.findbyattr(attr, val, rattr); 383 for(; matches != nil; matches = tl matches){ 384 (line, attrs) := hd matches; 385 for(; attrs != nil; attrs = tl attrs){ 386 if(rval == nil || (hd attrs).val == rval){ 387 inf := fillup(line, e); 388 if(inf != nil) 389 return inf; 390 break; 391 } 392 } 393 } 394 return nil; 395} 396 397fillup(line: ref Tuples, e: ref Dbentry): ref Hostinfo 398{ 399 ok: int; 400 inf := ref Hostinfo; 401 inf.netmask = ip->noaddr; 402 inf.ipgw = ip->noaddr; 403 inf.fs = ip->v4noaddr; 404 inf.auth = ip->v4noaddr; 405 inf.hostname = find(line, e, "sys"); 406 s := find(line, e, "ether"); 407 if(s != nil) 408 inf.ha = ether->parse(s); 409 s = find(line, e, "ip"); 410 if(s == nil) 411 return nil; 412 (ok, inf.ip) = IPaddr.parse(s); 413 if(ok < 0) 414 return nil; 415 (results, err) := ipattr->findnetattrs(ndb, "ip", s, list of{"ipmask", "ipgw", "fs", "FILESERVER", "SIGNER", "auth", "bootf"}); 416 if(err != nil) 417 return nil; 418 for(; results != nil; results = tl results){ 419 (a, nattrs) := hd results; 420 if(!a.eq(inf.ip)) 421 continue; # different network 422 for(; nattrs != nil; nattrs = tl nattrs){ 423 na := hd nattrs; 424 case na.name { 425 "ipmask" => 426 inf.netmask = takeipmask(na.pairs, inf.netmask); 427 "ipgw" => 428 inf.ipgw = takeipattr(na.pairs, inf.ipgw); 429 "fs" or "FILESERVER" => 430 inf.fs = takeipattr(na.pairs, inf.fs); 431 "auth" or "SIGNER" => 432 inf.auth = takeipattr(na.pairs, inf.auth); 433 "bootf" => 434 inf.bootf = takeattr(na.pairs, inf.bootf); 435 } 436 } 437 } 438 return inf; 439} 440 441takeattr(pairs: list of ref Attr, s: string): string 442{ 443 if(s != nil || pairs == nil) 444 return s; 445 return (hd pairs).val; 446} 447 448takeipattr(pairs: list of ref Attr, a: IPaddr): IPaddr 449{ 450 if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr))) 451 return a; 452 (ok, na) := parseip((hd pairs).val); 453 if(ok < 0) 454 return a; 455 return na; 456} 457 458takeipmask(pairs: list of ref Attr, a: IPaddr): IPaddr 459{ 460 if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr))) 461 return a; 462 (ok, na) := IPaddr.parsemask((hd pairs).val); 463 if(ok < 0) 464 return a; 465 return na; 466} 467 468findip(line: ref Tuples, e: ref Dbentry, attr: string): (int, IPaddr) 469{ 470 s := find(line, e, attr); 471 if(s == nil) 472 return (-1, ip->noaddr); 473 return parseip(s); 474} 475 476parseip(s: string): (int, IPaddr) 477{ 478 (ok, a) := IPaddr.parse(s); 479 if(ok < 0){ 480 # look it up if it's a system name 481 s = findbyattr("sys", s, "ip"); 482 (ok, a) = IPaddr.parse(s); 483 } 484 return (ok, a); 485} 486 487find(line: ref Tuples, e: ref Dbentry, attr: string): string 488{ 489 if(line != nil){ 490 a := line.find(attr); 491 if(a != nil) 492 return (hd a).val; 493 } 494 if(e != nil){ 495 for(matches := e.find(attr); matches != nil; matches = tl matches){ 496 (nil, a) := hd matches; 497 if(a != nil) 498 return (hd a).val; 499 } 500 } 501 return nil; 502} 503 504findbyattr(attr: string, val: string, rattr: string): string 505{ 506 ptr: ref Attrdb->Dbptr; 507 for(;;){ 508 e: ref Dbentry; 509 (e, ptr) = ndb.findbyattr(ptr, attr, val, rattr); 510 if(e == nil) 511 break; 512 rvl := e.find(rattr); 513 if(rvl != nil){ 514 (nil, al) := hd rvl; 515 return (hd al).val; 516 } 517 } 518 return nil; 519} 520 521missing(rec: ref Hostinfo): string 522{ 523 s := ""; 524 if(rec.ha == nil) 525 s += " hardware address"; 526 if(rec.ip.eq(ip->noaddr)) 527 s += " IP address"; 528 if(rec.bootf == nil) 529 s += " bootfile"; 530 if(rec.netmask.eq(ip->noaddr)) 531 s += " subnet mask"; 532 if(rec.ipgw.eq(ip->noaddr)) 533 s += " gateway"; 534 if(rec.fs.eq(ip->noaddr)) 535 s += " file server"; 536 if(rec.auth.eq(ip->noaddr)) 537 s += " authentication server"; 538 if(s != "") 539 return s[1:]; 540 return nil; 541} 542 543dtoa(data: array of byte): string 544{ 545 if(data == nil) 546 return nil; 547 result: string; 548 for(i:=0; i < len data; i++) 549 result += sys->sprint(".%d", int data[i]); 550 return result[1:]; 551} 552 553magic(cookie: array of byte): string 554{ 555 if(eqa(cookie, array[] of { byte 'p', byte '9', byte ' ', byte ' ' })) 556 return "plan9"; 557 if(eqa(cookie, array[] of { byte 99, byte 130, byte 83, byte 99 })) 558 return "rfc1048"; 559 if(eqa(cookie, array[] of { byte 'C', byte 'M', byte 'U', byte 0 })) 560 return "cmu"; 561 return dtoa(cookie); 562} 563 564eqa(a1: array of byte, a2: array of byte): int 565{ 566 if(len a1 != len a2) 567 return 0; 568 for(i := 0; i < len a1; i++) 569 if(a1[i] != a2[i]) 570 return 0; 571 return 1; 572} 573 574Hostinfo.text(rec: self ref Hostinfo): string 575{ 576 return sys->sprint("ha=%s ip=%s bf=%s sm=%s gw=%s fs=%s au=%s", 577 ether->text(rec.ha), rec.ip.text(), rec.bootf, rec.netmask.masktext(), rec.ipgw.text(), rec.fs.text(), rec.auth.text()); 578} 579 580Bootp: adt 581{ 582 op: int; # opcode [1] 583 htype: int; # hardware type[1] 584 hlen: int; # hardware address length [1] 585 hops: int; # gateway hops [1] 586 xid: int; # random number [4] 587 secs: int; # seconds elapsed since client started booting [2] 588 flags: int; # flags[2] 589 ciaddr: IPaddr; # client ip address (client->server)[4] 590 yiaddr: IPaddr; # your ip address (server->client)[4] 591 siaddr: IPaddr; # server's ip address [4] 592 giaddr: IPaddr; # gateway ip address [4] 593 chaddr: array of byte; # client hardware (mac) address [16] 594 sname: string; # server host name [64] 595 file: string; # boot file name [128] 596 vend: array of byte; # vendor-specific [128] 597 598 unpack: fn(a: array of byte): (ref Bootp, string); 599 pack: fn(bp: self ref Bootp): array of byte; 600 text: fn(bp: self ref Bootp): string; 601}; 602 603Bootp.unpack(data: array of byte): (ref Bootp, string) 604{ 605 if(len data < 300) 606 return (nil, "too short"); 607 608 bp := ref Bootp; 609 bp.op = int data[0]; 610 bp.htype = int data[1]; 611 bp.hlen = int data[2]; 612 if(bp.hlen > 16) 613 return (nil, "length error"); 614 bp.hops = int data[3]; 615 bp.xid = ip->get4(data, 4); 616 bp.secs = ip->get2(data, 8); 617 bp.flags = ip->get2(data, 10); 618 bp.ciaddr = IPaddr.newv4(data[12:16]); 619 bp.yiaddr = IPaddr.newv4(data[16:20]); 620 bp.siaddr = IPaddr.newv4(data[20:24]); 621 bp.giaddr = IPaddr.newv4(data[24:28]); 622 bp.chaddr = data[28:28+bp.hlen]; 623 bp.sname = ctostr(data[44:108]); 624 bp.file = ctostr(data[108:236]); 625 bp.vend = data[236:300]; 626 return (bp, nil); 627} 628 629Bootp.pack(bp: self ref Bootp): array of byte 630{ 631 data := array[364] of { * => byte 0 }; 632 data[0] = byte bp.op; 633 data[1] = byte bp.htype; 634 data[2] = byte bp.hlen; 635 data[3] = byte bp.hops; 636 ip->put4(data, 4, bp.xid); 637 ip->put2(data, 8, bp.secs); 638 ip->put2(data, 10, bp.flags); 639 data[12:] = bp.ciaddr.v4(); 640 data[16:] = bp.yiaddr.v4(); 641 data[20:] = bp.siaddr.v4(); 642 data[24:] = bp.giaddr.v4(); 643 data[28:] = bp.chaddr; 644 data[44:] = array of byte bp.sname; 645 data[108:] = array of byte bp.file; 646 data[236:] = bp.vend; 647 return data; 648} 649 650ctostr(cstr: array of byte): string 651{ 652 for(i:=0; i<len cstr; i++) 653 if(cstr[i] == byte 0) 654 break; 655 return string cstr[0:i]; 656} 657 658Bootp.text(bp: self ref Bootp): string 659{ 660 s := sys->sprint("op=%d htype=%d hlen=%d hops=%d xid=%ud secs=%ud ciaddr=%s yiaddr=%s", 661 int bp.op, bp.htype, bp.hlen, bp.hops, bp.xid, bp.secs, bp.ciaddr.text(), bp.yiaddr.text()); 662 s += sys->sprint(" server=%s gateway=%s hwaddr=%q host=%q file=%q magic=%q", 663 bp.siaddr.text(), bp.giaddr.text(), ether->text(bp.chaddr), bp.sname, bp.file, magic(bp.vend[0:4])); 664 if(magic(bp.vend[0:4]) == "plan9") 665 s += "("+ctostr(bp.vend)+")"; 666 return s; 667} 668