1implement Dhcpclient; 2 3# 4# DHCP and BOOTP clients 5# Copyright © 2004-2006 Vita Nuova Holdings Limited 6# 7 8include "sys.m"; 9 sys: Sys; 10 11include "ip.m"; 12 ip: IP; 13 IPv4off, IPaddrlen, Udphdrlen, Udpraddr, Udpladdr, Udprport, Udplport: import IP; 14 IPaddr: import ip; 15 get2, get4, put2, put4: import ip; 16 17include "keyring.m"; 18include "security.m"; # for Random 19 20include "dial.m"; 21 dial: Dial; 22 23include "dhcp.m"; 24 25debug := 0; 26 27xidgen: int; 28 29init() 30{ 31 sys = load Sys Sys->PATH; 32 random := load Random Random->PATH; 33 if(random != nil) 34 xidgen = random->randomint(Random->NotQuiteRandom); 35 else 36 xidgen = sys->pctl(0, nil)*sys->millisec(); 37 random = nil; 38 dial = load Dial Dial->PATH; 39 ip = load IP IP->PATH; 40 ip->init(); 41} 42 43tracing(d: int) 44{ 45 debug = d; 46} 47 48Bootconf.new(): ref Bootconf 49{ 50 bc := ref Bootconf; 51 bc.lease = 0; 52 bc.options = array[256] of array of byte; 53 return bc; 54} 55 56Bootconf.get(c: self ref Bootconf, n: int): array of byte 57{ 58 a := c.options; 59 if(n & Ovendor){ 60 a = c.vendor; 61 n &= ~Ovendor; 62 } 63 if(n < 0 || n >= len a) 64 return nil; 65 return a[n]; 66} 67 68Bootconf.getint(c: self ref Bootconf, n: int): int 69{ 70 a := c.get(n); 71 v := 0; 72 for(i := 0; i < len a; i++) 73 v = (v<<8) | int a[i]; 74 return v; 75} 76 77Bootconf.getip(c: self ref Bootconf, n: int): string 78{ 79 l := c.getips(n); 80 if(l == nil) 81 return nil; 82 return hd l; 83} 84 85Bootconf.getips(c: self ref Bootconf, n: int): list of string 86{ 87 a := c.get(n); 88 rl: list of string; 89 while(len a >= 4){ 90 rl = v4text(a) :: rl; 91 a = a[4:]; 92 } 93 l: list of string; 94 for(; rl != nil; rl = tl rl) 95 l = hd rl :: l; 96 return l; 97} 98 99Bootconf.gets(c: self ref Bootconf, n: int): string 100{ 101 a := c.get(n); 102 if(a == nil) 103 return nil; 104 for(i:=0; i<len a; i++) 105 if(a[i] == byte 0) 106 break; 107 return string a[0:i]; 108} 109 110Bootconf.put(c: self ref Bootconf, n: int, a: array of byte) 111{ 112 if(n < 0 || n >= len c.options) 113 return; 114 ca := array[len a] of byte; 115 ca[0:] = a; 116 c.options[n] = ca; 117} 118 119Bootconf.putint(c: self ref Bootconf, n: int, v: int) 120{ 121 if(n < 0 || n >= len c.options) 122 return; 123 a := array[4] of byte; 124 put4(a, 0, v); 125 c.options[n] = a; 126} 127 128Bootconf.putips(c: self ref Bootconf, n: int, ips: list of string) 129{ 130 if(n < 0 || n >= len c.options) 131 return; 132 na := len ips; 133 a := array[na*4] of byte; 134 na = 0; 135 for(; ips != nil; ips = tl ips){ 136 (nil, ipa) := IPaddr.parse(hd ips); 137 a[na++:] = ipa.v4(); 138 } 139 c.options[n] = a; 140} 141 142Bootconf.puts(c: self ref Bootconf, n: int, s: string) 143{ 144 if(n < 0 || n >= len c.options) 145 return; 146 c.options[n] = array of byte s; 147} 148 149# 150# 151# DHCP 152# 153# 154 155# BOOTP operations 156Bootprequest, Bootpreply: con 1+iota; 157 158# DHCP operations 159NotDHCP, Discover, Offer, Request, Decline, Ack, Nak, Release, Inform: con iota; 160 161Dhcp: adt { 162 udphdr: array of byte; 163 op: int; 164 htype: int; 165 hops: int; 166 xid: int; 167 secs: int; 168 flags: int; 169 ciaddr: IPaddr; 170 yiaddr: IPaddr; 171 siaddr: IPaddr; 172 giaddr: IPaddr; 173 chaddr: array of byte; 174 sname: string; 175 file: string; 176 options: list of (int, array of byte); 177 dhcpop: int; 178}; 179 180opnames := array[] of { 181 Discover => "Discover", 182 Offer => "Offer", 183 Request => "Request", 184 Decline => "Decline", 185 Ack => "Ack", 186 Nak => "Nak", 187 Release => "Release", 188 Inform => "Inform" 189}; 190 191opname(op: int): string 192{ 193 if(op >= 0 && op < len opnames) 194 return opnames[op]; 195 return sys->sprint("OP%d", op); 196} 197 198stringget(buf: array of byte): string 199{ 200 for(x := 0; x < len buf; x++) 201 if(buf[x] == byte 0) 202 break; 203 if(x == 0) 204 return nil; 205 return string buf[0 : x]; 206} 207 208eqbytes(b1: array of byte, b2: array of byte): int 209{ 210 l := len b1; 211 if(l != len b2) 212 return 0; 213 for(i := 0; i < l; i++) 214 if(b1[i] != b2[i]) 215 return 0; 216 return 1; 217} 218 219magic := array[] of {byte 99, byte 130, byte 83, byte 99}; # RFC2132 (replacing RFC1048) 220 221dhcpsend(fd: ref Sys->FD, xid: int, dhcp: ref Dhcp) 222{ 223 dhcp.xid = xid; 224 abuf := array[576+Udphdrlen] of {* => byte 0}; 225 abuf[0:] = dhcp.udphdr; 226 buf := abuf[Udphdrlen:]; 227 buf[0] = byte dhcp.op; 228 buf[1] = byte dhcp.htype; 229 buf[2] = byte len dhcp.chaddr; 230 buf[3] = byte dhcp.hops; 231 put4(buf, 4, xid); 232 put2(buf, 8, dhcp.secs); 233 put2(buf, 10, dhcp.flags); 234 buf[12:] = dhcp.ciaddr.v4(); 235 buf[16:] = dhcp.yiaddr.v4(); 236 buf[20:] = dhcp.siaddr.v4(); 237 buf[24:] = dhcp.giaddr.v4(); 238 buf[28:] = dhcp.chaddr; 239 buf[44:] = array of byte dhcp.sname; # [64] 240 buf[108:] = array of byte dhcp.file; # [128] 241 o := 236; 242 # RFC1542 suggests including magic and Oend as a minimum, even in BOOTP 243 buf[o:] = magic; 244 o += 4; 245 if(dhcp.dhcpop != NotDHCP){ 246 buf[o++] = byte Otype; 247 buf[o++] = byte 1; 248 buf[o++] = byte dhcp.dhcpop; 249 } 250 for(ol := dhcp.options; ol != nil; ol = tl ol){ 251 (opt, val) := hd ol; 252 buf[o++] = byte opt; 253 buf[o++] = byte len val; 254 if(len val > 0){ 255 buf[o:] = val; 256 o += len val; 257 } 258 } 259 buf[o++] = byte Oend; 260 if(debug) 261 dumpdhcp(dhcp, "->"); 262 sys->write(fd, abuf, len abuf); 263} 264 265kill(pid: int, grp: string) 266{ 267 fd := sys->open("#p/" + string pid + "/ctl", sys->OWRITE); 268 if(fd != nil) 269 sys->fprint(fd, "kill%s", grp); 270} 271 272v4text(a: array of byte): string 273{ 274 return sys->sprint("%ud.%ud.%ud.%ud", int a[0], int a[1], int a[2], int a[3]); 275} 276 277parseopt(a: array of byte, isdhcp: int): (int, list of (int, array of byte)) 278{ 279 opts: list of (int, array of byte); 280 xop := NotDHCP; 281 for(i := 0; i < len a;){ 282 op := int a[i++]; 283 if(op == Opad) 284 continue; 285 if(op == Oend || i >= len a) 286 break; 287 l := int a[i++]; 288 if(i+l > len a) 289 break; 290 if(isdhcp && op == Otype) 291 xop = int a[i]; 292 else 293 opts = (op, a[i:i+l]) :: opts; 294 i += l; 295 } 296 rl := opts; 297 opts = nil; 298 for(; rl != nil; rl = tl rl) 299 opts = hd rl :: opts; 300 return (xop, opts); 301} 302 303dhcpreader(pidc: chan of int, srv: ref DhcpIO) 304{ 305 pidc <-= sys->pctl(0, nil); 306 for(;;){ 307 abuf := array [576+Udphdrlen] of byte; 308 n := sys->read(srv.fd, abuf, len abuf); 309 if(n < 0){ 310 if(debug) 311 sys->print("read error: %r\n"); 312 sys->sleep(1000); 313 continue; 314 } 315 if(n < Udphdrlen+236){ 316 if(debug) 317 sys->print("short read: %d\n", n); 318 continue; 319 } 320 buf := abuf[Udphdrlen:n]; 321 n -= Udphdrlen; 322 dhcp := ref Dhcp; 323 dhcp.op = int buf[0]; 324 if(dhcp.op != Bootpreply){ 325 if(debug) 326 sys->print("bootp: not reply, discarded\n"); 327 continue; 328 } 329 dhcp.dhcpop = NotDHCP; 330 if(n >= 240 && eqbytes(buf[236:240], magic)) # otherwise it's something we won't understand 331 (dhcp.dhcpop, dhcp.options) = parseopt(buf[240:n], 1); 332 case dhcp.dhcpop { 333 NotDHCP or Ack or Nak or Offer => 334 ; 335 * => 336 if(debug) 337 sys->print("dhcp: ignore dhcp op %d\n", dhcp.dhcpop); 338 continue; 339 } 340 dhcp.udphdr = abuf[0:Udphdrlen]; 341 dhcp.htype = int buf[1]; 342 hlen := int buf[2]; 343 dhcp.hops = int buf[3]; 344 dhcp.xid = get4(buf, 4); 345 dhcp.secs = get2(buf, 8); 346 dhcp.flags = get2(buf, 10); 347 dhcp.ciaddr = IPaddr.newv4(buf[12:]); 348 dhcp.yiaddr = IPaddr.newv4(buf[16:]); 349 dhcp.siaddr = IPaddr.newv4(buf[20:]); 350 dhcp.giaddr = IPaddr.newv4(buf[24:]); 351 dhcp.chaddr = buf[28 : 28 + hlen]; 352 dhcp.sname = stringget(buf[44 : 108]); 353 dhcp.file = stringget(buf[108 : 236]); 354 srv.dc <-= dhcp; 355 } 356} 357 358timeoutstart(msecs: int): (int, chan of int) 359{ 360 tc := chan of int; 361 spawn timeoutproc(tc, msecs); 362 return (<-tc, tc); 363} 364 365timeoutproc(c: chan of int, msecs: int) 366{ 367 c <-= sys->pctl(0, nil); 368 sys->sleep(msecs); 369 c <-= 1; 370} 371 372hex(b: int): int 373{ 374 if(b >= '0' && b <= '9') 375 return b-'0'; 376 if(b >= 'A' && b <= 'F') 377 return b-'A' + 10; 378 if(b >= 'a' && b <= 'f') 379 return b-'a' + 10; 380 return -1; 381} 382 383gethaddr(device: string): (int, string, array of byte) 384{ 385 fd := sys->open(device, Sys->OREAD); 386 if(fd == nil) 387 return (-1, sys->sprint("%r"), nil); 388 buf := array [100] of byte; 389 n := sys->read(fd, buf, len buf); 390 if(n < 0) 391 return (-1, sys->sprint("%r"), nil); 392 if(n == 0) 393 return (-1, "empty address file", nil); 394 addr := array [n/2] of byte; 395 for(i := 0; i < len addr; i++){ 396 u := hex(int buf[2*i]); 397 l := hex(int buf[2*i+1]); 398 if(u < 0 || l < 0) 399 return (-1, "bad address syntax", nil); 400 addr[i] = byte ((u<<4)|l); 401 } 402 return (1, nil, addr); 403} 404 405newrequest(dest: IPaddr, bootfile: string, htype: int, haddr: array of byte, ipaddr: IPaddr, options: array of array of byte): ref Dhcp 406{ 407 dhcp := ref Dhcp; 408 dhcp.op = Bootprequest; 409 hdr := array[Udphdrlen] of {* => byte 0}; 410 hdr[Udpraddr:] = dest.v6(); 411 put2(hdr, Udprport, 67); 412 dhcp.udphdr = hdr; 413 dhcp.htype = htype; 414 dhcp.chaddr = haddr; 415 dhcp.hops = 0; 416 dhcp.secs = 0; 417 dhcp.flags = 0; 418 dhcp.xid = 0; 419 dhcp.ciaddr = ipaddr; 420 dhcp.yiaddr = ip->v4noaddr; 421 dhcp.siaddr = ip->v4noaddr; 422 dhcp.giaddr = ip->v4noaddr; 423 dhcp.file = bootfile; 424 dhcp.dhcpop = NotDHCP; 425 if(options != nil){ 426 for(i := 0; i < len options; i++) 427 if(options[i] != nil) 428 dhcp.options = (i, options[i]) :: dhcp.options; 429 } 430 clientid := array[len haddr + 1] of byte; 431 clientid[0] = byte htype; 432 clientid[1:] = haddr; 433 dhcp.options = (Oclientid, clientid) :: dhcp.options; 434 dhcp.options = (Ovendorclass, array of byte "plan9_386") :: dhcp.options; # 386 will do because type doesn't matter 435 return dhcp; 436} 437 438udpannounce(net: string): (ref Sys->FD, string) 439{ 440 if(net == nil) 441 net = "/net"; 442 conn := dial->announce(net+"/udp!*!68"); 443 if(conn == nil) 444 return (nil, sys->sprint("can't announce dhcp port: %r")); 445 if(sys->fprint(conn.cfd, "headers") < 0) 446 return (nil, sys->sprint("can't set headers mode on dhcp port: %r")); 447 conn.dfd = sys->open(conn.dir+"/data", Sys->ORDWR); 448 if(conn.dfd == nil) 449 return (nil, sys->sprint("can't open %s: %r", conn.dir+"/data")); 450 return (conn.dfd, nil); 451} 452 453ifcnoaddr(fd: ref Sys->FD, s: string) 454{ 455 if(fd != nil && sys->fprint(fd, "%s %s %s", s, (ip->noaddr).text(), (ip->noaddr).text()) < 0){ 456 if(debug) 457 sys->print("dhcp: ctl %s: %r\n", s); 458 } 459} 460 461setup(net: string, device: string, init: ref Bootconf): (ref Dhcp, ref DhcpIO, string) 462{ 463 (htype, err, mac) := gethaddr(device); 464 if(htype < 0) 465 return (nil, nil, sys->sprint("can't get hardware MAC address: %s", err)); 466 ciaddr := ip->v4noaddr; 467 if(init != nil && init.ip != nil){ 468 valid: int; 469 (valid, ciaddr) = IPaddr.parse(init.ip); 470 if(valid < 0) 471 return (nil, nil, sys->sprint("invalid ip address: %s", init.ip)); 472 } 473 (dfd, err2) := udpannounce(net); 474 if(err2 != nil) 475 return (nil, nil, err); 476 bootfile: string; 477 options: array of array of byte; 478 if(init != nil){ 479 bootfile = init.bootf; 480 options = init.options; 481 } 482 return (newrequest(ip->v4bcast, bootfile, htype, mac, ciaddr, options), DhcpIO.new(dfd), nil); 483} 484 485# 486# BOOTP (RFC951) is used by Inferno only during net boots, to get initial IP address and TFTP address and parameters 487# 488bootp(net: string, ctlifc: ref Sys->FD, device: string, init: ref Bootconf): (ref Bootconf, string) 489{ 490 (req, srv, err) := setup(net, device, init); 491 if(err != nil) 492 return (nil, err); 493 ifcnoaddr(ctlifc, "add"); 494 rdhcp := exchange(srv, ++xidgen, req, 1<<NotDHCP); 495 srv.rstop(); 496 ifcnoaddr(ctlifc, "remove"); 497 if(rdhcp == nil) 498 return (nil, "no response to BOOTP request"); 499 return (fillbootconf(init, rdhcp), nil); 500} 501 502defparams := array[] of { 503 byte Omask, byte Orouter, byte Odnsserver, byte Ohostname, byte Odomainname, byte Ontpserver, 504}; 505 506# 507# DHCP (RFC2131) 508# 509dhcp(net: string, ctlifc: ref Sys->FD, device: string, init: ref Bootconf, needparam: array of int): (ref Bootconf, ref Lease, string) 510{ 511 (req, srv, err) := setup(net, device, init); 512 if(err != nil) 513 return (nil, nil, err); 514 params := defparams; 515 if(needparam != nil){ 516 n := len defparams; 517 params = array[n+len needparam] of byte; 518 params[0:] = defparams; 519 for(i := 0; i < len needparam; i++) 520 params[n+i] = byte needparam[i]; 521 } 522 initopt := (Oparams, params) :: req.options; # RFC2131 requires parameters to be repeated each time 523 lease := ref Lease(0, chan[1] of (ref Bootconf, string)); 524 spawn dhcp1(srv, lease, net, ctlifc, req, init, initopt); 525 bc: ref Bootconf; 526 (bc, err) = <-lease.configs; 527 return (bc, lease, err); 528} 529 530dhcp1(srv: ref DhcpIO, lease: ref Lease, net: string, ctlifc: ref Sys->FD, req: ref Dhcp, init: ref Bootconf, initopt: list of (int, array of byte)) 531{ 532 cfd := -1; 533 if(ctlifc != nil) 534 cfd = ctlifc.fd; 535 lease.pid = sys->pctl(Sys->NEWPGRP|Sys->NEWFD, 1 :: srv.fd.fd :: cfd :: nil); 536 if(ctlifc != nil) 537 ctlifc = sys->fildes(ctlifc.fd); 538 srv.fd = sys->fildes(srv.fd.fd); 539 rep: ref Dhcp; 540 ifcnoaddr(ctlifc, "add"); 541 if(req.ciaddr.isvalid()) 542 rep = reacquire(srv, req, initopt, req.ciaddr); 543 if(rep == nil) 544 rep = askround(srv, req, initopt); 545 srv.rstop(); 546 ifcnoaddr(ctlifc, "remove"); 547 if(rep == nil){ 548 lease.pid = 0; 549 lease.configs <-= (nil, "no response"); 550 exit; 551 } 552 for(;;){ 553 conf := fillbootconf(init, rep); 554 applycfg(net, ctlifc, conf); 555 if(conf.lease == 0){ 556 srv.rstop(); 557 lease.pid = 0; 558 flush(lease.configs); 559 lease.configs <-= (conf, nil); 560 exit; 561 } 562 flush(lease.configs); 563 lease.configs <-= (conf, nil); 564 req.ciaddr = rep.yiaddr; 565 while((rep = tenancy(srv, req, conf.lease)) != nil){ 566 if(rep.dhcpop == Nak || !rep.ciaddr.eq(req.ciaddr)) 567 break; 568 req.udphdr[Udpraddr:] = rep.udphdr[Udpraddr:Udpraddr+IPaddrlen]; 569 conf = fillbootconf(init, rep); 570 } 571 removecfg(net, ctlifc, conf); 572 ifcnoaddr(ctlifc, "add"); 573 while((rep = askround(srv, req, initopt)) == nil){ 574 flush(lease.configs); 575 lease.configs <-= (nil, "no response"); 576 srv.rstop(); 577 sys->sleep(60*1000); 578 } 579 ifcnoaddr(ctlifc, "remove"); 580 } 581} 582 583reacquire(srv: ref DhcpIO, req: ref Dhcp, initopt: list of (int, array of byte), addr: IPaddr): ref Dhcp 584{ 585 # INIT-REBOOT: know an address; try requesting it (once) 586 # TO DO: could use Inform when our address is static but we need a few service parameters 587 req.ciaddr = ip->v4noaddr; 588 rep := request(srv, ++xidgen, req, (Oipaddr, addr.v4()) :: initopt); 589 if(rep != nil && rep.dhcpop == Ack && addr.eq(rep.yiaddr)){ 590 if(debug) 591 sys->print("req: server accepted\n"); 592 req.udphdr[Udpraddr:] = rep.udphdr[Udpraddr:Udpraddr+IPaddrlen]; 593 return rep; 594 } 595 if(debug) 596 sys->print("req: cannot reclaim\n"); 597 return nil; 598} 599 600askround(srv: ref DhcpIO, req: ref Dhcp, initopt: list of (int, array of byte)): ref Dhcp 601{ 602 # INIT 603 req.ciaddr = ip->v4noaddr; 604 req.udphdr[Udpraddr:] = (ip->v4bcast).v6(); 605 for(retries := 0; retries < 5; retries++){ 606 # SELECTING 607 req.dhcpop = Discover; 608 req.options = initopt; 609 rep := exchange(srv, ++xidgen, req, 1<<Offer); 610 if(rep == nil) 611 break; 612 # 613 # could wait a little while and accumulate offers, but is it sensible? 614 # we do sometimes see arguments between DHCP servers that could 615 # only be resolved by user choice 616 # 617 if(!rep.yiaddr.isvalid()) 618 continue; # server has no idea either 619 serverid := getopt(rep.options, Oserverid, 4); 620 if(serverid == nil) 621 continue; # broken server 622 # REQUESTING 623 options := (Oserverid, serverid) :: (Oipaddr, rep.yiaddr.v4()) :: initopt; 624 lease := getlease(rep); 625 if(lease != nil) 626 options = (Olease, lease) :: options; 627 rep = request(srv, rep.xid, req, options); 628 if(rep != nil){ 629 # could probe with ARP here, and if found, Decline 630 if(debug) 631 sys->print("req: server accepted\n"); 632 req.udphdr[Udpraddr:] = rep.udphdr[Udpraddr:Udpraddr+IPaddrlen]; 633 return rep; 634 } 635 } 636 return nil; 637} 638 639request(srv: ref DhcpIO, xid: int, req: ref Dhcp, options: list of (int, array of byte)): ref Dhcp 640{ 641 req.dhcpop = Request; # Selecting 642 req.options = options; 643 rep := exchange(srv, xid, req, (1<<Ack)|(1<<Nak)); 644 if(rep == nil || rep.dhcpop == Nak) 645 return nil; 646 return rep; 647} 648 649# renew 650# direct to server from T1 to T2 [RENEW] 651# Request must not include 652# requested IP address, server identifier 653# Request must include 654# ciaddr set to client's address 655# Request might include 656# lease time 657# similar, but broadcast, from T2 to T3 [REBIND] 658# at T3, unbind, restart Discover 659 660tenancy(srv: ref DhcpIO, req: ref Dhcp, leasesec: int): ref Dhcp 661{ 662 # configure address... 663 t3 := big leasesec * big 1000; # lease expires; restart 664 t2 := (big 3 * t3)/big 4; # broadcast renewal request at ¾time 665 t1 := t2/big 2; # renew lease with original server at ½time 666 srv.rstop(); 667 thebigsleep(t1); 668 # RENEW 669 rep := renewing(srv, req, t1, t2); 670 if(rep != nil) 671 return rep; 672 # REBIND 673 req.udphdr[Udpraddr:] = (ip->v4bcast).v6(); # now try broadcast 674 return renewing(srv, req, t2, t3); 675} 676 677renewing(srv: ref DhcpIO, req: ref Dhcp, a: big, b: big): ref Dhcp 678{ 679 Minute: con big(60*1000); 680 while(a < b){ 681 rep := exchange(srv, req.xid, req, (1<<Ack)|(1<<Nak)); 682 if(rep != nil) 683 return rep; 684 delta := (b-a)/big 2; 685 if(delta < Minute) 686 delta = Minute; 687 thebigsleep(delta); 688 a += delta; 689 } 690 return nil; 691} 692 693thebigsleep(msec: big) 694{ 695 Day: con big (24*3600*1000); # 1 day in msec 696 while(msec > big 0){ 697 n := msec; 698 if(n > Day) 699 n = Day; 700 sys->sleep(int n); 701 msec -= n; 702 } 703} 704 705getlease(m: ref Dhcp): array of byte 706{ 707 lease := getopt(m.options, Olease, 4); 708 if(lease == nil) 709 return nil; 710 if(get4(lease, 0) == 0){ 711 lease = array[4] of byte; 712 put4(lease, 0, 15*60); 713 } 714 return lease; 715} 716 717fillbootconf(init: ref Bootconf, pkt: ref Dhcp): ref Bootconf 718{ 719 bc := ref Bootconf; 720 if(init != nil) 721 *bc = *init; 722 if(bc.options == nil) 723 bc.options = array[256] of array of byte; 724 for(l := pkt.options; l != nil; l = tl l){ 725 (c, v) := hd l; 726 if(bc.options[c] == nil) 727 bc.options[c] = v; # give priority to first occurring 728 } 729 if((a := bc.get(Ovendorinfo)) != nil){ 730 if(bc.vendor == nil) 731 bc.vendor = array[256] of array of byte; 732 for(l = parseopt(a, 0).t1; l != nil; l = tl l){ 733 (c, v) := hd l; 734 if(bc.vendor[c] == nil) 735 bc.vendor[c] = v; 736 } 737 } 738 if(pkt.yiaddr.isvalid()){ 739 bc.ip = pkt.yiaddr.text(); 740 bc.ipmask = bc.getip(Omask); 741 if(bc.ipmask == nil) 742 bc.ipmask = pkt.yiaddr.classmask().masktext(); 743 } 744 bc.bootf = pkt.file; 745 bc.dhcpip = IPaddr.newv6(pkt.udphdr[Udpraddr:]).text(); 746 bc.siaddr = pkt.siaddr.text(); 747 bc.lease = bc.getint(Olease); 748 if(bc.lease == Infinite) 749 bc.lease = 0; 750 else if(debug > 1) 751 bc.lease = 2*60; # shorten time, for testing 752 bc.dom = bc.gets(Odomainname); 753 s := bc.gets(Ohostname); 754 for(i:=0; i<len s; i++) 755 if(s[i] == '.'){ 756 if(bc.dom == nil) 757 bc.dom = s[i+1:]; 758 s = s[0:i]; 759 break; 760 } 761 bc.sys = s; 762 bc.ipgw = bc.getip(Orouter); 763 bc.bootip = bc.getip(Otftpserver); 764 bc.serverid = bc.getip(Oserverid); 765 return bc; 766} 767 768Lease.release(l: self ref Lease) 769{ 770 # could send a Release message 771 # should unconfigure 772 if(l.pid){ 773 kill(l.pid, "grp"); 774 l.pid = 0; 775 } 776} 777 778flush(c: chan of (ref Bootconf, string)) 779{ 780 alt{ 781 <-c => ; 782 * => ; 783 } 784} 785 786DhcpIO: adt { 787 fd: ref Sys->FD; 788 pid: int; 789 dc: chan of ref Dhcp; 790 new: fn(fd: ref Sys->FD): ref DhcpIO; 791 rstart: fn(io: self ref DhcpIO); 792 rstop: fn(io: self ref DhcpIO); 793}; 794 795DhcpIO.new(fd: ref Sys->FD): ref DhcpIO 796{ 797 return ref DhcpIO(fd, 0, chan of ref Dhcp); 798} 799 800DhcpIO.rstart(io: self ref DhcpIO) 801{ 802 if(io.pid == 0){ 803 pids := chan of int; 804 spawn dhcpreader(pids, io); 805 io.pid = <-pids; 806 } 807} 808 809DhcpIO.rstop(io: self ref DhcpIO) 810{ 811 if(io.pid != 0){ 812 kill(io.pid, ""); 813 io.pid = 0; 814 } 815} 816 817getopt(options: list of (int, array of byte), op: int, minlen: int): array of byte 818{ 819 for(; options != nil; options = tl options){ 820 (opt, val) := hd options; 821 if(opt == op && len val >= minlen) 822 return val; 823 } 824 return nil; 825} 826 827exchange(srv: ref DhcpIO, xid: int, req: ref Dhcp, accept: int): ref Dhcp 828{ 829 srv.rstart(); 830 nsec := 3; 831 for(count := 0; count < 5; count++) { 832 (tpid, tc) := timeoutstart(nsec*1000); 833 dhcpsend(srv.fd, xid, req); 834 Wait: 835 for(;;){ 836 alt { 837 <-tc=> 838 break Wait; 839 rep := <-srv.dc=> 840 if(debug) 841 dumpdhcp(rep, "<-"); 842 if(rep.op == Bootpreply && 843 rep.xid == req.xid && 844 rep.ciaddr.eq(req.ciaddr) && 845 eqbytes(rep.chaddr, req.chaddr)){ 846 if((accept & (1<<rep.dhcpop)) == 0){ 847 if(debug) 848 sys->print("req: unexpected reply %s to %s\n", opname(rep.dhcpop), opname(req.dhcpop)); 849 continue; 850 } 851 kill(tpid, ""); 852 return rep; 853 } 854 if(debug) 855 sys->print("req: mismatch\n"); 856 } 857 } 858 req.secs += nsec; 859 nsec++; 860 } 861 return nil; 862} 863 864applycfg(net: string, ctlfd: ref Sys->FD, bc: ref Bootconf): string 865{ 866 # write addresses to /net/... 867 # local address, mask[or default], remote address [mtu] 868 if(net == nil) 869 net = "/net"; 870 if(bc.ip == nil) 871 return "invalid address"; 872 if(ctlfd != nil){ 873 if(sys->fprint(ctlfd, "add %s %s", bc.ip, bc.ipmask) < 0) # TO DO: [raddr [mtu]] 874 return sys->sprint("add interface: %r"); 875 # could use "mtu n" request to set/change mtu 876 } 877 # if primary: 878 # add default route if gateway valid 879 # put ndb entries ip=, ipmask=, ipgw=; sys= dom=; fs=; auth=; dns=; ntp=; other options from bc.options 880 if(bc.ipgw != nil){ 881 fd := sys->open(net+"/iproute", Sys->OWRITE); 882 if(fd != nil) 883 sys->fprint(fd, "add 0 0 %s", bc.ipgw); 884 } 885 s := sys->sprint("ip=%s ipmask=%s", bc.ip, bc.ipmask); 886 if(bc.ipgw != nil) 887 s += sys->sprint(" ipgw=%s", bc.ipgw); 888 s += "\n"; 889 if(bc.sys != nil) 890 s += sys->sprint(" sys=%s\n", bc.sys); 891 if(bc.dom != nil) 892 s += sys->sprint(" dom=%s.%s\n", bc.sys, bc.dom); 893 if((addr := bc.getip(OP9auth)) != nil) 894 s += sys->sprint(" auth=%s\n", addr); # TO DO: several addresses 895 if((addr = bc.getip(OP9fs)) != nil) 896 s += sys->sprint(" fs=%s\n", addr); 897 if((addr = bc.getip(Odnsserver)) != nil) 898 s += sys->sprint(" dns=%s\n", addr); 899 fd := sys->open(net+"/ndb", Sys->OWRITE | Sys->OTRUNC); 900 if(fd != nil){ 901 a := array of byte s; 902 sys->write(fd, a, len a); 903 } 904 return nil; 905} 906 907removecfg(nil: string, ctlfd: ref Sys->FD, bc: ref Bootconf): string 908{ 909 # remove localaddr, localmask[or default] 910 if(ctlfd != nil){ 911 if(sys->fprint(ctlfd, "remove %s %s", bc.ip, bc.ipmask) < 0) 912 return sys->sprint("remove address: %r"); 913 } 914 bc.ip = nil; 915 bc.ipgw = nil; 916 bc.ipmask = nil; 917 # remote address? 918 # clear net+"/ndb"? 919 return nil; 920} 921 922# 923# the following is just for debugging 924# 925 926dumpdhcp(m: ref Dhcp, dir: string) 927{ 928 s := ""; 929 sys->print("%s %s/%ud: ", dir, IPaddr.newv6(m.udphdr[Udpraddr:]).text(), get2(m.udphdr, Udprport)); 930 if(m.dhcpop != NotDHCP) 931 s = " "+opname(m.dhcpop); 932 sys->print("op %d%s htype %d hops %d xid %ud\n", m.op, s, m.htype, m.hops, m.xid); 933 sys->print("\tsecs %d flags 0x%.4ux\n", m.secs, m.flags); 934 sys->print("\tciaddr %s\n", m.ciaddr.text()); 935 sys->print("\tyiaddr %s\n", m.yiaddr.text()); 936 sys->print("\tsiaddr %s\n", m.siaddr.text()); 937 sys->print("\tgiaddr %s\n", m.giaddr.text()); 938 sys->print("\tchaddr "); 939 for(x := 0; x < len m.chaddr; x++) 940 sys->print("%2.2ux", int m.chaddr[x]); 941 sys->print("\n"); 942 if(m.sname != nil) 943 sys->print("\tsname %s\n", m.sname); 944 if(m.file != nil) 945 sys->print("\tfile %s\n", m.file); 946 if(m.options != nil){ 947 sys->print("\t"); 948 printopts(m.options, opts); 949 sys->print("\n"); 950 } 951} 952 953Optbytes, Optaddr, Optmask, Optint, Optstr, Optopts, Opthex: con iota; 954 955Opt: adt 956{ 957 code: int; 958 name: string; 959 otype: int; 960}; 961 962opts: array of Opt = array[] of { 963 (Omask, "ipmask", Optmask), 964 (Orouter, "ipgw", Optaddr), 965 (Odnsserver, "dns", Optaddr), 966 (Ohostname, "hostname", Optstr), 967 (Odomainname, "domain", Optstr), 968 (Ontpserver, "ntp", Optaddr), 969 (Oipaddr, "requestedip", Optaddr), 970 (Olease, "lease", Optint), 971 (Oserverid, "serverid", Optaddr), 972 (Otype, "dhcpop", Optint), 973 (Ovendorclass, "vendorclass", Optstr), 974 (Ovendorinfo, "vendorinfo", Optopts), 975 (Onetbiosns, "wins", Optaddr), 976 (Opop3server, "pop3", Optaddr), 977 (Osmtpserver, "smtp", Optaddr), 978 (Owwwserver, "www", Optaddr), 979 (Oparams, "params", Optbytes), 980 (Otftpserver, "tftp", Optaddr), 981 (Oclientid, "clientid", Opthex), 982}; 983 984p9opts: array of Opt = array[] of { 985 (OP9fs, "fs", Optaddr), 986 (OP9auth, "auth", Optaddr), 987}; 988 989lookopt(optab: array of Opt, code: int): (int, string, int) 990{ 991 for(i:=0; i<len optab; i++) 992 if(opts[i].code == code) 993 return opts[i]; 994 return (-1, nil, 0); 995} 996 997printopts(options: list of (int, array of byte), opts: array of Opt) 998{ 999 for(; options != nil; options = tl options){ 1000 (code, val) := hd options; 1001 sys->print("(%d %d", code, len val); 1002 (nil, name, otype) := lookopt(opts, code); 1003 if(name == nil){ 1004 for(v := 0; v < len val; v++) 1005 sys->print(" %d", int val[v]); 1006 }else{ 1007 sys->print(" %s", name); 1008 case otype { 1009 Optbytes => 1010 for(v := 0; v < len val; v++) 1011 sys->print(" %d", int val[v]); 1012 Opthex => 1013 for(v := 0; v < len val; v++) 1014 sys->print(" %#.2ux", int val[v]); 1015 Optaddr or Optmask => 1016 while(len val >= 4){ 1017 sys->print(" %s", v4text(val)); 1018 val = val[4:]; 1019 } 1020 Optstr => 1021 sys->print(" \"%s\"", string val); 1022 Optint => 1023 n := 0; 1024 for(v := 0; v < len val; v++) 1025 n = (n<<8) | int val[v]; 1026 sys->print(" %d", n); 1027 Optopts => 1028 printopts(parseopt(val, 0).t1, p9opts); 1029 } 1030 } 1031 sys->print(")"); 1032 } 1033} 1034