| /* |
| * 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 <parlib/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 <pthread.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; |
| int server; |
| |
| 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 evnotify(int rc) |
| { |
| struct event_msg msg = { 0 }; |
| |
| msg.ev_type = EV_USER_IPI; |
| msg.ev_arg1 = rc; |
| sys_notify(getppid(), EV_USER_IPI, &msg); |
| } |
| |
| void evexit(int rc) |
| { |
| if (server) |
| evnotify(rc); |
| exit(rc); |
| } |
| |
| 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]]]]]"); |
| evexit(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\n", argv[0]); |
| evexit(-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\n", |
| conf.maxraint, conf.minraint); |
| evexit(-1); |
| } |
| } |
| |
| void init(void) |
| { |
| parlib_wants_to_be_mcp = FALSE; |
| 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\n", |
| conf.type); |
| evexit(-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:OpPrSux: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\n"); |
| evexit(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 'S': |
| server = 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; |
| } |
| |
| if (server) { |
| evnotify(0); |
| uthread_sleep_forever(); |
| } |
| |
| evexit(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) |
| { |
| /* 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\n", |
| conf.dev); |
| evexit(-1); |
| } |
| } else if (validip(conf.laddr) && !isv4(conf.laddr)) { |
| if (ip6cfg(0) < 0) |
| fprintf(stderr, "can't start IPv6 on %s, address %R\n", |
| conf.dev, conf.laddr); |
| evexit(-1); |
| } |
| |
| if (!validip(conf.laddr)) { |
| if (dondbconfig) |
| ndbconfig(); |
| else |
| dodhcp = 1; |
| } |
| |
| /* run dhcp if we need something */ |
| if (dodhcp) { |
| mkclientid(); |
| dhcpquery(!noconfig, Sselecting); |
| } |
| |
| if (!validip(conf.laddr)) { |
| if (retry && dodhcp && !noconfig) { |
| warning("couldn't determine ip address, retrying"); |
| dhcpwatch(1); |
| return; |
| } |
| fprintf(stderr, "no success with DHCP\n"); |
| evexit(-1); |
| } |
| |
| |
| if (!noconfig) { |
| if (ip4cfg() < 0) { |
| fprintf(stderr, "can't start ip\n"); |
| evexit(-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\n"); |
| evexit(-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\n", net); |
| evexit(-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\n", ctlfile); |
| evexit(-1); |
| } |
| |
| for (cp = firstctl; cp != NULL; cp = cp->next) { |
| if (write(fd, cp->ctl, strlen(cp->ctl)) < 0) { |
| fprintf(stderr, "ctl message %s: %r\n", cp->ctl); |
| evexit(-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\n", |
| conf.mpoint); |
| evexit(-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\n", buf, conf.type, |
| conf.dev); |
| evexit(-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\n", buf); |
| evexit(-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\n"); |
| evexit(-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, |
| }; |
| |
| static void *dhcpwatchthr(void *arg); |
| |
| void dhcpwatch(int needconfig) |
| { |
| pthread_t tid; |
| intptr_t nc = needconfig; |
| |
| if (nodhcpwatch) |
| return; |
| |
| pthread_create(&tid, NULL, dhcpwatchthr, (void *)nc); |
| } |
| |
| static void *dhcpwatchthr(void *arg) |
| { |
| int secs, s; |
| uint32_t t; |
| int needconfig = (arg == NULL); |
| |
| /* keep trying to renew the lease */ |
| for (;;) { |
| secs = conf.lease / 2; |
| if (secs == 0) |
| secs = 5; |
| |
| /* avoid overflows */ |
| for (s = secs; s > 0; s -= t) { |
| t = s; |
| if (t > Maxsleep) |
| t = Maxsleep; |
| 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\n"); |
| evexit(-1); |
| } |
| needconfig = 0; |
| /* |
| * leave everything we've learned somewhere that |
| * other procs can find it. |
| */ |
| if (beprimary == 1) { |
| putndb(); |
| tweakservers(); |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| 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\n", conf.state); |
| evexit(-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\n", type); |
| evexit(-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, "syscall aborted") == 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) { |
| n = 1; |
| if (!validip(conf.fs) || !Oflag) { |
| 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); |
| |
| n = 1; |
| if (!validip(conf.auth) || !Oflag) { |
| 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\n"); |
| evexit(-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\n"); |
| evexit(-1); |
| } |
| |
| sprintf(data, "%s/data", devdir); |
| fd = open(data, O_RDWR); |
| if (fd < 0) { |
| fprintf(stderr, "open %s: %r\n", data); |
| evexit(-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\n"); |
| evexit(-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\n"); |
| evexit(-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\n"); |
| evexit(-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); |
| } |
| } |