Add `ipconfig` from Plan 9.
authorDan Cross <crossd@gmail.com>
Mon, 23 May 2016 17:39:13 +0000 (13:39 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Mon, 23 May 2016 19:38:43 +0000 (15:38 -0400)
ipconfig is an IP stack configuration tool.  It acts
as both a frontend to the filesystem-based configuration
dance one does when configuring a Plan 9-derived IP
stack, as well as a DHCP client and IPv6 autoconfiguration
agent.

This change is a port of `ipconfig` to Akaros.  Changes
include reformatting the code to match Akaros kernel style,
replacing `alarm` with Akaros system calls, removing
Plan 9- and GNU-specific code and replacing with more
portable idioms, and generally cleaning up.  This has been
tested on hardware for static and DHCP configuration of IPv4
and static configuration of IPv6.  It will be called by
`/ifconfig` if present in the KFS.  Fixes #4.

Change-Id: I233969a8d8efca429a773c7a8667f0bb7e88e552
Signed-off-by: Dan Cross <crossd@gmail.com>
Issue: 4
Bug: 28668810
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
Makefile
tools/apps/ipconfig/.gitignore [new file with mode: 0644]
tools/apps/ipconfig/Makefile [new file with mode: 0644]
tools/apps/ipconfig/README.md [new file with mode: 0644]
tools/apps/ipconfig/dhcp.h [new file with mode: 0644]
tools/apps/ipconfig/icmp.h [new file with mode: 0644]
tools/apps/ipconfig/ipconfig.h [new file with mode: 0644]
tools/apps/ipconfig/ipv6.c [new file with mode: 0644]
tools/apps/ipconfig/main.c [new file with mode: 0644]

index f47ac4a..3e00fd1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -686,11 +686,13 @@ $(app-dirs-clean):
 PHONY +=  apps-install
 apps-install: $(app-dirs-install)
        @$(call make_as_parent, -C tools/apps/busybox)
+       @$(call make_as_parent, -C tools/apps/ipconfig install)
        @$(call make_as_parent, -C tools/profile/perf install)
 
 PHONY += apps-clean
 apps-clean: $(app-dirs-clean)
        @$(call make_as_parent, -C tools/apps/busybox clean)
+       @$(call make_as_parent, -C tools/apps/ipconfig clean)
        @$(call make_as_parent, -C tools/profile/perf clean)
 
 # Cross Compiler
diff --git a/tools/apps/ipconfig/.gitignore b/tools/apps/ipconfig/.gitignore
new file mode 100644 (file)
index 0000000..1f19b36
--- /dev/null
@@ -0,0 +1 @@
+ipconfig
diff --git a/tools/apps/ipconfig/Makefile b/tools/apps/ipconfig/Makefile
new file mode 100644 (file)
index 0000000..e6aa54e
--- /dev/null
@@ -0,0 +1,31 @@
+include                ../../Makefrag
+
+SOURCES:=      main.c ipv6.c
+
+XCC:=          $(CROSS_COMPILE)gcc
+
+LIBS:=         -lndblib -liplib -lbenchutil
+
+PHONY:=                all
+all:           ipconfig
+
+PHONY+=                ipconfig
+ipconfig:      $(SOURCES)
+               @echo "  CC      ipconfig"
+               $(Q)$(XCC) -O2 -std=gnu99 -o ipconfig $(SOURCES) $(LIBS)
+
+PHONY+=                install
+install:       ipconfig
+               @echo "  IN      ipconfig"
+               $(Q)cp ipconfig $(KFS_ROOT)/bin/ipconfig
+               $(Q)chmod 755 $(KFS_ROOT)/bin/ipconfig
+
+PHONY+=                clean
+clean:
+               @echo "  RM      ipconfig"
+               $(Q)rm -f ipconfig
+
+PHONY+=                mrproper
+mrproper:      clean
+
+.PHONY:                $(PHONY)
diff --git a/tools/apps/ipconfig/README.md b/tools/apps/ipconfig/README.md
new file mode 100644 (file)
index 0000000..43e3ffc
--- /dev/null
@@ -0,0 +1,17 @@
+# ipconfig for Akaros
+
+This is Plan 9 ipconfig(8), ported to Akaros.
+
+ipconfig is an IP stack configuration tool.  It acts as
+both a frontend to the filesystem-based configuration dance
+one does when configuring a Plan 9-derived IP stack, as well
+as a implementing a DHCP client and IPv6 autoconfiguration
+agent.  It will be called from `/ifconfig` if present in the
+KFS.
+
+Changes to Plan 9 `ipconfig` include reformatting the code
+to match Akaros kernel style, replacing `alarm` with Akaros
+system calls, removing Plan 9- and GNU-specific code and
+replacing with more portable idioms and general cleaning up.
+
+Man page: http://plan9.bell-labs.com/magic/man2html/8/ipconfig
diff --git a/tools/apps/ipconfig/dhcp.h b/tools/apps/ipconfig/dhcp.h
new file mode 100644 (file)
index 0000000..3dfa5be
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+// Dynamic Host Configuration Protocol / BOOTP
+enum {
+       OfferTimeout = 60,              // when an offer times out
+       MaxLease = 60 * 60,             // longest lease for dynamic binding
+       MinLease = 15 * 60,             // shortest lease for dynamic binding
+       StaticLease = 30 * 60,  // lease for static binding
+
+       IPUDPHDRSIZE = 28,              // size of an IP plus UDP header
+       MINSUPPORTED = 576,             // biggest IP message the client must support
+
+       // lengths of some bootp fields
+       Maxhwlen = 16,
+       Maxfilelen = 128,
+       Maxoptlen = 312 - 4,
+
+       // bootp types
+       Bootrequest = 1,
+       Bootreply = 2,
+
+       // bootp flags
+       Fbroadcast = 1 << 15,
+
+       // dhcp v4 types
+       Discover = 1,
+       Offer = 2,
+       Request = 3,
+       Decline = 4,
+       Ack = 5,
+       Nak = 6,
+       Release = 7,
+       Inform = 8,
+
+       // bootp option types
+       OBend = 255,
+       OBpad = 0,
+       OBmask = 1,
+       OBtimeoff = 2,
+       OBrouter = 3,
+       OBtimeserver = 4,
+       OBnameserver = 5,
+       OBdnserver = 6,
+       OBlogserver = 7,
+       OBcookieserver = 8,
+       OBlprserver = 9,
+       OBimpressserver = 10,
+       OBrlserver = 11,
+       OBhostname = 12,                // 0x0c
+       OBbflen = 13,
+       OBdumpfile = 14,
+       OBdomainname = 15,
+       OBswapserver = 16,              // 0x10
+       OBrootpath = 17,
+       OBextpath = 18,
+       OBipforward = 19,
+       OBnonlocal = 20,
+       OBpolicyfilter = 21,
+       OBmaxdatagram = 22,
+       OBttl = 23,
+       OBpathtimeout = 24,
+       OBpathplateau = 25,
+       OBmtu = 26,
+       OBsubnetslocal = 27,
+       OBbaddr = 28,
+       OBdiscovermask = 29,
+       OBsupplymask = 30,
+       OBdiscoverrouter = 31,
+       OBrsserver = 32,                // 0x20
+       OBstaticroutes = 33,
+       OBtrailerencap = 34,
+       OBarptimeout = 35,
+       OBetherencap = 36,
+       OBtcpttl = 37,
+       OBtcpka = 38,
+       OBtcpkag = 39,
+       OBnisdomain = 40,
+       OBniserver = 41,
+       OBntpserver = 42,
+       OBvendorinfo = 43,              // 0x2b
+       OBnetbiosns = 44,
+       OBnetbiosdds = 45,
+       OBnetbiostype = 46,
+       OBnetbiosscope = 47,
+       OBxfontserver = 48,             // 0x30
+       OBxdispmanager = 49,
+       OBnisplusdomain = 64,   // 0x40
+       OBnisplusserver = 65,
+       OBhomeagent = 68,
+       OBsmtpserver = 69,
+       OBpop3server = 70,
+       OBnntpserver = 71,
+       OBwwwserver = 72,
+       OBfingerserver = 73,
+       OBircserver = 74,
+       OBstserver = 75,
+       OBstdaserver = 76,
+
+       // dhcp v4 options
+       ODipaddr = 50,                  // 0x32
+       ODlease = 51,
+       ODoverload = 52,
+       ODtype = 53,                    // 0x35
+       ODserverid = 54,                // 0x36
+       ODparams = 55,                  // 0x37
+       ODmessage = 56,
+       ODmaxmsg = 57,
+       ODrenewaltime = 58,
+       ODrebindingtime = 59,
+       ODvendorclass = 60,
+       ODclientid = 61,                // 0x3d
+       ODtftpserver = 66,
+       ODbootfile = 67,
+
+       ODpxearch = 93,                 // see rfc 4578
+       ODpxeni = 94,
+       ODpxeguid = 97,
+
+       // plan9 vendor info options, v4 addresses only (deprecated)
+       OP9fsv4 = 128,                  // plan9 file servers
+       OP9authv4 = 129,                // plan9 auth servers
+
+       // plan9 vendor info options, textual addresses, thus v4 or v6
+       OP9fs = 130,                    // plan9 file servers
+       OP9auth = 131,                  // plan9 auth servers
+       OP9ipaddr = 132,                // client's address
+       OP9ipmask = 133,                // client's subnet mask
+       OP9ipgw = 134,                  // client's gateway
+       // OP9dns = 135,                // dns servers
+};
+
+// a lease that never expires
+#define Lforever ~0UL
+
+// dhcp states
+enum {
+       Sinit,
+       Sselecting,
+       Srequesting,
+       Sbound,
+       Srenewing,
+       Srebinding,
+};
+
+struct bootp {
+       // Udphdr (included because of structure alignment on the alpha)
+       uint8_t udphdr[Udphdrsize];
+
+       uint8_t op;                                             // opcode
+       uint8_t htype;                                  // hardware type
+       uint8_t hlen;                                   // hardware address len
+       uint8_t hops;                                   // hops
+       uint8_t xid[4];                                 // a random number
+       uint8_t secs[2];                                // elapsed since client started booting
+       uint8_t flags[2];
+       uint8_t ciaddr[IPv4addrlen];    // client IP address (client tells server)
+       uint8_t yiaddr[IPv4addrlen];    // client IP address (server tells client)
+       uint8_t siaddr[IPv4addrlen];    // server IP address
+       uint8_t giaddr[IPv4addrlen];    // gateway IP address
+       uint8_t chaddr[Maxhwlen];               // client hardware address
+       char sname[64];                                 // server host name (optional)
+       char file[Maxfilelen];                  // boot file name
+       uint8_t optmagic[4];
+       uint8_t optdata[Maxoptlen];
+};
diff --git a/tools/apps/ipconfig/icmp.h b/tools/apps/ipconfig/icmp.h
new file mode 100644 (file)
index 0000000..68f8f2f
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+// ICMP for IP v4 and v6
+enum {
+       // Packet Types, icmp v4 (rfc 792)
+       EchoReply = 0,
+       Unreachable = 3,
+       SrcQuench = 4,
+       Redirect = 5,
+       EchoRequest = 8,
+       TimeExceed = 11,
+       InParmProblem = 12,
+       Timestamp = 13,
+       TimestampReply = 14,
+       InfoRequest = 15,
+       InfoReply = 16,
+       AddrMaskRequest = 17,
+       AddrMaskReply = 18,
+       Traceroute = 30,
+       IPv6WhereAreYou = 33,
+       IPv6IAmHere = 34,
+
+       // packet types, icmp v6 (rfc 2463)
+
+       // error messages
+       UnreachableV6 = 1,
+       PacketTooBigV6 = 2,
+       TimeExceedV6 = 3,
+       ParamProblemV6 = 4,
+
+       // informational messages (rfc 2461 also)
+       EchoRequestV6 = 128,
+       EchoReplyV6 = 129,
+       RouterSolicit = 133,
+       RouterAdvert = 134,
+       NbrSolicit = 135,
+       NbrAdvert = 136,
+       RedirectV6 = 137,
+
+       Maxtype6 = 137,
+
+       ICMP_HDRSIZE = 8,
+};
diff --git a/tools/apps/ipconfig/ipconfig.h b/tools/apps/ipconfig/ipconfig.h
new file mode 100644 (file)
index 0000000..12c3a75
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+struct conf {
+       // locally generated
+       char *type;
+       char *dev;
+       char mpoint[32];
+       int cfd;                                        // ifc control channel
+       int dfd;                                        // ifc data channel (for ppp)
+       char *cputype;
+       uint8_t hwa[32];                        // hardware address
+       int hwatype;
+       int hwalen;
+       uint8_t cid[32];
+       int cidlen;
+       char *baud;
+
+       // learned info
+       uint8_t gaddr[IPaddrlen];
+       uint8_t laddr[IPaddrlen];
+       uint8_t mask[IPaddrlen];
+       uint8_t raddr[IPaddrlen];
+       uint8_t dns[2 * IPaddrlen];
+       uint8_t fs[2 * IPaddrlen];
+       uint8_t auth[2 * IPaddrlen];
+       uint8_t ntp[IPaddrlen];
+       int mtu;
+
+       // dhcp specific
+       int state;
+       int fd;
+       uint32_t xid;
+       uint32_t starttime;
+       char sname[64];
+       char hostname[32];
+       char domainname[64];
+       uint8_t server[IPaddrlen];      // server IP address
+       uint32_t offered;                       // offered lease time
+       uint32_t lease;                         // lease time
+       uint32_t resend;                        // # of resends for current state
+       uint32_t timeout;                       // time to timeout - seconds
+
+       //
+       // IPv6
+       //
+
+       // solicitation specific - XXX add support for IPv6 leases
+       // ulong        solicit_retries;
+
+       // router-advertisement related
+       uint8_t sendra;
+       uint8_t recvra;
+       uint8_t mflag;
+       uint8_t oflag;
+       int maxraint;                           // rfc2461, p.39:
+                                                               // 4sec ≤ maxraint ≤ 1800sec, def 600
+       int minraint;                           // 3sec ≤ minraint ≤ 0.75*maxraint
+       int linkmtu;
+       int reachtime;                          // 3,600,000 msec, default 0
+       int rxmitra;                            // default 0
+       int ttl;                                        // default 0 (unspecified)
+
+       // default gateway params
+       uint8_t v6gaddr[IPaddrlen];
+       int routerlt;                           // router life time
+
+       // prefix related
+       uint8_t v6pref[IPaddrlen];
+       int prefixlen;
+       uint8_t onlink;                         // flag: address is `on-link'
+       uint8_t autoflag;                       // flag: autonomous
+       uint32_t validlt;                       // valid lifetime (seconds)
+       uint32_t preflt;                        // preferred lifetime (seconds)
+};
+
+struct ctl {
+       struct ctl *next;
+       char *ctl;
+};
+
+extern struct ctl *firstctl, **ctll;
+extern struct conf conf;
+extern int noconfig;
+extern int ipv6auto;
+extern int debug;
+extern int dodhcp;
+extern int dolog;
+extern int nip;
+extern int plan9;
+extern int dupl_disc;
+extern int myifc;
+extern char *vs;
+
+void adddefroute(char *, uint8_t *);
+int addoption(char *opt);
+void binddevice(void);
+void bootprequest(void);
+void controldevice(void);
+void dhcpquery(int, int);
+void dhcprecv(void);
+void dhcpsend(int);
+int dhcptimer(void);
+void dhcpwatch(int);
+void doadd(int);
+void doremove(void);
+void dounbind(void);
+void ea2lla(uint8_t *lla, uint8_t *ea);
+int getndb(void);
+void getoptions(uint8_t *p);
+int ip4cfg(void);
+int ip6cfg(int autoconf);
+int ipconfig4(void);
+int ipconfig6(int);
+void ipv62smcast(uint8_t *smcast, uint8_t *a);
+long jitter(void);
+void lookforip(char *);
+void mkclientid(void);
+void ndbconfig(void);
+int nipifcs(char *);
+int openlisten(void);
+uint8_t *optadd(uint8_t *, int, void *, int);
+uint8_t *optaddaddr(uint8_t *, int, uint8_t *);
+uint8_t *optaddbyte(uint8_t *, int, int);
+uint8_t *optaddstr(uint8_t *, int, char *);
+uint8_t *optaddulong(uint8_t *, int, uint32_t);
+uint8_t *optaddvec(uint8_t *, int, uint8_t *, int);
+uint8_t *optget(uint8_t *, int, int *);
+int optgetaddr(uint8_t *, int, uint8_t *);
+int optgetaddrs(uint8_t *, int, uint8_t *, int);
+int optgetbyte(uint8_t *, int);
+int optgetp9addrs(uint8_t *ap, int op, uint8_t *ip, int n);
+int optgetstr(uint8_t *, int, char *, int);
+uint32_t optgetulong(uint8_t *, int);
+int optgetvec(uint8_t *, int, uint8_t *, int);
+struct bootp *parsebootp(uint8_t *p, int n);
+int parseoptions(uint8_t *p, int n);
+int parseverb(char *);
+void procsetname(char *fmt, ...);
+void putndb(void);
+uint32_t randint(uint32_t low, uint32_t hi);
+void tweakservers(void);
+void usage(void);
+int validip(uint8_t *);
+void warning(char *fmt, ...);
+void writendb(char *, int, int);
+
+/*
+ * IPv6
+ */
+void doipv6(int);
+int ipconfig6(int);
+void recvra6(void);
+void sendra6(void);
+void v6paraminit(struct conf *);
+
+enum {
+       IsRouter = 1,
+       IsHostRecv = 2,
+       IsHostNoRecv = 3,
+
+       MAClen = 6,
+
+       IPv4 = 4,
+       IPv6 = 6,
+       Defmtu = 1400,
+
+       IP_HOPBYHOP = 0,
+       ICMPv4 = 1,
+       IP_IGMPPROTO = 2,
+       IP_TCPPROTO = 6,
+       IP_UDPPROTO = 17,
+       IP_ILPROTO = 40,
+       IP_v6ROUTE = 43,
+       IP_v6FRAG = 44,
+       IP_IPsecESP = 50,
+       IP_IPsecAH = 51,
+       IP_v6NOMORE = 59,
+       ICMP6_RS = 133,
+       ICMP6_RA = 134,
+
+       IP_IN_IP = 41,
+};
+
+enum {
+       MFMASK = 1 << 7,
+       OCMASK = 1 << 6,
+       OLMASK = 1 << 7,
+       AFMASK = 1 << 6,
+};
+
+enum {
+       MAXTTL = 255,
+       D64HLEN = IPV6HDR_LEN - IPV4HDR_LEN,
+       IP_MAX = 32 * 1024,
+};
+
+struct routersol {
+       uint8_t vcf[4];                         // version:4, traffic class:8, flow label:20
+       uint8_t ploadlen[2];            // payload length: packet length - 40
+       uint8_t proto;                          // next header  type
+       uint8_t ttl;                            // hop limit
+       uint8_t src[IPaddrlen];
+       uint8_t dst[IPaddrlen];
+       uint8_t type;
+       uint8_t code;
+       uint8_t cksum[2];
+       uint8_t res[4];
+};
+
+struct routeradv {
+       uint8_t vcf[4];                         // version:4, traffic class:8, flow label:20
+       uint8_t ploadlen[2];            // payload length: packet length - 40
+       uint8_t proto;                          // next header type
+       uint8_t ttl;                            // hop limit
+       uint8_t src[IPaddrlen];
+       uint8_t dst[IPaddrlen];
+       uint8_t type;
+       uint8_t code;
+       uint8_t cksum[2];
+       uint8_t cttl;
+       uint8_t mor;
+       uint8_t routerlt[2];
+       uint8_t rchbltime[4];
+       uint8_t rxmtimer[4];
+};
+
+struct lladdropt {
+       uint8_t type;
+       uint8_t len;
+       uint8_t lladdr[MAClen];
+};
+
+struct prefixopt {
+       uint8_t type;
+       uint8_t len;
+       uint8_t plen;
+       uint8_t lar;
+       uint8_t validlt[4];
+       uint8_t preflt[4];
+       uint8_t reserv[4];
+       uint8_t pref[IPaddrlen];
+};
+
+struct mtuopt {
+       uint8_t type;
+       uint8_t len;
+       uint8_t reserv[2];
+       uint8_t mtu[4];
+};
diff --git a/tools/apps/ipconfig/ipv6.c b/tools/apps/ipconfig/ipv6.c
new file mode 100644 (file)
index 0000000..237f884
--- /dev/null
@@ -0,0 +1,954 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+//
+// ipconfig for IPv6
+//     RS means Router Solicitation
+//     RA means Router Advertisement
+//
+
+#include <benchutil/alarm.h>
+#include <iplib/iplib.h>
+#include <parlib/common.h>
+#include <parlib/parlib.h>
+#include <parlib/uthread.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "icmp.h"
+#include "ipconfig.h"
+
+#define RALOG "v6routeradv"
+
+#define NetS(x) (((uint8_t *)x)[0] << 8 | ((uint8_t *)x)[1])
+#define NetL(x) \
+       (((uint8_t *)x)[0] << 24 | ((uint8_t *)x)[1] << 16 | \
+        ((uint8_t *)x)[2] << 8 | ((uint8_t *)x)[3])
+
+enum {
+       ICMP6LEN = 4,
+};
+
+// ICMP v4 & v6 header
+struct icmphdr {
+       uint8_t type;
+       uint8_t code;
+       uint8_t cksum[2];    // Checksum
+       uint8_t data[];
+};
+
+char *icmpmsg6[Maxtype6 + 1] = {
+        [EchoReply] "EchoReply",
+        [UnreachableV6] "UnreachableV6",
+        [PacketTooBigV6] "PacketTooBigV6",
+        [TimeExceedV6] "TimeExceedV6",
+        [Redirect] "Redirect",
+        [EchoRequest] "EchoRequest",
+        [TimeExceed] "TimeExceed",
+        [InParmProblem] "InParmProblem",
+        [Timestamp] "Timestamp",
+        [TimestampReply] "TimestampReply",
+        [InfoRequest] "InfoRequest",
+        [InfoReply] "InfoReply",
+        [AddrMaskRequest] "AddrMaskRequest",
+        [AddrMaskReply] "AddrMaskReply",
+        [EchoRequestV6] "EchoRequestV6",
+        [EchoReplyV6] "EchoReplyV6",
+        [RouterSolicit] "RouterSolicit",
+        [RouterAdvert] "RouterAdvert",
+        [NbrSolicit] "NbrSolicit",
+        [NbrAdvert] "NbrAdvert",
+        [RedirectV6] "RedirectV6",
+};
+
+static char *icmp6opts[] = {
+        [0] "unknown option",
+        [V6nd_srclladdr] "srcll_addr",
+        [V6nd_targlladdr] "targll_addr",
+        [V6nd_pfxinfo] "prefix",
+        [V6nd_redirhdr] "redirect",
+        [V6nd_mtu] "mtu",
+        [V6nd_home] "home",
+        [V6nd_srcaddrs] "src_addrs",
+        [V6nd_ip] "ip",
+        [V6nd_rdns] "rdns",
+        [V6nd_9fs] "9fs",
+        [V6nd_9auth] "9auth",
+};
+
+uint8_t v6allroutersL[IPaddrlen] = {
+    0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02
+};
+
+uint8_t v6allnodesL[IPaddrlen] = {
+    0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01
+};
+
+uint8_t v6Unspecified[IPaddrlen] = {
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+uint8_t v6loopback[IPaddrlen] = {
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
+};
+
+uint8_t v6glunicast[IPaddrlen] = {
+    0x08, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+uint8_t v6linklocal[IPaddrlen] = {
+    0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+uint8_t v6solpfx[IPaddrlen] = {
+    0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+    // last 3 bytes filled with low-order bytes of addr being solicited
+    0xff, 0, 0, 0,
+};
+
+uint8_t v6defmask[IPaddrlen] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+enum {
+       Vadd,
+       Vremove,
+       Vunbind,
+       Vaddpref6,
+       Vra6,
+};
+
+static void ralog(char *fmt, ...)
+{
+       char msg[512];
+       va_list arg;
+
+       va_start(arg, fmt);
+       vsnprintf(msg, sizeof(msg), fmt, arg);
+       va_end(arg);
+       fprintf(stderr, RALOG ": %s\n", msg);
+}
+
+void ea2lla(uint8_t *lla, uint8_t *ea)
+{
+       assert(IPaddrlen == 16);
+       memset(lla, 0, IPaddrlen);
+       lla[0] = 0xFE;
+       lla[1] = 0x80;
+       lla[8] = ea[0] | 0x2;
+       lla[9] = ea[1];
+       lla[10] = ea[2];
+       lla[11] = 0xFF;
+       lla[12] = 0xFE;
+       lla[13] = ea[3];
+       lla[14] = ea[4];
+       lla[15] = ea[5];
+}
+
+void ipv62smcast(uint8_t *smcast, uint8_t *a)
+{
+       assert(IPaddrlen == 16);
+       memset(smcast, 0, IPaddrlen);
+       smcast[0] = 0xFF;
+       smcast[1] = 0x02;
+       smcast[11] = 0x1;
+       smcast[12] = 0xFF;
+       smcast[13] = a[13];
+       smcast[14] = a[14];
+       smcast[15] = a[15];
+}
+
+void v6paraminit(struct conf *cf)
+{
+       cf->sendra = cf->recvra = 0;
+       cf->mflag = 0;
+       cf->oflag = 0;
+       cf->maxraint = Maxv6initraintvl;
+       cf->minraint = Maxv6initraintvl / 4;
+       cf->linkmtu = 1500;
+       cf->reachtime = V6reachabletime;
+       cf->rxmitra = V6retranstimer;
+       cf->ttl = MAXTTL;
+
+       cf->routerlt = 0;
+
+       cf->prefixlen = 64;
+       cf->onlink = 0;
+       cf->autoflag = 0;
+       cf->validlt = cf->preflt = ~0L;
+}
+
+static char *optname(unsigned opt)
+{
+       static char buf[32];
+
+       if (opt >= COUNT_OF(icmp6opts) || icmp6opts[opt] == NULL) {
+               snprintf(buf, sizeof(buf), "unknown option %d", opt);
+               return buf;
+       }
+
+       return icmp6opts[opt];
+}
+
+size_t opt_sprint(uint8_t *ps, uint8_t *pe, char *buf, size_t size)
+{
+       int otype, osz, pktsz;
+       uint8_t *a;
+       size_t n;
+
+       a = ps;
+       n = 0;
+       for (pktsz = pe - ps; pktsz > 0; pktsz -= osz) {
+               otype = a[0];
+               osz = a[1] * 8;
+
+               switch (otype) {
+               default:
+                       n += snprintf(buf + n, size - n, " option=%s ", optname(otype));
+               case V6nd_srclladdr:
+               case V6nd_targlladdr:
+                       if (pktsz < osz || osz != 8) {
+                               n += snprintf(buf + n, size - n,
+                                             " option=%s bad size=%d",
+                                             optname(otype), osz);
+                               return n;
+                       }
+                       n += snprintf(buf + n, size - n, " option=%s maddr=%E",
+                                     optname(otype), a + 2);
+                       break;
+               case V6nd_pfxinfo:
+                       if (pktsz < osz || osz != 32) {
+                               n += snprintf(buf + n, size - n,
+                                             " option=%s: bad size=%d",
+                                             optname(otype), osz);
+                               return n;
+                       }
+                       n += snprintf(buf, size - n,
+                                     " option=%s pref=%R preflen=%3.3d lflag=%1.1d aflag=%1.1d unused1=%1.1d validlt=%ud preflt=%ud unused2=%1.1d",
+                                     optname(otype),
+                                     a + 16,
+                                     (int)(*(a + 2)),
+                                     (*(a + 3) & (1 << 7)) != 0,
+                                     (*(a + 3) & (1 << 6)) != 0,
+                                     (*(a + 3) & 63) != 0,
+                                     NetL(a + 4),
+                                     NetL(a + 8),
+                                     NetL(a + 12) != 0);
+                       break;
+               }
+               a += osz;
+       }
+
+       return n;
+}
+
+static void pkt2str(uint8_t *ps, uint8_t *pe, char *buf, size_t size)
+{
+       int pktlen;
+       char *tn;
+       uint8_t *a;
+       struct icmphdr *h;
+       size_t n;
+
+       h = (struct icmphdr *)ps;
+       a = ps + 4;
+
+       pktlen = pe - ps;
+       if (pktlen < ICMP6LEN) {
+               snprintf(buf, size, "short pkt");
+               return;
+       }
+
+       tn = icmpmsg6[h->type];
+       if (tn == NULL)
+               n = snprintf(buf, size, "t=%ud c=%d ck=%4.4ux",
+                            h->type, h->code, (uint16_t)NetS(h->cksum));
+       else
+               n = snprintf(buf, size, "t=%s c=%d ck=%4.4ux",
+                            tn, h->code, (uint16_t)NetS(h->cksum));
+
+       switch (h->type) {
+       case RouterSolicit:
+               ps += 8;
+               n += snprintf(buf + n, size - n, " unused=%1.1d ", NetL(a) != 0);
+               opt_sprint(ps, pe, buf + n, size - n);
+               break;
+       case RouterAdvert:
+               ps += 16;
+               n += snprintf(buf + n, size - n,
+                             " hoplim=%3.3d mflag=%1.1d oflag=%1.1d unused=%1.1d routerlt=%d reachtime=%d rxmtimer=%d",
+                             a[0],
+                             (*(a + 1) & (1 << 7)) != 0,
+                             (*(a + 1) & (1 << 6)) != 0,
+                             (*(a + 1) & 63) != 0,
+                             NetS(a + 2),
+                             NetL(a + 4),
+                             NetL(a + 8));
+               opt_sprint(ps, pe, buf + n, size - n);
+               break;
+       default:
+               snprintf(buf + n, size - n, " unexpected icmp6 pkt type");
+               break;
+       }
+}
+
+int dialicmp(uint8_t *dst, int dport, int *ctlfd)
+{
+       int fd, cfd, n, m;
+       char cmsg[100], name[128], connind[40];
+       char hdrs[] = "headers";
+
+       snprintf(name, sizeof(name), "%s/icmpv6/clone", conf.mpoint);
+       cfd = open(name, O_RDWR);
+       if (cfd < 0) {
+               fprintf(stderr, "dialicmp: can't open %s: %r", name);
+               exit(-1);
+       }
+
+       n = snprintf(cmsg, sizeof(cmsg), "connect %R!%d!r %d", dst, dport, dport);
+       m = write(cfd, cmsg, n);
+       if (m < n) {
+               fprintf(stderr, "dialicmp: can't write %s to %s: %r", cmsg, name);
+               exit(-1);
+       }
+
+       lseek(cfd, 0, 0);
+       n = read(cfd, connind, sizeof(connind));
+       if (n < 0)
+               connind[0] = 0;
+       else if (n < sizeof(connind))
+               connind[n] = 0;
+       else
+               connind[sizeof(connind) - 1] = 0;
+
+       snprintf(name, sizeof(name), "%s/icmpv6/%s/data", conf.mpoint, connind);
+       fd = open(name, O_RDWR);
+       if (fd < 0) {
+               fprintf(stderr, "dialicmp: can't open %s: %r", name);
+               exit(-1);
+       }
+
+       n = sizeof(hdrs) - 1;
+       if (write(cfd, hdrs, n) < n) {
+               fprintf(stderr, "dialicmp: can't write `%s' to %s: %r", hdrs, name);
+               exit(-1);
+       }
+       *ctlfd = cfd;
+       return fd;
+}
+
+// Add an IPv6 address to an interface.
+int ip6cfg(int autoconf)
+{
+       int dupfound = 0, n;
+       char *p, *s;
+       char buf[256], line[1024];
+       uint8_t ethaddr[6];
+       FILE *bp;
+
+       if (autoconf) {
+               // create link-local addr
+               if (myetheraddr(ethaddr, conf.dev) < 0) {
+                       fprintf(stderr, "myetheraddr w/ %s failed: %r", conf.dev);
+                       exit(-1);
+               }
+               ea2lla(conf.laddr, ethaddr);
+       }
+
+       if (dupl_disc)
+               n = snprintf(buf, sizeof(buf), "try");
+       else
+               n = snprintf(buf, sizeof(buf), "add");
+
+       n += snprintf(buf + n, sizeof(buf) - n, " %R", conf.laddr);
+       if (!validip(conf.mask))
+               ipmove(conf.mask, v6defmask);
+       n += snprintf(buf + n, sizeof(buf) - n, " %M", conf.mask);
+       if (validip(conf.raddr)) {
+               n += snprintf(buf + n, sizeof(buf) - n, " %R", conf.raddr);
+               if (conf.mtu != 0)
+                       n += snprintf(buf + n, sizeof(buf) - n, " %d", conf.mtu);
+       }
+
+       if (write(conf.cfd, buf, n) < 0) {
+               warning("write(%s): %r", buf);
+               return -1;
+       }
+
+       if (!dupl_disc)
+               return 0;
+
+       usleep(3000 * 1000);
+
+       /* read arp table, look for addr duplication */
+       snprintf(buf, sizeof(buf), "%s/arp", conf.mpoint);
+       bp = fopen(buf, "r");
+       if (bp == NULL) {
+               warning("couldn't open %s: %r", buf);
+               return -1;
+       }
+
+       snprintf(buf, sizeof(buf), "%R", conf.laddr);
+       for (p = buf; *p != '\0'; p++)
+               if (isascii(*p) && isalpha(*p) && isupper(*p))
+                       *p = tolower(*p);
+       while ((s = fgets(line, sizeof(line), bp)) != NULL) {
+               s[strlen(line) - 1] = 0;
+               for (p = buf; *p != '\0'; p++)
+                       if (isascii(*p) && isalpha(*p) && isupper(*p))
+                               *p = tolower(*p);
+               if (strstr(s, buf) != 0) {
+                       warning("found dup entry in arp cache");
+                       dupfound = 1;
+                       break;
+               }
+       }
+       fclose(bp);
+
+       if (dupfound)
+               doremove();
+       else {
+               n = snprintf(buf, sizeof(buf), "add %R %M", conf.laddr, conf.mask);
+               if (validip(conf.raddr)) {
+                       n += snprintf(buf + n, sizeof(buf) - n, " %R", conf.raddr);
+                       if (conf.mtu != 0)
+                               n += snprintf(buf + n, sizeof(buf) - n, " %d", conf.mtu);
+               }
+               write(conf.cfd, buf, n);
+       }
+       return 0;
+}
+
+static int recvra6on(char *net, int conn)
+{
+       struct ipifc *ifc;
+
+       ifc = readipifc(net, NULL, conn);
+       if (ifc == NULL)
+               return 0;
+       else if (ifc->sendra6 > 0)
+               return IsRouter;
+       else if (ifc->recvra6 > 0)
+               return IsHostRecv;
+       else
+               return IsHostNoRecv;
+}
+
+/* send icmpv6 router solicitation to multicast address for all routers */
+static void sendrs(int fd)
+{
+       struct routersol *rs;
+       uint8_t buff[sizeof(*rs)];
+
+       memset(buff, 0, sizeof(buff));
+       rs = (struct routersol *)buff;
+       memmove(rs->dst, v6allroutersL, IPaddrlen);
+       memmove(rs->src, v6Unspecified, IPaddrlen);
+       rs->type = ICMP6_RS;
+
+       if (write(fd, rs, sizeof(buff)) < sizeof(buff))
+               ralog("sendrs: write failed, pkt size %d", sizeof(buff));
+       else
+               ralog("sendrs: sent solicitation to %R from %R on %s", rs->dst, rs->src,
+                     conf.dev);
+}
+
+/*
+ * a router receiving a router adv from another
+ * router calls this; it is basically supposed to
+ * log the information in the ra and raise a flag
+ * if any parameter value is different from its configured values.
+ *
+ * doing nothing for now since I don't know where to log this yet.
+ */
+static void recvrarouter(uint8_t buf[], int pktlen)
+{
+       ralog("i am a router and got a router advert");
+}
+
+// host receiving a router advertisement calls this
+static void ewrite(int fd, char *str)
+{
+       int n;
+
+       n = strlen(str);
+       if (write(fd, str, n) != n)
+               ralog("write(%s) failed: %r", str);
+}
+
+static void issuebasera6(struct conf *cf)
+{
+       char cfg[256];
+
+       snprintf(cfg, sizeof(cfg),
+                "ra6 mflag %d oflag %d reachtime %d rxmitra %d ttl %d routerlt %d",
+                cf->mflag, cf->oflag, cf->reachtime, cf->rxmitra,
+                cf->ttl, cf->routerlt);
+       ewrite(cf->cfd, cfg);
+}
+
+static void issuerara6(struct conf *cf)
+{
+       char cfg[256];
+
+       snprintf(cfg, sizeof(cfg),
+                "ra6 sendra %d recvra %d maxraint %d minraint %d linkmtu %d",
+                cf->sendra, cf->recvra, cf->maxraint, cf->minraint, cf->linkmtu);
+       ewrite(cf->cfd, cfg);
+}
+
+static void
+issueadd6(struct conf *cf)
+{
+       char cfg[256];
+
+       snprintf(cfg, sizeof(cfg),
+                "add6 %R %d %d %d %lud %lud", cf->v6pref, cf->prefixlen,
+                cf->onlink, cf->autoflag, cf->validlt, cf->preflt);
+       ewrite(cf->cfd, cfg);
+}
+
+static void
+recvrahost(uint8_t buf[], int pktlen)
+{
+       int arpfd, m, n;
+       char abuf[100], *msg;
+       uint8_t optype;
+       struct lladdropt *llao;
+       struct mtuopt *mtuo;
+       struct prefixopt *prfo;
+       struct routeradv *ra;
+       static int first = 1;
+
+       ra = (struct routeradv *)buf;
+       //      memmove(conf.v6gaddr, ra->src, IPaddrlen);
+       conf.ttl = ra->cttl;
+       conf.mflag = (MFMASK & ra->mor);
+       conf.oflag = (OCMASK & ra->mor);
+       conf.routerlt = nhgets(ra->routerlt);
+       conf.reachtime = nhgetl(ra->rchbltime);
+       conf.rxmitra = nhgetl(ra->rxmtimer);
+
+       //      issueadd6(&conf);               /* for conf.v6gaddr? */
+       msg = "ra6 recvra 1";
+       if (write(conf.cfd, msg, strlen(msg)) < 0)
+               ralog("write(ra6 recvra 1) failed: %r");
+       issuebasera6(&conf);
+
+       m = sizeof(*ra);
+       while (pktlen - m > 0) {
+               optype = buf[m];
+               switch (optype) {
+               case V6nd_srclladdr:
+                       llao = (struct lladdropt *)&buf[m];
+                       m += 8 * buf[m + 1];
+                       if (llao->len != 1) {
+                               ralog(
+                                   "recvrahost: illegal len (%d) for source link layer address option",
+                                   llao->len);
+                               return;
+                       }
+                       if (!ISIPV6LINKLOCAL(ra->src)) {
+                               ralog("recvrahost: non-link-local src addr for router adv %R",
+                                     ra->src);
+                               return;
+                       }
+
+                       snprintf(abuf, sizeof(abuf), "%s/arp", conf.mpoint);
+                       arpfd = open(abuf, O_WRONLY);
+                       if (arpfd < 0) {
+                               ralog("recvrahost: couldn't open %s to write: %r", abuf);
+                               return;
+                       }
+
+                       n = snprintf(abuf, sizeof(abuf), "add ether %R %E", ra->src,
+                                    llao->lladdr);
+                       if (write(arpfd, abuf, n) < n)
+                               ralog("recvrahost: couldn't write to %s/arp", conf.mpoint);
+                       close(arpfd);
+                       break;
+               case V6nd_targlladdr:
+               case V6nd_redirhdr:
+                       m += 8 * buf[m + 1];
+                       ralog("ignoring unexpected option type `%s' in Routeradv",
+                             optname(optype));
+                       break;
+               case V6nd_mtu:
+                       mtuo = (struct mtuopt *)&buf[m];
+                       m += 8 * mtuo->len;
+                       conf.linkmtu = nhgetl(mtuo->mtu);
+                       break;
+               case V6nd_pfxinfo:
+                       prfo = (struct prefixopt *)&buf[m];
+                       m += 8 * prfo->len;
+                       if (prfo->len != 4) {
+                               ralog("illegal len (%d) for prefix option", prfo->len);
+                               return;
+                       }
+                       memmove(conf.v6pref, prfo->pref, IPaddrlen);
+                       conf.prefixlen = prfo->plen;
+                       conf.onlink = ((prfo->lar & OLMASK) != 0);
+                       conf.autoflag = ((prfo->lar & AFMASK) != 0);
+                       conf.validlt = nhgetl(prfo->validlt);
+                       conf.preflt = nhgetl(prfo->preflt);
+                       issueadd6(&conf);
+                       if (first) {
+                               first = 0;
+                               ralog("got initial RA from %R on %s; pfx %R", ra->src, conf.dev,
+                                     prfo->pref);
+                       }
+                       break;
+               default:
+                       if (debug)
+                               ralog("ignoring optype %d in Routeradv from %R",
+                                     optype, ra->src);
+               /* fall through */
+               case V6nd_srcaddrs:
+                       /* netsbd sends this, so quietly ignore it for now */
+                       m += 8 * buf[m + 1];
+                       break;
+               }
+       }
+}
+
+/*
+ * daemon to receive router advertisements from routers
+ */
+void recvra6(void)
+{
+       int fd, cfd, n, sendrscnt, sleepfor;
+       uint8_t buf[4096];
+
+       /* TODO: why not v6allroutersL? */
+       fd = dialicmp(v6allnodesL, ICMP6_RA, &cfd);
+       if (fd < 0) {
+               fprintf(stderr, "can't open icmp_ra connection: %r");
+               exit(-1);
+       }
+
+       sendrscnt = Maxv6rss;
+
+       switch (fork()) {
+       case -1:
+               fprintf(stderr, "can't fork: %r");
+               exit(-1);
+       default:
+               return;
+       case 0:
+               break;
+       }
+
+       ralog("recvra6 on %s", conf.dev);
+       sleepfor = jitter();
+       for (;;) {
+               //
+               // We only get 3 (Maxv6rss) tries, so make sure we
+               // wait long enough to be certain that at least one RA
+               // will be transmitted.
+               //
+               struct alarm_waiter waiter;
+
+               init_awaiter(&waiter, alarm_abort_sysc);
+               waiter.data = current_uthread;
+               set_awaiter_rel(&waiter, 1000 * MAX(sleepfor, 7000));
+               set_alarm(&waiter);
+               n = read(fd, buf, sizeof(buf));
+               unset_alarm(&waiter);
+
+               if (n <= 0) {
+                       if (sendrscnt > 0) {
+                               sendrscnt--;
+                               if (recvra6on(conf.mpoint, myifc) == IsHostRecv)
+                                       sendrs(fd);
+                               sleepfor = V6rsintvl + (lrand48() % 100);
+                       }
+                       if (sendrscnt == 0) {
+                               sendrscnt--;
+                               sleepfor = 0;
+                               ralog("recvra6: no router advs after %d sols on %s", Maxv6rss,
+                                     conf.dev);
+                       }
+                       continue;
+               }
+
+               sleepfor = 0;
+               sendrscnt = -1; /* got at least initial ra; no whining */
+               switch (recvra6on(conf.mpoint, myifc)) {
+               case IsRouter:
+                       recvrarouter(buf, n);
+                       break;
+               case IsHostRecv:
+                       recvrahost(buf, n);
+                       break;
+               case IsHostNoRecv:
+                       ralog("recvra6: recvra off, quitting on %s", conf.dev);
+                       close(fd);
+                       exit(0);
+               default:
+                       ralog("recvra6: unable to read router status on %s", conf.dev);
+                       break;
+               }
+       }
+}
+
+/*
+ * return -1 -- error, reading/writing some file,
+ *         0 -- no arp table updates
+ *         1 -- successful arp table update
+ */
+int recvrs(uint8_t *buf, int pktlen, uint8_t *sol)
+{
+       int n, optsz, arpfd;
+       char abuf[256];
+       struct routersol *rs;
+       struct lladdropt *llao;
+
+       rs = (struct routersol *)buf;
+       n = sizeof(*rs);
+       optsz = pktlen - n;
+       pkt2str(buf, buf + pktlen, abuf, sizeof(abuf));
+
+       if (optsz != sizeof(*llao))
+               return 0;
+       if (buf[n] != V6nd_srclladdr || 8 * buf[n + 1] != sizeof(*llao)) {
+               ralog("rs opt err %s", abuf);
+               return -1;
+       }
+
+       ralog("rs recv %s", abuf);
+
+       if (memcmp(rs->src, v6Unspecified, IPaddrlen) == 0)
+               return 0;
+
+       snprintf(abuf, sizeof(abuf), "%s/arp", conf.mpoint);
+       arpfd = open(abuf, O_WRONLY);
+       if (arpfd < 0) {
+               ralog("recvrs: can't open %s/arp to write: %r", conf.mpoint);
+               return -1;
+       }
+
+       llao = (struct lladdropt *)&buf[n];
+       n = snprintf(abuf, sizeof(abuf), "add ether %R %E", rs->src, llao->lladdr);
+       if (write(arpfd, abuf, n) < n) {
+               ralog("recvrs: can't write to %s/arp: %r", conf.mpoint);
+               close(arpfd);
+               return -1;
+       }
+
+       memmove(sol, rs->src, IPaddrlen);
+       close(arpfd);
+       return 1;
+}
+
+void sendra(int fd, uint8_t *dst, int rlt)
+{
+       int pktsz, preflen;
+       char abuf[1024], tmp[64];
+       uint8_t buf[1024], macaddr[6], src[IPaddrlen];
+       struct ipifc *ifc = NULL;
+       struct iplifc *lifc, *nlifc;
+       struct lladdropt *llao;
+       struct prefixopt *prfo;
+       struct routeradv *ra;
+
+       memset(buf, 0, sizeof(buf));
+       ra = (struct routeradv *)buf;
+
+       myetheraddr(macaddr, conf.dev);
+       ea2lla(src, macaddr);
+       memmove(ra->src, src, IPaddrlen);
+       memmove(ra->dst, dst, IPaddrlen);
+       ra->type = ICMP6_RA;
+       ra->cttl = conf.ttl;
+
+       if (conf.mflag > 0)
+               ra->mor |= MFMASK;
+       if (conf.oflag > 0)
+               ra->mor |= OCMASK;
+       if (rlt > 0)
+               hnputs(ra->routerlt, conf.routerlt);
+       else
+               hnputs(ra->routerlt, 0);
+       hnputl(ra->rchbltime, conf.reachtime);
+       hnputl(ra->rxmtimer, conf.rxmitra);
+
+       pktsz = sizeof(*ra);
+
+       /* include all global unicast prefixes on interface in prefix options */
+       ifc = readipifc(conf.mpoint, ifc, myifc);
+       for (lifc = (ifc ? ifc->lifc : NULL); lifc; lifc = nlifc) {
+               nlifc = lifc->next;
+               prfo = (struct prefixopt *)(buf + pktsz);
+               /* global unicast address? */
+               if (!ISIPV6LINKLOCAL(lifc->ip) && !ISIPV6MCAST(lifc->ip) &&
+                   memcmp(lifc->ip, IPnoaddr, IPaddrlen) != 0 &&
+                   memcmp(lifc->ip, v6loopback, IPaddrlen) != 0 && !isv4(lifc->ip)) {
+                       memmove(prfo->pref, lifc->net, IPaddrlen);
+
+                       /* hack to find prefix length */
+                       snprintf(tmp, sizeof(tmp), "%M", lifc->mask);
+                       preflen = atoi(&tmp[1]);
+                       prfo->plen = preflen & 0xff;
+                       if (prfo->plen == 0)
+                               continue;
+
+                       prfo->type = V6nd_pfxinfo;
+                       prfo->len = 4;
+                       prfo->lar = AFMASK;
+                       hnputl(prfo->validlt, lifc->validlt);
+                       hnputl(prfo->preflt, lifc->preflt);
+                       pktsz += sizeof(*prfo);
+               }
+       }
+       /*
+        * include link layer address (mac address for now) in
+        * link layer address option
+        */
+       llao = (struct lladdropt *)(buf + pktsz);
+       llao->type = V6nd_srclladdr;
+       llao->len = 1;
+       memmove(llao->lladdr, macaddr, sizeof(macaddr));
+       pktsz += sizeof(*llao);
+       pkt2str(buf + 40, buf + pktsz, abuf, sizeof(abuf));
+       if (write(fd, buf, pktsz) < pktsz)
+               ralog("sendra fail %s: %r", abuf);
+       else if (debug)
+               ralog("sendra succ %s", abuf);
+}
+
+/*
+ * daemon to send router advertisements to hosts
+ */
+void sendra6(void)
+{
+       int fd, cfd, n, dstknown = 0, sendracnt, sleepfor, nquitmsgs;
+       long lastra, now;
+       uint8_t buf[4096], dst[IPaddrlen];
+       struct ipifc *ifc = NULL;
+
+       fd = dialicmp(v6allnodesL, ICMP6_RS, &cfd);
+       if (fd < 0) {
+               fprintf(stderr, "can't open icmp_rs connection: %r");
+               exit(-1);
+       }
+
+       sendracnt = Maxv6initras;
+       nquitmsgs = Maxv6finalras;
+
+       switch (fork()) {
+       case -1:
+               fprintf(stderr, "can't fork: %r");
+               exit(-1);
+       default:
+               return;
+       case 0:
+               break;
+       }
+
+       ralog("sendra6 on %s", conf.dev);
+       sleepfor = jitter();
+       for (;;) {
+               struct alarm_waiter waiter;
+
+               init_awaiter(&waiter, alarm_abort_sysc);
+               set_awaiter_rel(&waiter, 1000 * MAX(sleepfor, 0));
+               lastra = time(0);
+               set_alarm(&waiter);
+               n = read(fd, buf, sizeof(buf));
+               unset_alarm(&waiter);
+
+               ifc = readipifc(conf.mpoint, ifc, myifc);
+               if (ifc == NULL) {
+                       ralog("sendra6: can't read router params on %s", conf.mpoint);
+                       continue;
+               }
+
+               if (ifc->sendra6 <= 0) {
+                       if (nquitmsgs > 0) {
+                               sendra(fd, v6allnodesL, 0);
+                               nquitmsgs--;
+                               sleepfor = Minv6interradelay + jitter();
+                               continue;
+                       } else {
+                               ralog("sendra6: sendra off, quitting on %s", conf.dev);
+                               exit(0);
+                       }
+               }
+
+               nquitmsgs = Maxv6finalras;
+
+               if (n <= 0) { /* no RS */
+                       if (sendracnt > 0)
+                               sendracnt--;
+               } else { /* respond to RS */
+                       dstknown = recvrs(buf, n, dst);
+                       now = time(0);
+
+                       if (now - lastra < Minv6interradelay) {
+                               /* too close, skip */
+                               sleepfor = lastra + Minv6interradelay + jitter() - now;
+                               continue;
+                       }
+                       usleep(jitter() * 1000);
+               }
+               sleepfor = randint(ifc->rp.minraint, ifc->rp.maxraint);
+               if (dstknown > 0)
+                       sendra(fd, dst, 1);
+               else
+                       sendra(fd, v6allnodesL, 1);
+       }
+}
+
+void startra6(void)
+{
+       static const char routeon[] = "iprouting 1";
+
+       if (conf.recvra > 0)
+               recvra6();
+
+       if (conf.sendra > 0) {
+               if (write(conf.cfd, routeon, sizeof(routeon) - 1) < 0) {
+                       warning("write (iprouting 1) failed: %r");
+                       return;
+               }
+               sendra6();
+               if (conf.recvra <= 0)
+                       recvra6();
+       }
+}
+
+void doipv6(int what)
+{
+       nip = nipifcs(conf.mpoint);
+       if (!noconfig) {
+               lookforip(conf.mpoint);
+               controldevice();
+               binddevice();
+       }
+
+       switch (what) {
+       default:
+               fprintf(stderr, "unknown IPv6 verb\n");
+               exit(-1);
+       case Vaddpref6:
+               issueadd6(&conf);
+               break;
+       case Vra6:
+               issuebasera6(&conf);
+               issuerara6(&conf);
+               startra6();
+               break;
+       }
+}
diff --git a/tools/apps/ipconfig/main.c b/tools/apps/ipconfig/main.c
new file mode 100644 (file)
index 0000000..e57ec17
--- /dev/null
@@ -0,0 +1,1929 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+/*
+ * ipconfig - configure parameters of an ip stack
+ */
+
+#include <benchutil/alarm.h>
+#include <iplib/iplib.h>
+#include <ndblib/ndb.h>
+#include <parlib/common.h>
+#include <parlib/printf-ext.h>
+#include <parlib/uthread.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "dhcp.h"
+#include "ipconfig.h"
+
+#define DEBUG(...) do { if (debug) warning(__VA_ARGS__); } while (0)
+
+/* possible verbs */
+enum {
+       /* commands */
+       Vadd,
+       Vremove,
+       Vunbind,
+       Vaddpref6,
+       Vra6,
+       /* media */
+       Vether,
+       Vgbe,
+       Vloopback,
+       Vtorus,
+       Vtree,
+       Vpkt,
+};
+
+enum {
+       Taddr,
+       Taddrs,
+       Tstr,
+       Tbyte,
+       Tulong,
+       Tvec,
+};
+
+struct option {
+       char *name;
+       int type;
+};
+
+/*
+ * I was too lazy to look up the types for each of these
+ * options.  If someone feels like it, please mail me a
+ * corrected array -- presotto
+ */
+struct option option[256] = {
+    [OBmask] { "ipmask", Taddr },
+    [OBtimeoff] { "timeoff", Tulong },
+    [OBrouter] { "ipgw", Taddrs },
+    [OBtimeserver] { "time", Taddrs },
+    [OBnameserver] { "name", Taddrs },
+    [OBdnserver] { "dns", Taddrs },
+    [OBlogserver] { "log", Taddrs },
+    [OBcookieserver] { "cookie", Taddrs },
+    [OBlprserver] { "lpr", Taddrs },
+    [OBimpressserver] { "impress", Taddrs },
+    [OBrlserver] { "rl", Taddrs },
+    [OBhostname] { "sys", Tstr },
+    [OBbflen] { "bflen", Tulong },
+    [OBdumpfile] { "dumpfile", Tstr },
+    [OBdomainname] { "dom", Tstr },
+    [OBswapserver] { "swap", Taddrs },
+    [OBrootpath] { "rootpath", Tstr },
+    [OBextpath] { "extpath", Tstr },
+    [OBipforward] { "ipforward", Taddrs },
+    [OBnonlocal] { "nonlocal", Taddrs },
+    [OBpolicyfilter] { "policyfilter", Taddrs },
+    [OBmaxdatagram] { "maxdatagram", Tulong },
+    [OBttl] { "ttl", Tulong },
+    [OBpathtimeout] { "pathtimeout", Taddrs },
+    [OBpathplateau] { "pathplateau", Taddrs },
+    [OBmtu] { "mtu", Tulong },
+    [OBsubnetslocal] { "subnetslocal", Taddrs },
+    [OBbaddr] { "baddr", Taddrs },
+    [OBdiscovermask] { "discovermask", Taddrs },
+    [OBsupplymask] { "supplymask", Taddrs },
+    [OBdiscoverrouter] { "discoverrouter", Taddrs },
+    [OBrsserver] { "rs", Taddrs },
+    [OBstaticroutes] { "staticroutes", Taddrs },
+    [OBtrailerencap] { "trailerencap", Taddrs },
+    [OBarptimeout] { "arptimeout", Tulong },
+    [OBetherencap] { "etherencap", Taddrs },
+    [OBtcpttl] { "tcpttl", Tulong },
+    [OBtcpka] { "tcpka", Tulong },
+    [OBtcpkag] { "tcpkag", Tulong },
+    [OBnisdomain] { "nisdomain", Tstr },
+    [OBniserver] { "ni", Taddrs },
+    [OBntpserver] { "ntp", Taddrs },
+    [OBnetbiosns] { "netbiosns", Taddrs },
+    [OBnetbiosdds] { "netbiosdds", Taddrs },
+    [OBnetbiostype] { "netbiostype", Taddrs },
+    [OBnetbiosscope] { "netbiosscope", Taddrs },
+    [OBxfontserver] { "xfont", Taddrs },
+    [OBxdispmanager] { "xdispmanager", Taddrs },
+    [OBnisplusdomain] { "nisplusdomain", Tstr },
+    [OBnisplusserver] { "nisplus", Taddrs },
+    [OBhomeagent] { "homeagent", Taddrs },
+    [OBsmtpserver] { "smtp", Taddrs },
+    [OBpop3server] { "pop3", Taddrs },
+    [OBnntpserver] { "nntp", Taddrs },
+    [OBwwwserver] { "www", Taddrs },
+    [OBfingerserver] { "finger", Taddrs },
+    [OBircserver] { "irc", Taddrs },
+    [OBstserver] { "st", Taddrs },
+    [OBstdaserver] { "stdar", Taddrs },
+    [ODipaddr] { "ipaddr", Taddr },
+    [ODlease] { "lease", Tulong },
+    [ODoverload] { "overload", Taddr },
+    [ODtype] { "type", Tbyte },
+    [ODserverid] { "serverid", Taddr },
+    [ODparams] { "params", Tvec },
+    [ODmessage] { "message", Tstr },
+    [ODmaxmsg] { "maxmsg", Tulong },
+    [ODrenewaltime] { "renewaltime", Tulong },
+    [ODrebindingtime] { "rebindingtime", Tulong },
+    [ODvendorclass] { "vendorclass", Tvec },
+    [ODclientid] { "clientid", Tvec },
+    [ODtftpserver] { "tftp", Taddr },
+    [ODbootfile] { "bootfile", Tstr },
+};
+
+uint8_t defrequested[] = {
+    OBmask, OBrouter, OBdnserver, OBhostname, OBdomainname, OBntpserver,
+};
+
+uint8_t requested[256];
+int nrequested;
+
+char *argv0;
+int Oflag;
+int beprimary = -1;
+struct conf conf;
+int debug;
+int dodhcp;
+int dondbconfig;
+int dupl_disc = 1; /* flag: V6 duplicate neighbor discovery */
+struct ctl *firstctl, **ctll;
+struct ipifc *ifc;
+int ipv6auto;
+int myifc = -1;
+char *ndboptions;
+int nip;
+int noconfig;
+int nodhcpwatch;
+char optmagic[4] = {0x63, 0x82, 0x53, 0x63};
+int plan9 = 1;
+int sendhostname;
+
+char *verbs[] = {
+    [Vadd] "add",
+    [Vremove] "remove",
+    [Vunbind] "unbind",
+    [Vether] "ether",
+    [Vgbe] "gbe",
+    [Vloopback] "loopback",
+    [Vaddpref6] "add6",
+    [Vra6] "ra6",
+    [Vtorus] "torus",
+    [Vtree] "tree",
+    [Vpkt] "pkt",
+};
+
+void usage(void)
+{
+       fprintf(stderr,
+               "usage: %s %s\n\t%s\n",
+               argv0,
+               "[-6dDGnNOpPruX][-b baud][-c ctl]* [-g gw] [-h host][-m mtu]",
+               "[-x mtpt][-o dhcpopt] type dev [verb] [laddr [mask [raddr [fs [auth]]]]]");
+       exit(1);
+}
+
+void warning(char *fmt, ...)
+{
+       char buf[1024];
+       va_list arg;
+
+       va_start(arg, fmt);
+       vsnprintf(buf, sizeof(buf), fmt, arg);
+       va_end(arg);
+       fprintf(stderr, "%s: %s\n", argv0, buf);
+}
+
+char *sysname(void)
+{
+       static char sname[256];
+
+       gethostname(sname, sizeof(sname));
+
+       return sname;
+}
+
+void parsenorm(int argc, char **argv)
+{
+       switch (argc) {
+       case 5:
+               if (parseip(conf.auth, argv[4]) == -1)
+                       usage();
+               /* fall through */
+       case 4:
+               if (parseip(conf.fs, argv[3]) == -1)
+                       usage();
+               /* fall through */
+       case 3:
+               if (parseip(conf.raddr, argv[2]) == -1)
+                       usage();
+               /* fall through */
+       case 2:
+               /*
+                * can't test for parseipmask()==-1 cuz 255.255.255.255
+                * looks like that.
+                */
+               if (strcmp(argv[1], "0") != 0)
+                       parseipmask(conf.mask, argv[1]);
+               /* fall through */
+       case 1:
+               if (parseip(conf.laddr, argv[0]) == -1)
+                       usage();
+               /* fall through */
+       case 0:
+               break;
+       default:
+               usage();
+       }
+}
+
+void parse6pref(int argc, char **argv)
+{
+       switch (argc) {
+       case 6:
+               conf.preflt = strtoul(argv[5], 0, 10);
+               /* fall through */
+       case 5:
+               conf.validlt = strtoul(argv[4], 0, 10);
+               /* fall through */
+       case 4:
+               conf.autoflag = (atoi(argv[3]) != 0);
+               /* fall through */
+       case 3:
+               conf.onlink = (atoi(argv[2]) != 0);
+               /* fall through */
+       case 2:
+               conf.prefixlen = atoi(argv[1]);
+               /* fall through */
+       case 1:
+               if (parseip(conf.v6pref, argv[0]) == -1) {
+                       fprintf(stderr, "bad address %s", argv[0]);
+                       exit(-1);
+               }
+               break;
+       }
+       DEBUG("parse6pref: pref %R len %d", conf.v6pref, conf.prefixlen);
+}
+
+/* parse router advertisement (keyword, value) pairs */
+void parse6ra(int argc, char *argv[])
+{
+       int i, argsleft;
+       char *kw, *val;
+
+       if (argc % 2 != 0)
+               usage();
+
+       i = 0;
+       for (argsleft = argc; argsleft > 1; argsleft -= 2) {
+               kw = argv[i];
+               val = argv[i + 1];
+               if (strcmp(kw, "recvra") == 0)
+                       conf.recvra = (atoi(val) != 0);
+               else if (strcmp(kw, "sendra") == 0)
+                       conf.sendra = (atoi(val) != 0);
+               else if (strcmp(kw, "mflag") == 0)
+                       conf.mflag = (atoi(val) != 0);
+               else if (strcmp(kw, "oflag") == 0)
+                       conf.oflag = (atoi(val) != 0);
+               else if (strcmp(kw, "maxraint") == 0)
+                       conf.maxraint = atoi(val);
+               else if (strcmp(kw, "minraint") == 0)
+                       conf.minraint = atoi(val);
+               else if (strcmp(kw, "linkmtu") == 0)
+                       conf.linkmtu = atoi(val);
+               else if (strcmp(kw, "reachtime") == 0)
+                       conf.reachtime = atoi(val);
+               else if (strcmp(kw, "rxmitra") == 0)
+                       conf.rxmitra = atoi(val);
+               else if (strcmp(kw, "ttl") == 0)
+                       conf.ttl = atoi(val);
+               else if (strcmp(kw, "routerlt") == 0)
+                       conf.routerlt = atoi(val);
+               else {
+                       warning("bad ra6 keyword %s", kw);
+                       usage();
+               }
+               i += 2;
+       }
+
+       /* consistency check */
+       if (conf.maxraint < conf.minraint) {
+               fprintf(stderr, "maxraint %d < minraint %d",
+                       conf.maxraint, conf.minraint);
+               exit(-1);
+       }
+}
+
+void init(void)
+{
+       srand(lrand48());
+       if (register_printf_specifier('E', printf_ethaddr,
+                                     printf_ethaddr_info) != 0)
+               fprintf(stderr, "Installing 'E' failed\n");
+       if (register_printf_specifier('R', printf_ipaddr, printf_ipaddr_info) != 0)
+               fprintf(stderr, "Installing 'R' failed\n");
+       if (register_printf_specifier('M', printf_ipmask, printf_ipmask_info) != 0)
+               fprintf(stderr, "Installing 'M' failed\n");
+
+       setnetmtpt(conf.mpoint, sizeof(conf).mpoint, NULL);
+       conf.cputype = getenv("cputype");
+       if (conf.cputype == NULL)
+               conf.cputype = "386";
+
+       ctll = &firstctl;
+       v6paraminit(&conf);
+
+       /* init set of requested dhcp parameters with the default */
+       nrequested = sizeof(defrequested);
+       memcpy(requested, defrequested, nrequested);
+}
+
+int parseargs(int argc, char *argv[])
+{
+       char *p;
+       int action, verb;
+
+       /* default to any host name we already have */
+       if (*conf.hostname == 0) {
+               p = getenv("sysname");
+               if (p == NULL || *p == 0)
+                       p = sysname();
+               if (p != NULL)
+                       strncpy(conf.hostname, p, sizeof(conf).hostname - 1);
+       }
+
+       /* defaults */
+       conf.type = "ether";
+       conf.dev = "/net/ether0";
+       action = Vadd;
+
+       /* get optional medium and device */
+       if (argc > 0) {
+               verb = parseverb(*argv);
+               switch (verb) {
+               case Vether:
+               case Vgbe:
+               case Vloopback:
+               case Vtorus:
+               case Vtree:
+               case Vpkt:
+                       conf.type = *argv++;
+                       argc--;
+                       if (argc > 0) {
+                               conf.dev = *argv++;
+                               argc--;
+                       }
+                       break;
+               }
+       }
+
+       /* get optional verb */
+       if (argc > 0) {
+               verb = parseverb(*argv);
+               switch (verb) {
+               case Vether:
+               case Vgbe:
+               case Vloopback:
+               case Vtorus:
+               case Vtree:
+               case Vpkt:
+                       fprintf(stderr, "medium %s already specified", conf.type);
+                       exit(-1);
+               case Vadd:
+               case Vremove:
+               case Vunbind:
+               case Vaddpref6:
+               case Vra6:
+                       argv++;
+                       argc--;
+                       action = verb;
+                       break;
+               }
+       }
+
+       /* get verb-dependent arguments */
+       switch (action) {
+       case Vadd:
+       case Vremove:
+       case Vunbind:
+               parsenorm(argc, argv);
+               break;
+       case Vaddpref6:
+               parse6pref(argc, argv);
+               break;
+       case Vra6:
+               parse6ra(argc, argv);
+               break;
+       }
+       return action;
+}
+
+int main(int argc, char *argv[])
+{
+       int retry, action, ch;
+       struct ctl *cp;
+
+       init();
+       retry = 0;
+       while ((ch = getopt(argc, argv, "6b:c:dDg:h:m:nNo:OpPrux:X")) != -1) {
+               switch (ch) {
+               case '6': /* IPv6 auto config */
+                       ipv6auto = 1;
+                       break;
+               case 'b':
+                       conf.baud = optarg;
+                       break;
+               case 'c':
+                       cp = malloc(sizeof(*cp));
+                       if (cp == NULL) {
+                               fprintf(stderr, "%r");
+                               exit(1);
+                       }
+                       *ctll = cp;
+                       ctll = &cp->next;
+                       cp->next = NULL;
+                       cp->ctl = optarg;
+                       break;
+               case 'd':
+                       dodhcp = 1;
+                       break;
+               case 'D':
+                       debug = 1;
+                       break;
+               case 'g':
+                       if (parseip(conf.gaddr, optarg) == -1)
+                               usage();
+                       break;
+               case 'G':
+                       plan9 = 0;
+                       break;
+               case 'h':
+                       snprintf(conf.hostname, sizeof(conf).hostname,
+                                "%s", optarg);
+                       sendhostname = 1;
+                       break;
+               case 'm':
+                       conf.mtu = atoi(optarg);
+                       break;
+               case 'n':
+                       noconfig = 1;
+                       break;
+               case 'N':
+                       dondbconfig = 1;
+                       break;
+               case 'o':
+                       if (addoption(optarg) < 0)
+                               usage();
+                       break;
+               case 'O':
+                       Oflag = 1;
+                       break;
+               case 'p':
+                       beprimary = 1;
+                       break;
+               case 'P':
+                       beprimary = 0;
+                       break;
+               case 'r':
+                       retry = 1;
+                       break;
+               case 'u': /* IPv6: duplicate neighbour disc. off */
+                       dupl_disc = 0;
+                       break;
+               case 'x':
+                       setnetmtpt(conf.mpoint, sizeof(conf).mpoint, optarg);
+                       break;
+               case 'X':
+                       nodhcpwatch = 1;
+                       break;
+               default:
+                       usage();
+               }
+       }
+       argv0 = "ipconfig"; /* boot invokes us as tcp? */
+       argc -= optind;
+       argv += optind;
+
+       action = parseargs(argc, argv);
+       switch (action) {
+       case Vadd:
+               doadd(retry);
+               break;
+       case Vremove:
+               doremove();
+               break;
+       case Vunbind:
+               dounbind();
+               break;
+       case Vaddpref6:
+       case Vra6:
+               doipv6(action);
+               break;
+       }
+
+       return 0;
+}
+
+int havendb(char *net)
+{
+       struct stat s;
+       char buf[128];
+
+       snprintf(buf, sizeof(buf), "%s/ndb", net);
+       if (stat(buf, &s) < 0)
+               return 0;
+       if (s.st_size == 0)
+               return 0;
+       return 1;
+}
+
+void doadd(int retry)
+{
+       int tries;
+
+       /* get number of preexisting interfaces */
+       nip = nipifcs(conf.mpoint);
+       if (beprimary == -1 && (nip == 0 || !havendb(conf.mpoint)))
+               beprimary = 1;
+
+       /* get ipifc into name space and condition device for ip */
+       if (!noconfig) {
+               lookforip(conf.mpoint);
+               controldevice();
+               binddevice();
+       }
+
+       if (ipv6auto) {
+               if (ip6cfg(ipv6auto) < 0) {
+                       fprintf(stderr, "can't automatically start IPv6 on %s",
+                               conf.dev);
+                       exit(-1);
+               }
+       } else if (validip(conf.laddr) && !isv4(conf.laddr)) {
+               if (ip6cfg(0) < 0)
+                       fprintf(stderr, "can't start IPv6 on %s, address %R",
+                               conf.dev, conf.laddr);
+                       exit(-1);
+       }
+
+       if (!validip(conf.laddr)) {
+               if (dondbconfig)
+                       ndbconfig();
+               else
+                       dodhcp = 1;
+       }
+
+       /* run dhcp if we need something */
+       if (dodhcp) {
+               mkclientid();
+               for (tries = 0; tries < 30; tries++) {
+                       dhcpquery(!noconfig, Sselecting);
+                       if (conf.state == Sbound)
+                               break;
+                       usleep(1000 * 1000);
+               }
+       }
+
+       if (!validip(conf.laddr)) {
+               if (retry && dodhcp && !noconfig) {
+                       warning("couldn't determine ip address, retrying");
+                       dhcpwatch(1);
+                       return;
+               }
+               fprintf(stderr, "no success with DHCP");
+               exit(-1);
+       }
+
+
+       if (!noconfig) {
+               if (ip4cfg() < 0) {
+                       fprintf(stderr, "can't start ip");
+                       exit(-1);
+               }
+               if (dodhcp && conf.lease != Lforever)
+                       dhcpwatch(0);
+       }
+
+       /* leave everything we've learned somewhere other procs can find it */
+       if (beprimary == 1) {
+               putndb();
+               tweakservers();
+       }
+}
+
+void doremove(void)
+{
+       char file[128];
+       char buf[256];
+       int cfd;
+       struct ipifc *nifc;
+       struct iplifc *lifc;
+
+       if (!validip(conf.laddr)) {
+               fprintf(stderr, "remove requires an address");
+               exit(-1);
+       }
+       ifc = readipifc(conf.mpoint, ifc, -1);
+       for (nifc = ifc; nifc != NULL; nifc = nifc->next) {
+               if (strcmp(nifc->dev, conf.dev) != 0)
+                       continue;
+               for (lifc = nifc->lifc; lifc != NULL; lifc = lifc->next) {
+                       if (ipcmp(conf.laddr, lifc->ip) != 0)
+                               continue;
+                       if (validip(conf.mask) && ipcmp(conf.mask, lifc->mask) != 0)
+                               continue;
+                       if (validip(conf.raddr) && ipcmp(conf.raddr, lifc->net) != 0)
+                               continue;
+
+                       snprintf(file, sizeof(file), "%s/ipifc/%d/ctl",
+                                conf.mpoint, nifc->index);
+                       cfd = open(file, O_RDWR);
+                       if (cfd < 0) {
+                               warning("can't open %s: %r", conf.mpoint);
+                               continue;
+                       }
+                       snprintf(buf, sizeof(buf), "remove %R %M", lifc->ip, lifc->mask);
+                       if (write(cfd, buf, strlen(buf)) != strlen(buf))
+                               warning("can't remove %R %M from %s: %r",
+                                       lifc->ip, lifc->mask, file);
+                       close(cfd);
+               }
+       }
+}
+
+void dounbind(void)
+{
+       struct ipifc *nifc;
+       char file[128];
+       int cfd;
+
+       ifc = readipifc(conf.mpoint, ifc, -1);
+       for (nifc = ifc; nifc != NULL; nifc = nifc->next) {
+               if (strcmp(nifc->dev, conf.dev) == 0) {
+                       snprintf(file, sizeof(file), "%s/ipifc/%d/ctl",
+                                conf.mpoint, nifc->index);
+                       cfd = open(file, O_RDWR);
+                       if (cfd < 0) {
+                               warning("can't open %s: %r", conf.mpoint);
+                               break;
+                       }
+                       if (write(cfd, "unbind", strlen("unbind")) < 0)
+                               warning("can't unbind from %s: %r", file);
+                       close(cfd);
+                       break;
+               }
+       }
+}
+
+/* set the default route */
+void adddefroute(char *mpoint, uint8_t *gaddr)
+{
+       char buf[256];
+       int cfd;
+
+       sprintf(buf, "%s/iproute", mpoint);
+       cfd = open(buf, O_RDWR);
+       if (cfd < 0)
+               return;
+
+       if (isv4(gaddr))
+               snprintf(buf, sizeof(buf), "add 0 0 %R", gaddr);
+       else
+               snprintf(buf, sizeof(buf), "add :: /0 %R", gaddr);
+       write(cfd, buf, strlen(buf));
+       close(cfd);
+}
+
+/* create a client id */
+void mkclientid(void)
+{
+       if ((strcmp(conf.type, "ether") == 0) || (strcmp(conf.type, "gbe") == 0)) {
+               if (myetheraddr(conf.hwa, conf.dev) == 0) {
+                       conf.hwalen = 6;
+                       conf.hwatype = 1;
+                       conf.cid[0] = conf.hwatype;
+                       memmove(&conf.cid[1], conf.hwa, conf.hwalen);
+                       conf.cidlen = conf.hwalen + 1;
+               } else {
+                       conf.hwatype = -1;
+                       snprintf((char *)conf.cid, sizeof(conf).cid, "plan9_%ld.%d",
+                                lrand48(), getpid());
+                       conf.cidlen = strlen((char *)conf.cid);
+               }
+       }
+}
+
+/* bind ip into the namespace */
+void lookforip(char *net)
+{
+       struct stat s;
+       char proto[64];
+
+       snprintf(proto, sizeof(proto), "%s/ipifc", net);
+       if (stat(proto, &s) < 0) {
+               fprintf(stderr, "no ip stack bound onto %s", net);
+               exit(-1);
+       }
+}
+
+/* send some ctls to a device */
+void controldevice(void)
+{
+       char ctlfile[256];
+       int fd;
+       struct ctl *cp;
+
+       if (firstctl == NULL ||
+           (strcmp(conf.type, "ether") != 0 && strcmp(conf.type, "gbe") != 0))
+               return;
+
+       snprintf(ctlfile, sizeof(ctlfile), "%s/clone", conf.dev);
+       fd = open(ctlfile, O_RDWR);
+       if (fd < 0) {
+               fprintf(stderr, "can't open %s", ctlfile);
+               exit(-1);
+       }
+
+       for (cp = firstctl; cp != NULL; cp = cp->next) {
+               if (write(fd, cp->ctl, strlen(cp->ctl)) < 0) {
+                       fprintf(stderr, "ctl message %s: %r", cp->ctl);
+                       exit(-1);
+               }
+               lseek(fd, 0, 0);
+       }
+}
+
+/* bind an ip stack to a device, leave the control channel open */
+void binddevice(void)
+{
+       char buf[256];
+
+       if (myifc < 0) {
+               /* get a new ip interface */
+               snprintf(buf, sizeof(buf), "%s/ipifc/clone", conf.mpoint);
+               conf.cfd = open(buf, O_RDWR);
+               if (conf.cfd < 0) {
+                       fprintf(stderr, "opening %s/ipifc/clone: %r", conf.mpoint);
+                       exit(-1);
+               }
+
+               /* specify medium as ethernet, bind the interface to it */
+               snprintf(buf, sizeof(buf), "bind %s %s", conf.type, conf.dev);
+               if (write(conf.cfd, buf, strlen(buf)) != strlen(buf)) {
+                       fprintf(stderr, "%s: bind %s %s: %r", buf, conf.type, conf.dev);
+                       exit(-1);
+               }
+       } else {
+               /* open the old interface */
+               snprintf(buf, sizeof(buf), "%s/ipifc/%d/ctl", conf.mpoint, myifc);
+               conf.cfd = open(buf, O_RDWR);
+               if (conf.cfd < 0) {
+                       fprintf(stderr, "open %s: %r", buf);
+                       exit(-1);
+               }
+       }
+}
+
+/* add a logical interface to the ip stack */
+int ip4cfg(void)
+{
+       char buf[256];
+       int n;
+
+       if (!validip(conf.laddr))
+               return -1;
+
+       n = snprintf(buf, sizeof(buf), "add");
+       n += snprintf(buf + n, sizeof(buf) - n, " %R", conf.laddr);
+
+       if (!validip(conf.mask))
+               ipmove(conf.mask, defmask(conf.laddr));
+       n += snprintf(buf + n, sizeof(buf) - n, " %R", conf.mask);
+
+       if (validip(conf.raddr)) {
+               n += snprintf(buf + n, sizeof(buf) - n, " %R", conf.raddr);
+               if (conf.mtu != 0)
+                       n += snprintf(buf + n, sizeof(buf) - n, " %d", conf.mtu);
+       }
+
+       if (write(conf.cfd, buf, n) < 0) {
+               warning("write(%s): %r", buf);
+               return -1;
+       }
+
+       if (beprimary == 1 && validip(conf.gaddr))
+               adddefroute(conf.mpoint, conf.gaddr);
+
+       return 0;
+}
+
+/* remove a logical interface to the ip stack */
+void ipunconfig(void)
+{
+       char buf[256];
+       int n;
+
+       if (!validip(conf.laddr))
+               return;
+       DEBUG("couldn't renew IP lease, releasing %R", conf.laddr);
+       n = sprintf(buf, "remove");
+       n += snprintf(buf + n, sizeof(buf) - n, " %R", conf.laddr);
+
+       if (!validip(conf.mask))
+               ipmove(conf.mask, defmask(conf.laddr));
+       n += snprintf(buf + n, sizeof(buf) - n, " %R", conf.mask);
+
+       write(conf.cfd, buf, n);
+
+       ipmove(conf.laddr, IPnoaddr);
+       ipmove(conf.raddr, IPnoaddr);
+       ipmove(conf.mask, IPnoaddr);
+
+       /* forget configuration info */
+       if (beprimary == 1)
+               writendb("", 0, 0);
+}
+
+void dhcpquery(int needconfig, int startstate)
+{
+       char buf[256];
+
+       if (needconfig) {
+               snprintf(buf, sizeof(buf), "add %R %R", IPnoaddr, IPnoaddr);
+               write(conf.cfd, buf, strlen(buf));
+       }
+
+       conf.fd = openlisten();
+       if (conf.fd < 0) {
+               conf.state = Sinit;
+               return;
+       }
+
+       /* try dhcp for 10 seconds */
+       conf.xid = lrand48();
+       conf.starttime = time(0);
+       conf.state = startstate;
+       switch (startstate) {
+       case Sselecting:
+               conf.offered = 0;
+               dhcpsend(Discover);
+               break;
+       case Srenewing:
+               dhcpsend(Request);
+               break;
+       default:
+               fprintf(stderr, "internal error 0");
+               exit(-1);
+       }
+       conf.resend = 0;
+       conf.timeout = time(0) + 4;
+
+       while (conf.state != Sbound) {
+               dhcprecv();
+               if (dhcptimer() < 0)
+                       break;
+               if (time(0) - conf.starttime > 10)
+                       break;
+       }
+       close(conf.fd);
+
+       if (needconfig) {
+               snprintf(buf, sizeof(buf), "remove %R %R", IPnoaddr, IPnoaddr);
+               write(conf.cfd, buf, strlen(buf));
+       }
+}
+
+enum {
+       // This was an hour, but needs to be less for the ARM/GS1 until the timer
+       // code has been cleaned up (pb).
+       Maxsleep = 450,
+};
+
+void dhcpwatch(int needconfig)
+{
+       int secs, s;
+       uint32_t t;
+
+       if (nodhcpwatch)
+               return;
+
+       switch (fork()) {
+       default:
+               return;
+       case 0:
+               break;
+       }
+
+       // procsetname("dhcpwatch");
+       /* keep trying to renew the lease */
+       for (;;) {
+               if (conf.lease == 0)
+                       secs = 5;
+               else
+                       secs = conf.lease >> 1;
+
+               /* avoid overflows */
+               for (s = secs; s > 0; s -= t) {
+                       if (s > Maxsleep)
+                               t = Maxsleep;
+                       else
+                               t = s;
+                       usleep(t * 1000 * 1000);
+               }
+
+               if (conf.lease > 0) {
+                       /*
+                        * during boot, the starttime can be bogus so avoid
+                        * spurious ipunconfig's
+                        */
+                       t = time(0) - conf.starttime;
+                       if (t > (3 * secs) / 2)
+                               t = secs;
+                       if (t >= conf.lease) {
+                               conf.lease = 0;
+                               if (!noconfig) {
+                                       ipunconfig();
+                                       needconfig = 1;
+                               }
+                       } else
+                               conf.lease -= t;
+               }
+               dhcpquery(needconfig, needconfig ? Sselecting : Srenewing);
+
+               if (needconfig && conf.state == Sbound) {
+                       if (ip4cfg() < 0) {
+                               fprintf(stderr, "can't start ip: %r");
+                               exit(-1);
+                       }
+                       needconfig = 0;
+                       /*
+                        * leave everything we've learned somewhere that
+                        * other procs can find it.
+                        */
+                       if (beprimary == 1) {
+                               putndb();
+                               tweakservers();
+                       }
+               }
+       }
+}
+
+int dhcptimer(void)
+{
+       uint32_t now;
+
+       now = time(0);
+       if (now < conf.timeout)
+               return 0;
+
+       switch (conf.state) {
+       default:
+               fprintf(stderr, "dhcptimer: unknown state %d", conf.state);
+               exit(-1);
+       case Sinit:
+       case Sbound:
+               break;
+       case Sselecting:
+       case Srequesting:
+       case Srebinding:
+               dhcpsend(conf.state == Sselecting ? Discover : Request);
+               conf.timeout = now + 4;
+               if (++conf.resend > 5) {
+                       conf.state = Sinit;
+                       return -1;
+               }
+               break;
+       case Srenewing:
+               dhcpsend(Request);
+               conf.timeout = now + 1;
+               if (++conf.resend > 3) {
+                       conf.state = Srebinding;
+                       conf.resend = 0;
+               }
+               break;
+       }
+       return 0;
+}
+
+void dhcpsend(int type)
+{
+       struct bootp bp;
+       uint8_t *p;
+       int n;
+       uint8_t vendor[64];
+       struct udphdr *up = (struct udphdr *)bp.udphdr;
+
+       memset(&bp, 0, sizeof(bp));
+
+       hnputs(up->rport, 67);
+       bp.op = Bootrequest;
+       hnputl(bp.xid, conf.xid);
+       hnputs(bp.secs, time(0) - conf.starttime);
+       hnputs(bp.flags, 0);
+       memmove(bp.optmagic, optmagic, 4);
+       if (conf.hwatype >= 0 && conf.hwalen < sizeof(bp).chaddr) {
+               memmove(bp.chaddr, conf.hwa, conf.hwalen);
+               bp.hlen = conf.hwalen;
+               bp.htype = conf.hwatype;
+       }
+       p = bp.optdata;
+       p = optaddbyte(p, ODtype, type);
+       p = optadd(p, ODclientid, conf.cid, conf.cidlen);
+       switch (type) {
+       default:
+               fprintf(stderr, "dhcpsend: unknown message type: %d", type);
+               exit(-1);
+       case Discover:
+               ipmove(up->raddr, IPv4bcast); /* broadcast */
+               if (*conf.hostname && sendhostname)
+                       p = optaddstr(p, OBhostname, conf.hostname);
+               if (plan9) {
+                       n = snprintf((char *)vendor, sizeof(vendor), "plan9_%s",
+                                    conf.cputype);
+                       p = optaddvec(p, ODvendorclass, vendor, n);
+               }
+               p = optaddvec(p, ODparams, requested, nrequested);
+               if (validip(conf.laddr))
+                       p = optaddaddr(p, ODipaddr, conf.laddr);
+               break;
+       case Request:
+               switch (conf.state) {
+               case Srenewing:
+                       ipmove(up->raddr, conf.server);
+                       v6tov4(bp.ciaddr, conf.laddr);
+                       break;
+               case Srebinding:
+                       ipmove(up->raddr, IPv4bcast); /* broadcast */
+                       v6tov4(bp.ciaddr, conf.laddr);
+                       break;
+               case Srequesting:
+                       ipmove(up->raddr, IPv4bcast); /* broadcast */
+                       p = optaddaddr(p, ODipaddr, conf.laddr);
+                       p = optaddaddr(p, ODserverid, conf.server);
+                       break;
+               }
+               p = optaddulong(p, ODlease, conf.offered);
+               if (plan9) {
+                       n = snprintf((char *)vendor, sizeof(vendor), "plan9_%s",
+                                    conf.cputype);
+                       p = optaddvec(p, ODvendorclass, vendor, n);
+               }
+               p = optaddvec(p, ODparams, requested, nrequested);
+               if (*conf.hostname && sendhostname)
+                       p = optaddstr(p, OBhostname, conf.hostname);
+               break;
+       case Release:
+               ipmove(up->raddr, conf.server);
+               v6tov4(bp.ciaddr, conf.laddr);
+               p = optaddaddr(p, ODipaddr, conf.laddr);
+               p = optaddaddr(p, ODserverid, conf.server);
+               break;
+       }
+
+       *p++ = OBend;
+
+       n = p - (uint8_t *)&bp;
+
+       /*
+        * We use a maximum size DHCP packet to survive the
+        * All_Aboard NAT package from Internet Share.  It
+        * always replies to DHCP requests with a packet of the
+        * same size, so if the request is too short the reply
+        * is truncated.
+        */
+       if (write(conf.fd, &bp, sizeof(bp)) != sizeof(bp))
+               warning("dhcpsend: write failed: %r");
+}
+
+void rerrstr(char *buf, size_t buflen)
+{
+       snprintf(buf, buflen, "%s", errstr());
+}
+
+void dhcprecv(void)
+{
+       int i, n, type;
+       uint32_t lease;
+       char err[256];
+       uint8_t buf[8000], vopts[256], taddr[IPaddrlen];
+       struct bootp *bp;
+       struct alarm_waiter waiter;
+
+       init_awaiter(&waiter, alarm_abort_sysc);
+       waiter.data = current_uthread;
+
+       memset(buf, 0, sizeof(buf));
+       set_awaiter_rel(&waiter, 1000 * 1000);
+       set_alarm(&waiter);
+       n = read(conf.fd, buf, sizeof(buf));
+       unset_alarm(&waiter);
+
+       if (n < 0) {
+               rerrstr(err, sizeof(err));
+               if (strstr(err, "interrupt") == NULL)
+                       warning("dhcprecv: bad read: %s", err);
+               else
+                       DEBUG("dhcprecv: read timed out");
+               return;
+       }
+       if (n == 0) {
+               warning("dhcprecv: zero-length packet read");
+               return;
+       }
+
+       bp = parsebootp(buf, n);
+       if (bp == 0) {
+               DEBUG("parsebootp failed: dropping packet");
+               return;
+       }
+
+       type = optgetbyte(bp->optdata, ODtype);
+       switch (type) {
+       default:
+               warning("dhcprecv: unknown type: %d", type);
+               break;
+       case Offer:
+               DEBUG("got offer from %R ", bp->siaddr);
+               if (conf.state != Sselecting) {
+                       DEBUG("");
+                       break;
+               }
+               lease = optgetulong(bp->optdata, ODlease);
+               if (lease == 0) {
+                       /*
+                        * The All_Aboard NAT package from Internet Share
+                        * doesn't give a lease time, so we have to assume one.
+                        */
+                       warning("Offer with %lud lease, using %d", lease, MinLease);
+                       lease = MinLease;
+               }
+               DEBUG("lease=%lud ", lease);
+               if (!optgetaddr(bp->optdata, ODserverid, conf.server)) {
+                       warning("Offer from server with invalid serverid");
+                       break;
+               }
+
+               v4tov6(conf.laddr, bp->yiaddr);
+               memmove(conf.sname, bp->sname, sizeof(conf).sname);
+               conf.sname[sizeof(conf).sname - 1] = 0;
+               DEBUG("server=%R sname=%s", conf.server, conf.sname);
+               conf.offered = lease;
+               conf.state = Srequesting;
+               dhcpsend(Request);
+               conf.resend = 0;
+               conf.timeout = time(0) + 4;
+               break;
+       case Ack:
+               DEBUG("got ack from %R ", bp->siaddr);
+               if (conf.state != Srequesting && conf.state != Srenewing &&
+                   conf.state != Srebinding)
+                       break;
+
+               /* ignore a bad lease */
+               lease = optgetulong(bp->optdata, ODlease);
+               if (lease == 0) {
+                       /*
+                        * The All_Aboard NAT package from Internet Share
+                        * doesn't give a lease time, so we have to assume one.
+                        */
+                       warning("Ack with %lud lease, using %d", lease, MinLease);
+                       lease = MinLease;
+               }
+               DEBUG("lease=%lud ", lease);
+
+               /* address and mask */
+               if (!validip(conf.laddr) || !Oflag)
+                       v4tov6(conf.laddr, bp->yiaddr);
+               if (!validip(conf.mask) || !Oflag) {
+                       if (!optgetaddr(bp->optdata, OBmask, conf.mask))
+                               ipmove(conf.mask, IPnoaddr);
+               }
+               DEBUG("ipaddr=%R ipmask=%M ", conf.laddr, conf.mask);
+
+               /*
+                * get a router address either from the router option
+                * or from the router that forwarded the dhcp packet
+                */
+               if (validip(conf.gaddr) && Oflag) {
+                       DEBUG("ipgw=%R ", conf.gaddr);
+               } else if (optgetaddr(bp->optdata, OBrouter, conf.gaddr)) {
+                       DEBUG("ipgw=%R ", conf.gaddr);
+               } else if (memcmp(bp->giaddr, IPnoaddr + IPv4off, IPv4addrlen) != 0) {
+                       v4tov6(conf.gaddr, bp->giaddr);
+                       DEBUG("giaddr=%R ", conf.gaddr);
+               }
+
+               /* get dns servers */
+               memset(conf.dns, 0, sizeof(conf).dns);
+               n = optgetaddrs(bp->optdata, OBdnserver, conf.dns,
+                               sizeof(conf).dns / IPaddrlen);
+               for (i = 0; i < n; i++)
+                       DEBUG("dns=%R ", conf.dns + i * IPaddrlen);
+
+               /* get ntp servers */
+               memset(conf.ntp, 0, sizeof(conf).ntp);
+               n = optgetaddrs(bp->optdata, OBntpserver, conf.ntp,
+                               sizeof(conf).ntp / IPaddrlen);
+               for (i = 0; i < n; i++)
+                       DEBUG("ntp=%R ", conf.ntp + i * IPaddrlen);
+
+               /* get names */
+               optgetstr(bp->optdata, OBhostname,
+                         conf.hostname, sizeof(conf).hostname);
+               optgetstr(bp->optdata, OBdomainname,
+                         conf.domainname, sizeof(conf).domainname);
+
+               /* get anything else we asked for */
+               getoptions(bp->optdata);
+
+               /* get plan9-specific options */
+               n = optgetvec(bp->optdata, OBvendorinfo, vopts, sizeof(vopts) - 1);
+               if (n > 0 && parseoptions(vopts, n) == 0) {
+                       if (validip(conf.fs) && Oflag)
+                               n = 1;
+                       else {
+                               n = optgetp9addrs(vopts, OP9fs, conf.fs, 2);
+                               if (n == 0)
+                                       n = optgetaddrs(vopts, OP9fsv4, conf.fs, 2);
+                       }
+                       for (i = 0; i < n; i++)
+                               DEBUG("fs=%R ", conf.fs + i * IPaddrlen);
+
+                       if (validip(conf.auth) && Oflag)
+                               n = 1;
+                       else {
+                               n = optgetp9addrs(vopts, OP9auth, conf.auth, 2);
+                               if (n == 0)
+                                       n = optgetaddrs(vopts, OP9authv4, conf.auth, 2);
+                       }
+                       for (i = 0; i < n; i++)
+                               DEBUG("auth=%R ", conf.auth + i * IPaddrlen);
+
+                       n = optgetp9addrs(vopts, OP9ipaddr, taddr, 1);
+                       if (n > 0)
+                               memmove(conf.laddr, taddr, IPaddrlen);
+                       n = optgetp9addrs(vopts, OP9ipmask, taddr, 1);
+                       if (n > 0)
+                               memmove(conf.mask, taddr, IPaddrlen);
+                       n = optgetp9addrs(vopts, OP9ipgw, taddr, 1);
+                       if (n > 0)
+                               memmove(conf.gaddr, taddr, IPaddrlen);
+                       DEBUG("new ipaddr=%R new ipmask=%M new ipgw=%R",
+                             conf.laddr, conf.mask, conf.gaddr);
+               }
+               conf.lease = lease;
+               conf.state = Sbound;
+               DEBUG("server=%R sname=%s", conf.server, conf.sname);
+               break;
+       case Nak:
+               conf.state = Sinit;
+               warning("recved dhcpnak on %s", conf.mpoint);
+               break;
+       }
+}
+
+/* return pseudo-random integer in range low...(hi-1) */
+uint32_t randint(uint32_t low, uint32_t hi)
+{
+       if (hi < low)
+               return low;
+       return low + (lrand48() % hi);
+}
+
+// compute small pseudo-random delay in ms
+long jitter(void)
+{
+       return randint(0, 10 * 1000);
+}
+
+int openlisten(void)
+{
+       int n, fd, cfd;
+       char data[128], devdir[40];
+
+       if (validip(conf.laddr) &&
+           (conf.state == Srenewing || conf.state == Srebinding))
+               sprintf(data, "%s/udp!%R!68", conf.mpoint, conf.laddr);
+       else
+               sprintf(data, "%s/udp!*!68", conf.mpoint);
+       for (n = 0; (cfd = announce9(data, devdir, 0)) < 0; n++) {
+               if (!noconfig) {
+                       fprintf(stderr, "can't announce for dhcp: %r");
+                       exit(-1);
+               }
+
+               /* might be another client - wait and try again */
+               warning("can't announce %s: %r", data);
+               usleep(jitter() * 1000);
+               if (n > 10)
+                       return -1;
+       }
+
+       if (write(cfd, "headers", strlen("headers")) < 0) {
+               fprintf(stderr, "can't set header mode: %r");
+               exit(-1);
+       }
+
+       sprintf(data, "%s/data", devdir);
+       fd = open(data, O_RDWR);
+       if (fd < 0) {
+               fprintf(stderr, "open %s: %r", data);
+               exit(-1);
+       }
+       close(cfd);
+       return fd;
+}
+
+uint8_t *optadd(uint8_t *p, int op, void *d, int n)
+{
+       p[0] = op;
+       p[1] = n;
+       memmove(p + 2, d, n);
+       return p + n + 2;
+}
+
+uint8_t *optaddbyte(uint8_t *p, int op, int b)
+{
+       p[0] = op;
+       p[1] = 1;
+       p[2] = b;
+       return p + 3;
+}
+
+uint8_t *optaddulong(uint8_t *p, int op, uint32_t x)
+{
+       p[0] = op;
+       p[1] = 4;
+       hnputl(p + 2, x);
+       return p + 6;
+}
+
+uint8_t *optaddaddr(uint8_t *p, int op, uint8_t *ip)
+{
+       p[0] = op;
+       p[1] = 4;
+       v6tov4(p + 2, ip);
+       return p + 6;
+}
+
+/* add dhcp option op with value v of length n to dhcp option array p */
+uint8_t *optaddvec(uint8_t *p, int op, uint8_t *v, int n)
+{
+       p[0] = op;
+       p[1] = n;
+       memmove(p + 2, v, n);
+       return p + 2 + n;
+}
+
+uint8_t *optaddstr(uint8_t *p, int op, char *v)
+{
+       int n;
+
+       n = strlen(v) + 1; /* microsoft leaves on the NUL, so we do too */
+       p[0] = op;
+       p[1] = n;
+       memmove(p + 2, v, n);
+       return p + 2 + n;
+}
+
+/*
+ * parse p, looking for option `op'.  if non-nil, np points to minimum length.
+ * return NULL if option is too small, else ptr to opt, and
+ * store actual length via np if non-nil.
+ */
+uint8_t *optget(uint8_t *p, int op, int *np)
+{
+       int len, code;
+
+       while ((code = *p++) != OBend) {
+               if (code == OBpad)
+                       continue;
+               len = *p++;
+               if (code != op) {
+                       p += len;
+                       continue;
+               }
+               if (np != NULL) {
+                       if (*np > len)
+                               return 0;
+                       *np = len;
+               }
+               return p;
+       }
+       return 0;
+}
+
+int optgetbyte(uint8_t *p, int op)
+{
+       int len;
+
+       len = 1;
+       p = optget(p, op, &len);
+       if (p == NULL)
+               return 0;
+       return *p;
+}
+
+uint32_t optgetulong(uint8_t *p, int op)
+{
+       int len;
+
+       len = 4;
+       p = optget(p, op, &len);
+       if (p == NULL)
+               return 0;
+       return nhgetl(p);
+}
+
+int optgetaddr(uint8_t *p, int op, uint8_t *ip)
+{
+       int len;
+
+       len = 4;
+       p = optget(p, op, &len);
+       if (p == NULL)
+               return 0;
+       v4tov6(ip, p);
+       return 1;
+}
+
+/* expect at most n addresses; ip[] only has room for that many */
+int optgetaddrs(uint8_t *p, int op, uint8_t *ip, int n)
+{
+       int len, i;
+
+       len = 4;
+       p = optget(p, op, &len);
+       if (p == NULL)
+               return 0;
+       len /= IPv4addrlen;
+       if (len > n)
+               len = n;
+       for (i = 0; i < len; i++)
+               v4tov6(&ip[i * IPaddrlen], &p[i * IPv4addrlen]);
+       return i;
+}
+
+/* expect at most n addresses; ip[] only has room for that many */
+int optgetp9addrs(uint8_t *ap, int op, uint8_t *ip, int n)
+{
+       int len, i, slen, addrs;
+       char *p;
+
+       len = 1; /* minimum bytes needed */
+       p = (char *)optget(ap, op, &len);
+       if (p == NULL)
+               return 0;
+       addrs = *p++; /* first byte is address count */
+       for (i = 0; i < n && i < addrs && len > 0; i++) {
+               slen = strlen(p) + 1;
+               if (parseip(&ip[i * IPaddrlen], p) == -1)
+                       fprintf(stderr, "%s: bad address %s\n", argv0, p);
+               DEBUG("got plan 9 option %d addr %R (%s)", op, &ip[i * IPaddrlen], p);
+               p += slen;
+               len -= slen;
+       }
+       return addrs;
+}
+
+int optgetvec(uint8_t *p, int op, uint8_t *v, int n)
+{
+       int len;
+
+       len = 1;
+       p = optget(p, op, &len);
+       if (p == NULL)
+               return 0;
+       if (len > n)
+               len = n;
+       memmove(v, p, len);
+       return len;
+}
+
+int optgetstr(uint8_t *p, int op, char *s, int n)
+{
+       int len;
+
+       len = 1;
+       p = optget(p, op, &len);
+       if (p == NULL)
+               return 0;
+       if (len >= n)
+               len = n - 1;
+       memmove(s, p, len);
+       s[len] = 0;
+       return len;
+}
+
+/*
+ * sanity check options area
+ *     - options don't overflow packet
+ *     - options end with an OBend
+ */
+int parseoptions(uint8_t *p, int n)
+{
+       int code, len, nin = n;
+
+       while (n > 0) {
+               code = *p++;
+               n--;
+               if (code == OBend)
+                       return 0;
+               if (code == OBpad)
+                       continue;
+               if (n == 0) {
+                       warning(
+                           "parseoptions: bad option: 0x%x: truncated: opt length = %d",
+                           code, nin);
+                       return -1;
+               }
+
+               len = *p++;
+               n--;
+               DEBUG("parseoptions: %s(%d) len %d, bytes left %d",
+                     option[code].name, code, len, n);
+               if (len > n) {
+                       warning(
+                           "parseoptions: bad option: 0x%x: %d > %d: opt length = %d",
+                           code, len, n, nin);
+                       return -1;
+               }
+               p += len;
+               n -= len;
+       }
+
+       /* make sure packet ends with an OBend after all the optget code */
+       *p = OBend;
+       return 0;
+}
+
+/*
+ * sanity check received packet:
+ *     - magic is dhcp magic
+ *     - options don't overflow packet
+ */
+struct bootp *parsebootp(uint8_t *p, int n)
+{
+       struct bootp *bp;
+
+       bp = (struct bootp *)p;
+       if (n < bp->optmagic - p) {
+               warning(
+                   "parsebootp: short bootp packet; with options, need %d bytes, got %d",
+                   bp->optmagic - p, n);
+               return NULL;
+       }
+
+       if (conf.xid != nhgetl(bp->xid)) /* not meant for us */
+               return NULL;
+
+       if (bp->op != Bootreply) {
+               warning("parsebootp: bad op %d", bp->op);
+               return NULL;
+       }
+
+       n -= bp->optmagic - p;
+       p = bp->optmagic;
+
+       if (n < 4) {
+               warning("parsebootp: no option data");
+               return NULL;
+       }
+       if (memcmp(optmagic, p, 4) != 0) {
+               warning("parsebootp: bad opt magic %x %x %x %x",
+                       p[0], p[1], p[2], p[3]);
+               return NULL;
+       }
+       p += 4;
+       n -= 4;
+       DEBUG("parsebootp: new packet");
+       if (parseoptions(p, n) < 0)
+               return NULL;
+       return bp;
+}
+
+/* write out an ndb entry */
+void writendb(char *s, int n, int append)
+{
+       char file[64];
+       int fd;
+
+       snprintf(file, sizeof(file), "%s/ndb", conf.mpoint);
+       if (append) {
+               fd = open(file, O_WRITE);
+               lseek(fd, 0, 2);
+       } else
+               fd = open(file, O_WRITE | O_TRUNC);
+       write(fd, s, n);
+       close(fd);
+}
+
+/* put server addresses into the ndb entry */
+size_t putaddrs(char *buf, size_t size, char *attr, uint8_t *a, int len)
+{
+       int i;
+       size_t n;
+       char *p;
+
+       n = 0;
+       p = "";
+       for (i = 0; i < len && validip(a); i += IPaddrlen, a += IPaddrlen) {
+               n += snprintf(buf + n, size - n, "%s%s=%R\n", p, attr, a);
+               p = " ";
+       }
+
+       return n;
+}
+
+/* make an ndb entry and put it into /net/ndb for the servers to see */
+void putndb(void)
+{
+       int append;
+       char buf[1024];
+       char *np;
+       size_t n;
+
+       buf[0] = '\0';
+       n = 0;
+       if (getndb() == 0)
+               append = 1;
+       else {
+               append = 0;
+               n += snprintf(buf + n, sizeof(buf) - n, "ip=%R ipmask=%M ipgw=%R\n",
+                             conf.laddr, conf.mask, conf.gaddr);
+       }
+       np = strchr(conf.hostname, '.');
+       if (np != NULL) {
+               if (*conf.domainname == 0)
+                       snprintf(conf.domainname, sizeof(conf).domainname, "%s", np + 1);
+               *np = 0;
+       }
+       if (*conf.hostname)
+               n += snprintf(buf + n, sizeof(buf) - n, "\tsys=%s\n", conf.hostname);
+       if (*conf.domainname)
+               n += snprintf(buf + n, sizeof(buf) - n, "\tdom=%s.%s\n",
+                             conf.hostname, conf.domainname);
+       if (validip(conf.fs))
+               n += putaddrs(buf + n, sizeof(buf) - n, "\tfs",
+                             conf.fs, sizeof(conf).fs);
+       if (validip(conf.auth))
+               n += putaddrs(buf + n, sizeof(buf) - n, "\tauth",
+                             conf.auth, sizeof(conf).auth);
+       if (validip(conf.dns))
+               n += putaddrs(buf + n, sizeof(buf) - n, "\tdns",
+                             conf.dns, sizeof(conf).dns);
+       if (validip(conf.ntp))
+               n += putaddrs(buf + n, sizeof(buf) - n, "\tntp",
+                             conf.ntp, sizeof(conf).ntp);
+       if (ndboptions)
+               n += snprintf(buf + n, sizeof(buf) - n, "%s\n", ndboptions);
+       if (n > 0)
+               writendb(buf, n, append);
+}
+
+/* get an ndb entry someone else wrote */
+int getndb(void)
+{
+       char buf[1024];
+       int fd, n;
+       char *p;
+
+       snprintf(buf, sizeof(buf), "%s/ndb", conf.mpoint);
+       fd = open(buf, O_RDONLY);
+       n = read(fd, buf, sizeof(buf) - 1);
+       close(fd);
+       if (n <= 0)
+               return -1;
+       buf[n] = 0;
+       p = strstr(buf, "ip=");
+       if (p == NULL)
+               return -1;
+       if (parseip(conf.laddr, p + 3) == -1)
+               fprintf(stderr, "%s: bad address %s\n", argv0, p + 3);
+       return 0;
+}
+
+/* tell a server to refresh */
+void tweakserver(char *server)
+{
+       int fd;
+       char file[64];
+
+       snprintf(file, sizeof(file), "%s/%s", conf.mpoint, server);
+       fd = open(file, O_RDWR);
+       if (fd < 0)
+               return;
+       write(fd, "refresh", strlen("refresh"));
+       close(fd);
+}
+
+/* tell all servers to refresh their information */
+void tweakservers(void)
+{
+       tweakserver("dns");
+       tweakserver("cs");
+}
+
+/* return number of networks */
+int nipifcs(char *net)
+{
+       int n;
+       struct ipifc *nifc;
+       struct iplifc *lifc;
+
+       n = 0;
+       ifc = readipifc(net, ifc, -1);
+       for (nifc = ifc; nifc != NULL; nifc = nifc->next) {
+               /*
+                * ignore loopback devices when trying to
+                * figure out if we're the primary interface.
+                */
+               if (strcmp(nifc->dev, "/dev/null") != 0)
+                       for (lifc = nifc->lifc; lifc != NULL; lifc = lifc->next)
+                               if (validip(lifc->ip)) {
+                                       n++;
+                                       break;
+                               }
+               if (strcmp(nifc->dev, conf.dev) == 0)
+                       myifc = nifc->index;
+       }
+       return n;
+}
+
+/* return true if this is a valid v4 address */
+int validip(uint8_t *addr)
+{
+       return ipcmp(addr, IPnoaddr) != 0 && ipcmp(addr, v4prefix) != 0;
+}
+
+/* look for an action */
+int parseverb(char *name)
+{
+       int i;
+
+       for (i = 0; i < COUNT_OF(verbs); i++)
+               if (verbs[i] != NULL && strcmp(name, verbs[i]) == 0)
+                       return i;
+       return -1;
+}
+
+/* get everything out of ndb */
+void ndbconfig(void)
+{
+       int nattr, nauth = 0, ndns = 0, nfs = 0, ok;
+       char etheraddr[32];
+       char *attrs[10];
+       struct ndb *db;
+       struct ndbtuple *t, *nt;
+
+       db = ndbopen(0);
+       if (db == NULL) {
+               fprintf(stderr, "can't open ndb: %r");
+               exit(-1);
+       }
+       if ((strcmp(conf.type, "ether") != 0 && strcmp(conf.type, "gbe") != 0) ||
+           myetheraddr(conf.hwa, conf.dev) != 0) {
+               fprintf(stderr, "can't read hardware address");
+               exit(-1);
+       }
+       snprintf(etheraddr, sizeof(etheraddr), "%E", conf.hwa);
+       nattr = 0;
+       attrs[nattr++] = "ip";
+       attrs[nattr++] = "ipmask";
+       attrs[nattr++] = "ipgw";
+       /* the @ triggers resolution to an IP address; see ndb(2) */
+       attrs[nattr++] = "@dns";
+       attrs[nattr++] = "@ntp";
+       attrs[nattr++] = "@fs";
+       attrs[nattr++] = "@auth";
+       attrs[nattr] = NULL;
+       t = ndbipinfo(db, "ether", etheraddr, attrs, nattr);
+       for (nt = t; nt != NULL; nt = nt->entry) {
+               ok = 1;
+               if (strcmp(nt->attr, "ip") == 0)
+                       ok = parseip(conf.laddr, nt->val);
+               else if (strcmp(nt->attr, "ipmask") == 0)
+                       parseipmask(conf.mask, nt->val); /* could be -1 */
+               else if (strcmp(nt->attr, "ipgw") == 0)
+                       ok = parseip(conf.gaddr, nt->val);
+               else if (ndns < 2 && strcmp(nt->attr, "dns") == 0)
+                       ok = parseip(conf.dns + IPaddrlen * ndns, nt->val);
+               else if (strcmp(nt->attr, "ntp") == 0)
+                       ok = parseip(conf.ntp, nt->val);
+               else if (nfs < 2 && strcmp(nt->attr, "fs") == 0)
+                       ok = parseip(conf.fs + IPaddrlen * nfs, nt->val);
+               else if (nauth < 2 && strcmp(nt->attr, "auth") == 0)
+                       ok = parseip(conf.auth + IPaddrlen * nauth, nt->val);
+               if (!ok)
+                       fprintf(stderr, "%s: bad %s address in ndb: %s\n",
+                               argv0, nt->attr, nt->val);
+       }
+       ndbfree(t);
+       if (!validip(conf.laddr)) {
+               fprintf(stderr, "address not found in ndb");
+               exit(-1);
+       }
+}
+
+int addoption(char *opt)
+{
+       int i;
+       struct option *o;
+
+       if (opt == NULL)
+               return -1;
+       for (o = option; o < &option[COUNT_OF(option)]; o++)
+               if (o->name && strcmp(opt, o->name) == 0) {
+                       i = o - option;
+                       if (memchr(requested, i, nrequested) == 0 &&
+                           nrequested < COUNT_OF(requested))
+                               requested[nrequested++] = i;
+                       return 0;
+               }
+       return -1;
+}
+
+char *optgetx(uint8_t *p, uint8_t opt)
+{
+       int i, n;
+       uint32_t x;
+       char str[256], buf[1024];
+       uint8_t ip[IPaddrlen], ips[16 * IPaddrlen], vec[256];
+       size_t l;
+       struct option *o;
+
+       o = &option[opt];
+       if (o->name == NULL)
+               return NULL;
+
+       memset(buf, '\0', sizeof(buf));
+       switch (o->type) {
+       case Taddr:
+               if (optgetaddr(p, opt, ip))
+                       snprintf(buf, sizeof(buf), "%s=%R", o->name, ip);
+               break;
+       case Taddrs:
+               n = optgetaddrs(p, opt, ips, 16);
+               if (n > 0)
+                       l = snprintf(buf, sizeof(buf), "%s=%R", o->name, ips);
+               for (i = 1; i < n; i++) {
+                       l += snprintf(buf + l, sizeof(buf) - l, " %s=%R",
+                                     o->name, &ips[i * IPaddrlen]);
+               }
+               break;
+       case Tulong:
+               x = optgetulong(p, opt);
+               if (x != 0)
+                       snprintf(buf, sizeof(buf), "%s=%lud", o->name, x);
+               break;
+       case Tbyte:
+               x = optgetbyte(p, opt);
+               if (x != 0)
+                       snprintf(buf, sizeof(buf), "%s=%lud", o->name, x);
+               break;
+       case Tstr:
+               if (optgetstr(p, opt, str, sizeof(str)))
+                       snprintf(buf, sizeof(buf), "%s=%s", o->name, str);
+               break;
+       case Tvec:
+               n = optgetvec(p, opt, vec, sizeof(vec));
+               if (n > 0) /* what's %H?  it's not installed */
+                       snprintf(buf, sizeof(buf), "%s=%.*H", o->name, n, vec);
+               break;
+       }
+       return strdup(buf);
+}
+
+void getoptions(uint8_t *p)
+{
+       int i;
+       char buf[1024];
+       char *s;
+
+       for (i = COUNT_OF(defrequested); i < nrequested; i++) {
+               s = optgetx(p, requested[i]);
+               if (s == NULL)
+                       continue;
+               DEBUG("%s ", s);
+               snprintf(buf, sizeof(buf),
+                        (ndboptions == NULL) ? "\t%s" : "\t%s%s",
+                        s, ndboptions);
+               free(ndboptions);
+               ndboptions = strdup(buf);
+               free(s);
+       }
+}