implement Dhcpclient; # # DHCP and BOOTP clients # Copyright © 2004-2006 Vita Nuova Holdings Limited # include "sys.m"; sys: Sys; include "ip.m"; ip: IP; IPv4off, IPaddrlen, Udphdrlen, Udpraddr, Udpladdr, Udprport, Udplport: import IP; IPaddr: import ip; get2, get4, put2, put4: import ip; include "keyring.m"; include "security.m"; # for Random include "dial.m"; dial: Dial; include "dhcpclient.m"; debug := 0; xidgen: int; init() { sys = load Sys Sys->PATH; random := load Random Random->PATH; if(random != nil) xidgen = random->randomint(Random->NotQuiteRandom); else xidgen = sys->pctl(0, nil)*sys->millisec(); random = nil; dial = load Dial Dial->PATH; ip = load IP IP->PATH; ip->init(); } tracing(d: int) { debug = d; } Bootconf.new(): ref Bootconf { bc := ref Bootconf; bc.lease = 0; bc.options = array[256] of array of byte; return bc; } Bootconf.get(c: self ref Bootconf, n: int): array of byte { a := c.options; if(n & Ovendor){ a = c.vendor; n &= ~Ovendor; } if(n < 0 || n >= len a) return nil; return a[n]; } Bootconf.getint(c: self ref Bootconf, n: int): int { a := c.get(n); v := 0; for(i := 0; i < len a; i++) v = (v<<8) | int a[i]; return v; } Bootconf.getip(c: self ref Bootconf, n: int): string { l := c.getips(n); if(l == nil) return nil; return hd l; } Bootconf.getips(c: self ref Bootconf, n: int): list of string { a := c.get(n); rl: list of string; while(len a >= 4){ rl = v4text(a) :: rl; a = a[4:]; } l: list of string; for(; rl != nil; rl = tl rl) l = hd rl :: l; return l; } Bootconf.gets(c: self ref Bootconf, n: int): string { a := c.get(n); if(a == nil) return nil; for(i:=0; i= len c.options) return; ca := array[len a] of byte; ca[0:] = a; c.options[n] = ca; } Bootconf.putint(c: self ref Bootconf, n: int, v: int) { if(n < 0 || n >= len c.options) return; a := array[4] of byte; put4(a, 0, v); c.options[n] = a; } Bootconf.putips(c: self ref Bootconf, n: int, ips: list of string) { if(n < 0 || n >= len c.options) return; na := len ips; a := array[na*4] of byte; na = 0; for(; ips != nil; ips = tl ips){ (nil, ipa) := IPaddr.parse(hd ips); a[na++:] = ipa.v4(); } c.options[n] = a; } Bootconf.puts(c: self ref Bootconf, n: int, s: string) { if(n < 0 || n >= len c.options) return; c.options[n] = array of byte s; } # # # DHCP # # # BOOTP operations Bootprequest, Bootpreply: con 1+iota; # DHCP operations NotDHCP, Discover, Offer, Request, Decline, Ack, Nak, Release, Inform: con iota; Dhcp: adt { udphdr: array of byte; op: int; htype: int; hops: int; xid: int; secs: int; flags: int; ciaddr: IPaddr; yiaddr: IPaddr; siaddr: IPaddr; giaddr: IPaddr; chaddr: array of byte; sname: string; file: string; options: list of (int, array of byte); dhcpop: int; }; opnames := array[] of { Discover => "Discover", Offer => "Offer", Request => "Request", Decline => "Decline", Ack => "Ack", Nak => "Nak", Release => "Release", Inform => "Inform" }; opname(op: int): string { if(op >= 0 && op < len opnames) return opnames[op]; return sys->sprint("OP%d", op); } stringget(buf: array of byte): string { for(x := 0; x < len buf; x++) if(buf[x] == byte 0) break; if(x == 0) return nil; return string buf[0 : x]; } eqbytes(b1: array of byte, b2: array of byte): int { l := len b1; if(l != len b2) return 0; for(i := 0; i < l; i++) if(b1[i] != b2[i]) return 0; return 1; } magic := array[] of {byte 99, byte 130, byte 83, byte 99}; # RFC2132 (replacing RFC1048) dhcpsend(fd: ref Sys->FD, xid: int, dhcp: ref Dhcp) { dhcp.xid = xid; abuf := array[576+Udphdrlen] of {* => byte 0}; abuf[0:] = dhcp.udphdr; buf := abuf[Udphdrlen:]; buf[0] = byte dhcp.op; buf[1] = byte dhcp.htype; buf[2] = byte len dhcp.chaddr; buf[3] = byte dhcp.hops; put4(buf, 4, xid); put2(buf, 8, dhcp.secs); put2(buf, 10, dhcp.flags); buf[12:] = dhcp.ciaddr.v4(); buf[16:] = dhcp.yiaddr.v4(); buf[20:] = dhcp.siaddr.v4(); buf[24:] = dhcp.giaddr.v4(); buf[28:] = dhcp.chaddr; buf[44:] = array of byte dhcp.sname; # [64] buf[108:] = array of byte dhcp.file; # [128] o := 236; # RFC1542 suggests including magic and Oend as a minimum, even in BOOTP buf[o:] = magic; o += 4; if(dhcp.dhcpop != NotDHCP){ buf[o++] = byte Otype; buf[o++] = byte 1; buf[o++] = byte dhcp.dhcpop; } for(ol := dhcp.options; ol != nil; ol = tl ol){ (opt, val) := hd ol; buf[o++] = byte opt; buf[o++] = byte len val; if(len val > 0){ buf[o:] = val; o += len val; } } buf[o++] = byte Oend; if(debug) dumpdhcp(dhcp, "->"); sys->write(fd, abuf, len abuf); } kill(pid: int, grp: string) { fd := sys->open("#p/" + string pid + "/ctl", sys->OWRITE); if(fd != nil) sys->fprint(fd, "kill%s", grp); } v4text(a: array of byte): string { return sys->sprint("%ud.%ud.%ud.%ud", int a[0], int a[1], int a[2], int a[3]); } parseopt(a: array of byte, isdhcp: int): (int, list of (int, array of byte)) { opts: list of (int, array of byte); xop := NotDHCP; for(i := 0; i < len a;){ op := int a[i++]; if(op == Opad) continue; if(op == Oend || i >= len a) break; l := int a[i++]; if(i+l > len a) break; if(isdhcp && op == Otype) xop = int a[i]; else opts = (op, a[i:i+l]) :: opts; i += l; } rl := opts; opts = nil; for(; rl != nil; rl = tl rl) opts = hd rl :: opts; return (xop, opts); } dhcpreader(pidc: chan of int, srv: ref DhcpIO) { pidc <-= sys->pctl(0, nil); for(;;){ abuf := array [576+Udphdrlen] of byte; n := sys->read(srv.fd, abuf, len abuf); if(n < 0){ if(debug) sys->print("read error: %r\n"); sys->sleep(1000); continue; } if(n < Udphdrlen+236){ if(debug) sys->print("short read: %d\n", n); continue; } buf := abuf[Udphdrlen:n]; n -= Udphdrlen; dhcp := ref Dhcp; dhcp.op = int buf[0]; if(dhcp.op != Bootpreply){ if(debug) sys->print("bootp: not reply, discarded\n"); continue; } dhcp.dhcpop = NotDHCP; if(n >= 240 && eqbytes(buf[236:240], magic)) # otherwise it's something we won't understand (dhcp.dhcpop, dhcp.options) = parseopt(buf[240:n], 1); case dhcp.dhcpop { NotDHCP or Ack or Nak or Offer => ; * => if(debug) sys->print("dhcp: ignore dhcp op %d\n", dhcp.dhcpop); continue; } dhcp.udphdr = abuf[0:Udphdrlen]; dhcp.htype = int buf[1]; hlen := int buf[2]; dhcp.hops = int buf[3]; dhcp.xid = get4(buf, 4); dhcp.secs = get2(buf, 8); dhcp.flags = get2(buf, 10); dhcp.ciaddr = IPaddr.newv4(buf[12:]); dhcp.yiaddr = IPaddr.newv4(buf[16:]); dhcp.siaddr = IPaddr.newv4(buf[20:]); dhcp.giaddr = IPaddr.newv4(buf[24:]); dhcp.chaddr = buf[28 : 28 + hlen]; dhcp.sname = stringget(buf[44 : 108]); dhcp.file = stringget(buf[108 : 236]); srv.dc <-= dhcp; } } timeoutstart(msecs: int): (int, chan of int) { tc := chan of int; spawn timeoutproc(tc, msecs); return (<-tc, tc); } timeoutproc(c: chan of int, msecs: int) { c <-= sys->pctl(0, nil); sys->sleep(msecs); c <-= 1; } hex(b: int): int { if(b >= '0' && b <= '9') return b-'0'; if(b >= 'A' && b <= 'F') return b-'A' + 10; if(b >= 'a' && b <= 'f') return b-'a' + 10; return -1; } gethaddr(device: string): (int, string, array of byte) { fd := sys->open(device, Sys->OREAD); if(fd == nil) return (-1, sys->sprint("%r"), nil); buf := array [100] of byte; n := sys->read(fd, buf, len buf); if(n < 0) return (-1, sys->sprint("%r"), nil); if(n == 0) return (-1, "empty address file", nil); addr := array [n/2] of byte; for(i := 0; i < len addr; i++){ u := hex(int buf[2*i]); l := hex(int buf[2*i+1]); if(u < 0 || l < 0) return (-1, "bad address syntax", nil); addr[i] = byte ((u<<4)|l); } return (1, nil, addr); } newrequest(dest: IPaddr, bootfile: string, htype: int, haddr: array of byte, ipaddr: IPaddr, options: array of array of byte): ref Dhcp { dhcp := ref Dhcp; dhcp.op = Bootprequest; hdr := array[Udphdrlen] of {* => byte 0}; hdr[Udpraddr:] = dest.v6(); put2(hdr, Udprport, 67); dhcp.udphdr = hdr; dhcp.htype = htype; dhcp.chaddr = haddr; dhcp.hops = 0; dhcp.secs = 0; dhcp.flags = 0; dhcp.xid = 0; dhcp.ciaddr = ipaddr; dhcp.yiaddr = ip->v4noaddr; dhcp.siaddr = ip->v4noaddr; dhcp.giaddr = ip->v4noaddr; dhcp.file = bootfile; dhcp.dhcpop = NotDHCP; if(options != nil){ for(i := 0; i < len options; i++) if(options[i] != nil) dhcp.options = (i, options[i]) :: dhcp.options; } clientid := array[len haddr + 1] of byte; clientid[0] = byte htype; clientid[1:] = haddr; dhcp.options = (Oclientid, clientid) :: dhcp.options; dhcp.options = (Ovendorclass, array of byte "plan9_386") :: dhcp.options; # 386 will do because type doesn't matter return dhcp; } udpannounce(net: string): (ref Sys->FD, string) { if(net == nil) net = "/net"; conn := dial->announce(net+"/udp!*!68"); if(conn == nil) return (nil, sys->sprint("can't announce dhcp port: %r")); if(sys->fprint(conn.cfd, "headers") < 0) return (nil, sys->sprint("can't set headers mode on dhcp port: %r")); conn.dfd = sys->open(conn.dir+"/data", Sys->ORDWR); if(conn.dfd == nil) return (nil, sys->sprint("can't open %s: %r", conn.dir+"/data")); return (conn.dfd, nil); } ifcnoaddr(fd: ref Sys->FD, s: string) { if(fd != nil && sys->fprint(fd, "%s %s %s", s, (ip->noaddr).text(), (ip->noaddr).text()) < 0){ if(debug) sys->print("dhcp: ctl %s: %r\n", s); } } setup(net: string, device: string, init: ref Bootconf): (ref Dhcp, ref DhcpIO, string) { (htype, err, mac) := gethaddr(device); if(htype < 0) return (nil, nil, sys->sprint("can't get hardware MAC address: %s", err)); ciaddr := ip->v4noaddr; if(init != nil && init.ip != nil){ valid: int; (valid, ciaddr) = IPaddr.parse(init.ip); if(valid < 0) return (nil, nil, sys->sprint("invalid ip address: %s", init.ip)); } (dfd, err2) := udpannounce(net); if(err2 != nil) return (nil, nil, err); bootfile: string; options: array of array of byte; if(init != nil){ bootfile = init.bootf; options = init.options; } return (newrequest(ip->v4bcast, bootfile, htype, mac, ciaddr, options), DhcpIO.new(dfd), nil); } # # BOOTP (RFC951) is used by Inferno only during net boots, to get initial IP address and TFTP address and parameters # bootp(net: string, ctlifc: ref Sys->FD, device: string, init: ref Bootconf): (ref Bootconf, string) { (req, srv, err) := setup(net, device, init); if(err != nil) return (nil, err); ifcnoaddr(ctlifc, "add"); rdhcp := exchange(srv, ++xidgen, req, 1<FD, device: string, init: ref Bootconf, needparam: array of int): (ref Bootconf, ref Lease, string) { (req, srv, err) := setup(net, device, init); if(err != nil) return (nil, nil, err); params := defparams; if(needparam != nil){ n := len defparams; params = array[n+len needparam] of byte; params[0:] = defparams; for(i := 0; i < len needparam; i++) params[n+i] = byte needparam[i]; } initopt := (Oparams, params) :: req.options; # RFC2131 requires parameters to be repeated each time lease := ref Lease(0, chan[1] of (ref Bootconf, string)); spawn dhcp1(srv, lease, net, ctlifc, req, init, initopt); bc: ref Bootconf; (bc, err) = <-lease.configs; return (bc, lease, err); } dhcp1(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)) { cfd := -1; if(ctlifc != nil) cfd = ctlifc.fd; lease.pid = sys->pctl(Sys->NEWPGRP|Sys->NEWFD, 1 :: srv.fd.fd :: cfd :: nil); if(ctlifc != nil) ctlifc = sys->fildes(ctlifc.fd); srv.fd = sys->fildes(srv.fd.fd); rep: ref Dhcp; ifcnoaddr(ctlifc, "add"); if(req.ciaddr.isvalid()) rep = reacquire(srv, req, initopt, req.ciaddr); if(rep == nil) rep = askround(srv, req, initopt); srv.rstop(); ifcnoaddr(ctlifc, "remove"); if(rep == nil){ lease.pid = 0; lease.configs <-= (nil, "no response"); exit; } for(;;){ conf := fillbootconf(init, rep); applycfg(net, ctlifc, conf); if(conf.lease == 0){ srv.rstop(); lease.pid = 0; flush(lease.configs); lease.configs <-= (conf, nil); exit; } flush(lease.configs); lease.configs <-= (conf, nil); req.ciaddr = rep.yiaddr; while((rep = tenancy(srv, req, conf.lease)) != nil){ if(rep.dhcpop == Nak || !rep.ciaddr.eq(req.ciaddr)) break; req.udphdr[Udpraddr:] = rep.udphdr[Udpraddr:Udpraddr+IPaddrlen]; conf = fillbootconf(init, rep); } removecfg(net, ctlifc, conf); ifcnoaddr(ctlifc, "add"); while((rep = askround(srv, req, initopt)) == nil){ flush(lease.configs); lease.configs <-= (nil, "no response"); srv.rstop(); sys->sleep(60*1000); } ifcnoaddr(ctlifc, "remove"); } } reacquire(srv: ref DhcpIO, req: ref Dhcp, initopt: list of (int, array of byte), addr: IPaddr): ref Dhcp { # INIT-REBOOT: know an address; try requesting it (once) # TO DO: could use Inform when our address is static but we need a few service parameters if(debug) sys->print("reacquire\n"); req.ciaddr = ip->v4noaddr; rep := request(srv, ++xidgen, req, (Oipaddr, addr.v4()) :: initopt); if(rep != nil && rep.dhcpop == Ack && addr.eq(rep.yiaddr)){ if(debug) sys->print("req: server accepted\n"); req.udphdr[Udpraddr:] = rep.udphdr[Udpraddr:Udpraddr+IPaddrlen]; return rep; } if(debug) sys->print("req: cannot reclaim\n"); return nil; } askround(srv: ref DhcpIO, req: ref Dhcp, initopt: list of (int, array of byte)): ref Dhcp { # INIT if(debug) sys->print("askround\n"); req.ciaddr = ip->v4noaddr; req.udphdr[Udpraddr:] = (ip->v4bcast).v6(); for(retries := 0; retries < 5; retries++){ # SELECTING req.dhcpop = Discover; req.options = initopt; rep := exchange(srv, ++xidgen, req, 1<print("req: server accepted\n"); req.udphdr[Udpraddr:] = rep.udphdr[Udpraddr:Udpraddr+IPaddrlen]; return rep; } } return nil; } request(srv: ref DhcpIO, xid: int, req: ref Dhcp, options: list of (int, array of byte)): ref Dhcp { req.dhcpop = Request; # Selecting req.options = options; rep := exchange(srv, xid, req, (1<v4bcast).v6(); # now try broadcast return renewing(srv, req, t2, t3); } renewing(srv: ref DhcpIO, req: ref Dhcp, a: big, b: big): ref Dhcp { Minute: con big(60*1000); while(a < b){ rep := exchange(srv, req.xid, req, (1< big 0){ n := msec; if(n > Day) n = Day; sys->sleep(int n); msec -= n; } } getlease(m: ref Dhcp): array of byte { lease := getopt(m.options, Olease, 4); if(lease == nil) return nil; if(get4(lease, 0) == 0){ lease = array[4] of byte; put4(lease, 0, 15*60); } return lease; } fillbootconf(init: ref Bootconf, pkt: ref Dhcp): ref Bootconf { bc := ref Bootconf; if(init != nil) *bc = *init; if(bc.options == nil) bc.options = array[256] of array of byte; for(l := pkt.options; l != nil; l = tl l){ (c, v) := hd l; if(bc.options[c] == nil) bc.options[c] = v; # give priority to first occurring } if((a := bc.get(Ovendorinfo)) != nil){ if(bc.vendor == nil) bc.vendor = array[256] of array of byte; for(l = parseopt(a, 0).t1; l != nil; l = tl l){ (c, v) := hd l; if(bc.vendor[c] == nil) bc.vendor[c] = v; } } if(pkt.yiaddr.isvalid()){ bc.ip = pkt.yiaddr.text(); bc.ipmask = bc.getip(Omask); if(bc.ipmask == nil) bc.ipmask = pkt.yiaddr.classmask().masktext(); } bc.bootf = pkt.file; bc.dhcpip = IPaddr.newv6(pkt.udphdr[Udpraddr:]).text(); bc.siaddr = pkt.siaddr.text(); bc.lease = bc.getint(Olease); if(bc.lease == Infinite) bc.lease = 0; else if(debug > 1) bc.lease = 2*60; # shorten time, for testing bc.dom = bc.gets(Odomainname); s := bc.gets(Ohostname); for(i:=0; i ; * => ; } } DhcpIO: adt { fd: ref Sys->FD; pid: int; dc: chan of ref Dhcp; new: fn(fd: ref Sys->FD): ref DhcpIO; rstart: fn(io: self ref DhcpIO); rstop: fn(io: self ref DhcpIO); }; DhcpIO.new(fd: ref Sys->FD): ref DhcpIO { return ref DhcpIO(fd, 0, chan of ref Dhcp); } DhcpIO.rstart(io: self ref DhcpIO) { if(io.pid == 0){ pids := chan of int; spawn dhcpreader(pids, io); io.pid = <-pids; } } DhcpIO.rstop(io: self ref DhcpIO) { if(io.pid != 0){ kill(io.pid, ""); io.pid = 0; } } getopt(options: list of (int, array of byte), op: int, minlen: int): array of byte { for(; options != nil; options = tl options){ (opt, val) := hd options; if(opt == op && len val >= minlen) return val; } return nil; } exchange(srv: ref DhcpIO, xid: int, req: ref Dhcp, accept: int): ref Dhcp { srv.rstart(); nsec := 3; for(count := 0; count < 5; count++) { (tpid, tc) := timeoutstart(nsec*1000); dhcpsend(srv.fd, xid, req); Wait: for(;;){ alt { <-tc=> break Wait; rep := <-srv.dc=> if(debug) dumpdhcp(rep, "<-"); if(rep.op == Bootpreply && rep.xid == req.xid && rep.ciaddr.eq(req.ciaddr) && eqbytes(rep.chaddr, req.chaddr)){ if((accept & (1<print("req: unexpected reply %s to %s\n", opname(rep.dhcpop), opname(req.dhcpop)); continue; } kill(tpid, ""); return rep; } if(debug) sys->print("req: mismatch\n"); } } req.secs += nsec; nsec++; } return nil; } applycfg(net: string, ctlfd: ref Sys->FD, bc: ref Bootconf): string { # write addresses to /net/... # local address, mask[or default], remote address [mtu] if(net == nil) net = "/net"; if(bc.ip == nil) return "invalid address"; if(ctlfd != nil){ if(sys->fprint(ctlfd, "add %s %s", bc.ip, bc.ipmask) < 0) # TO DO: [raddr [mtu]] return sys->sprint("add interface: %r"); # could use "mtu n" request to set/change mtu } # if primary: # add default route if gateway valid # put ndb entries ip=, ipmask=, ipgw=; sys= dom=; fs=; auth=; dns=; ntp=; other options from bc.options if(bc.ipgw != nil){ fd := sys->open(net+"/iproute", Sys->OWRITE); if(fd != nil) sys->fprint(fd, "add 0 0 %s", bc.ipgw); } s := sys->sprint("ip=%s ipmask=%s", bc.ip, bc.ipmask); if(bc.ipgw != nil) s += sys->sprint(" ipgw=%s", bc.ipgw); s += "\n"; if(bc.sys != nil) s += sys->sprint(" sys=%s\n", bc.sys); if(bc.dom != nil) s += sys->sprint(" dom=%s.%s\n", bc.sys, bc.dom); if((addr := bc.getip(OP9auth)) != nil) s += sys->sprint(" auth=%s\n", addr); # TO DO: several addresses if((addr = bc.getip(OP9fs)) != nil) s += sys->sprint(" fs=%s\n", addr); if((addr = bc.getip(Odnsserver)) != nil) s += sys->sprint(" dns=%s\n", addr); fd := sys->open(net+"/ndb", Sys->OWRITE | Sys->OTRUNC); if(fd != nil){ a := array of byte s; sys->write(fd, a, len a); } return nil; } removecfg(nil: string, ctlfd: ref Sys->FD, bc: ref Bootconf): string { # remove localaddr, localmask[or default] if(ctlfd != nil){ if(sys->fprint(ctlfd, "remove %s %s", bc.ip, bc.ipmask) < 0) return sys->sprint("remove address: %r"); } bc.ip = nil; bc.ipgw = nil; bc.ipmask = nil; # remote address? # clear net+"/ndb"? return nil; } # # the following is just for debugging # dumpdhcp(m: ref Dhcp, dir: string) { s := ""; sys->print("%s %s/%ud: ", dir, IPaddr.newv6(m.udphdr[Udpraddr:]).text(), get2(m.udphdr, Udprport)); if(m.dhcpop != NotDHCP) s = " "+opname(m.dhcpop); sys->print("op %d%s htype %d hops %d xid %ux\n", m.op, s, m.htype, m.hops, m.xid); sys->print("\tsecs %d flags 0x%.4ux\n", m.secs, m.flags); sys->print("\tciaddr %s\n", m.ciaddr.text()); sys->print("\tyiaddr %s\n", m.yiaddr.text()); sys->print("\tsiaddr %s\n", m.siaddr.text()); sys->print("\tgiaddr %s\n", m.giaddr.text()); sys->print("\tchaddr "); for(x := 0; x < len m.chaddr; x++) sys->print("%2.2ux", int m.chaddr[x]); sys->print("\n"); if(m.sname != nil) sys->print("\tsname %s\n", m.sname); if(m.file != nil) sys->print("\tfile %s\n", m.file); if(m.options != nil){ sys->print("\t"); printopts(m.options, opts); sys->print("\n"); } } Optbytes, Optaddr, Optmask, Optint, Optstr, Optopts, Opthex: con iota; Opt: adt { code: int; name: string; otype: int; }; opts: array of Opt = array[] of { (Omask, "ipmask", Optmask), (Orouter, "ipgw", Optaddr), (Odnsserver, "dns", Optaddr), (Ohostname, "hostname", Optstr), (Odomainname, "domain", Optstr), (Ontpserver, "ntp", Optaddr), (Oipaddr, "requestedip", Optaddr), (Olease, "lease", Optint), (Oserverid, "serverid", Optaddr), (Otype, "dhcpop", Optint), (Ovendorclass, "vendorclass", Optstr), (Ovendorinfo, "vendorinfo", Optopts), (Onetbiosns, "wins", Optaddr), (Opop3server, "pop3", Optaddr), (Osmtpserver, "smtp", Optaddr), (Owwwserver, "www", Optaddr), (Oparams, "params", Optbytes), (Otftpserver, "tftp", Optaddr), (Oclientid, "clientid", Opthex), }; p9opts: array of Opt = array[] of { (OP9fs, "fs", Optaddr), (OP9auth, "auth", Optaddr), }; lookopt(optab: array of Opt, code: int): (int, string, int) { for(i:=0; iprint("(%d %d", code, len val); (nil, name, otype) := lookopt(opts, code); if(name == nil){ for(v := 0; v < len val; v++) sys->print(" %d", int val[v]); }else{ sys->print(" %s", name); case otype { Optbytes => for(v := 0; v < len val; v++) sys->print(" %d", int val[v]); Opthex => for(v := 0; v < len val; v++) sys->print(" %#.2ux", int val[v]); Optaddr or Optmask => while(len val >= 4){ sys->print(" %s", v4text(val)); val = val[4:]; } Optstr => sys->print(" \"%s\"", string val); Optint => n := 0; for(v := 0; v < len val; v++) n = (n<<8) | int val[v]; sys->print(" %d", n); Optopts => printopts(parseopt(val, 0).t1, p9opts); } } sys->print(")"); } }