ref: 3b7e03a30ed29ee0666c00568bf67b8179c28d12
dir: /sys/src/cmd/ip/ipconfig/main.c/
/* * ipconfig - configure parameters of an ip stack */ #include <u.h> #include <libc.h> #include <bio.h> #include <ip.h> #include <ndb.h> #include "ipconfig.h" #include "../dhcp.h" #include <libsec.h> /* genrandom() */ Conf conf; int myifc = -1; int beprimary = -1; int noconfig; Ipifc *ifc; Ctl *firstctl, **ctll = &firstctl; int debug; int dolog; int plan9 = 1; int Oflag; int rflag; int dodhcp; int nodhcpwatch; int sendhostname; char *ndboptions; int ipv6auto; int dupl_disc = 1; /* flag: V6 duplicate neighbor discovery */ int dondbconfig; char *dbfile; static char logfile[] = "ipconfig"; static void binddevice(void); static void controldevice(void); extern void pppbinddev(void); static void doadd(void); static void doremove(void); static void dounbind(void); static void ndbconfig(void); void usage(void) { fprint(2, "usage: %s [-6dDGnNOpPruX][-b baud][-c ctl]* [-g gw]" "[-h host][-m mtu]\n" "\t[-f dbfile][-x mtpt][-o dhcpopt] type dev [verb] [laddr [mask " "[raddr [fs [auth]]]]]\n", argv0); exits("usage"); } static void init(void) { srand(truerand()); fmtinstall('E', eipfmt); fmtinstall('I', eipfmt); fmtinstall('M', eipfmt); fmtinstall('V', eipfmt); nsec(); /* make sure time file is open before forking */ conf.cfd = -1; conf.rfd = -1; setnetmtpt(conf.mpoint, sizeof conf.mpoint, nil); conf.cputype = getenv("cputype"); if(conf.cputype == nil) conf.cputype = "386"; v6paraminit(&conf); dhcpinit(); } void warning(char *fmt, ...) { char buf[1024]; va_list arg; va_start(arg, fmt); vseprint(buf, buf + sizeof buf, fmt, arg); va_end(arg); if (dolog) syslog(0, logfile, "%s", buf); else fprint(2, "%s: %s\n", argv0, buf); } static 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(); } } static char* finddev(char *dir, char *name, char *dev) { int fd, i, nd; Dir *d; fd = open(dir, OREAD); if(fd >= 0){ d = nil; nd = dirreadall(fd, &d); close(fd); for(i=0; i<nd; i++){ if(strncmp(d[i].name, name, strlen(name))) continue; if(strstr(d[i].name, "ctl") != nil) continue; /* ignore ctl files */ dev = smprint("%s/%s", dir, d[i].name); break; } free(d); } return dev; } /* look for an action */ static int parseverb(char *name) { static char *verbs[] = { [Vadd] "add", [Vremove] "remove", [Vunbind] "unbind", [Vether] "ether", [Vgbe] "gbe", [Vppp] "ppp", [Vloopback] "loopback", [Vaddpref6] "add6", [Vra6] "ra6", [Vtorus] "torus", [Vtree] "tree", [Vpkt] "pkt", }; int i; for(i = 0; i < nelem(verbs); i++) if(verbs[i] != nil && strcmp(name, verbs[i]) == 0) return i; return -1; } static 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 == nil || *p == 0) p = sysname(); if(p != nil) strncpy(conf.hostname, p, sizeof conf.hostname-1); } /* defaults */ conf.type = "ether"; conf.dev = nil; action = Vadd; /* get optional medium and device */ if (argc > 0){ verb = parseverb(*argv); switch(verb){ case Vether: case Vgbe: case Vppp: case Vloopback: case Vtorus: case Vtree: case Vpkt: conf.type = *argv++; argc--; if(argc > 0){ conf.dev = *argv++; argc--; } else if(verb == Vppp) conf.dev = finddev("/dev", "eia", "/dev/eia0"); break; } } if(conf.dev == nil) conf.dev = finddev(conf.mpoint, "ether", "/net/ether0"); /* get optional verb */ if (argc > 0){ verb = parseverb(*argv); switch(verb){ case Vether: case Vgbe: case Vppp: case Vloopback: case Vtorus: case Vtree: case Vpkt: sysfatal("medium %s already specified", conf.type); 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; } static int findifc(char *net, char *dev) { Ipifc *nifc; ifc = readipifc(net, ifc, -1); for(nifc = ifc; nifc != nil; nifc = nifc->next) if(strcmp(nifc->dev, dev) == 0) return nifc->index; return -1; } static int isether(void) { return strcmp(conf.type, "ether") == 0 || strcmp(conf.type, "gbe") == 0; } /* create a client id */ static void mkclientid(void) { if(isether() && 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; snprint((char*)conf.cid, sizeof conf.cid, "plan9_%ld.%d", lrand(), getpid()); conf.cidlen = strlen((char*)conf.cid); genrandom(conf.hwa, sizeof(conf.hwa)); } ea2lla(conf.lladdr, conf.hwa); } void main(int argc, char **argv) { int action; Ctl *cp; init(); ARGBEGIN { case '6': /* IPv6 auto config */ ipv6auto = 1; break; case 'b': conf.baud = EARGF(usage()); break; case 'c': cp = malloc(sizeof *cp); if(cp == nil) sysfatal("%r"); *ctll = cp; ctll = &cp->next; cp->next = nil; cp->ctl = EARGF(usage()); break; case 'd': dodhcp = 1; break; case 'D': debug = 1; break; case 'f': dbfile = EARGF(usage()); break; case 'g': if (parseip(conf.gaddr, EARGF(usage())) == -1) usage(); break; case 'G': plan9 = 0; break; case 'h': snprint(conf.hostname, sizeof conf.hostname, "%s", EARGF(usage())); sendhostname = 1; break; case 'm': conf.mtu = atoi(EARGF(usage())); break; case 'n': noconfig = 1; break; case 'N': dondbconfig = 1; break; case 'o': if(addoption(EARGF(usage())) < 0) usage(); break; case 'O': Oflag = 1; break; case 'p': beprimary = 1; break; case 'P': beprimary = 0; break; case 'r': rflag = 1; break; case 'u': /* IPv6: duplicate neighbour disc. off */ dupl_disc = 0; break; case 'x': setnetmtpt(conf.mpoint, sizeof conf.mpoint, EARGF(usage())); break; case 'X': nodhcpwatch = 1; break; default: usage(); } ARGEND; argv0 = "ipconfig"; /* boot invokes us as tcp? */ action = parseargs(argc, argv); myifc = findifc(conf.mpoint, conf.dev); if(myifc < 0) { switch(action){ default: if(noconfig) break; /* bind new interface */ controldevice(); binddevice(); myifc = findifc(conf.mpoint, conf.dev); case Vremove: case Vunbind: break; } if(myifc < 0) sysfatal("interface not found for: %s", conf.dev); } else { /* open old interface */ binddevice(); } switch(action){ case Vadd: mkclientid(); if(dondbconfig){ dodhcp = 0; ndbconfig(); break; } doadd(); break; case Vra6: case Vaddpref6: mkclientid(); doipv6(action); break; case Vremove: doremove(); break; case Vunbind: dounbind(); break; } exits(nil); } static void doadd(void) { if(!validip(conf.laddr)){ if(ipv6auto){ ipmove(conf.laddr, conf.lladdr); dodhcp = 0; } else dodhcp = 1; } /* run dhcp if we need something */ if(dodhcp){ fprint(conf.rfd, "tag dhcp"); dhcpquery(!noconfig, Sselecting); } if(!validip(conf.laddr)) if(rflag && dodhcp && !noconfig){ warning("couldn't determine ip address, retrying"); dhcpwatch(1); return; } else sysfatal("no success with DHCP"); DEBUG("adding address %I %M on %s", conf.laddr, conf.mask, conf.dev); if(noconfig) return; if(!isv4(conf.laddr)){ if(ip6cfg() < 0) sysfatal("can't start IPv6 on %s, address %I", conf.dev, conf.laddr); } else { if(ip4cfg() < 0) sysfatal("can't start IPv4 on %s, address %I", conf.dev, conf.laddr); else if(dodhcp && conf.lease != Lforever) dhcpwatch(0); } /* leave everything we've learned somewhere other procs can find it */ if(beprimary && !dondbconfig && !ipv6auto) putndb(); refresh(); } static void doremove(void) { if(!validip(conf.laddr)) sysfatal("remove requires an address"); if(fprint(conf.cfd, "remove %I %M", conf.laddr, conf.mask) < 0) warning("can't remove %I %M: %r", conf.laddr, conf.mask); } static void dounbind(void) { if(fprint(conf.cfd, "unbind") < 0) warning("can't unbind %s: %r", conf.dev); } /* send some ctls to a device */ static void controldevice(void) { char ctlfile[256]; int fd; Ctl *cp; if (firstctl == nil || !isether()) return; snprint(ctlfile, sizeof ctlfile, "%s/clone", conf.dev); fd = open(ctlfile, ORDWR); if(fd < 0) sysfatal("can't open %s", ctlfile); for(cp = firstctl; cp != nil; cp = cp->next){ if(write(fd, cp->ctl, strlen(cp->ctl)) < 0) sysfatal("ctl message %s: %r", cp->ctl); seek(fd, 0, 0); } // close(fd); /* or does it need to be left hanging? */ } /* bind an ip stack to a device, leave the control channel open */ static void binddevice(void) { char buf[256]; if(myifc >= 0){ /* open the old interface */ snprint(buf, sizeof buf, "%s/ipifc/%d/ctl", conf.mpoint, myifc); conf.cfd = open(buf, ORDWR); if(conf.cfd < 0) sysfatal("open %s: %r", buf); } else if(strcmp(conf.type, "ppp") == 0) pppbinddev(); else { /* get a new ip interface */ snprint(buf, sizeof buf, "%s/ipifc/clone", conf.mpoint); conf.cfd = open(buf, ORDWR); if(conf.cfd < 0) sysfatal("opening %s/ipifc/clone: %r", conf.mpoint); /* specify medium as ethernet, bind the interface to it */ if(fprint(conf.cfd, "bind %s %s", conf.type, conf.dev) < 0) sysfatal("%s: bind %s %s: %r", buf, conf.type, conf.dev); } snprint(buf, sizeof buf, "%s/iproute", conf.mpoint); conf.rfd = open(buf, OWRITE); } /* add a logical interface to the ip stack */ int ip4cfg(void) { char buf[256]; int n; if(!validip(conf.laddr) || !isv4(conf.laddr)) return -1; n = sprint(buf, "add"); n += snprint(buf+n, sizeof buf-n, " %I", conf.laddr); if(!validip(conf.mask)) ipmove(conf.mask, defmask(conf.laddr)); n += snprint(buf+n, sizeof buf-n, " %I", conf.mask); if(validip(conf.raddr)){ n += snprint(buf+n, sizeof buf-n, " %I", conf.raddr); if(conf.mtu != 0) n += snprint(buf+n, sizeof buf-n, " %d", conf.mtu); } if(write(conf.cfd, buf, n) < 0){ warning("write(%s): %r", buf); return -1; } if(validip(conf.gaddr) && isv4(conf.gaddr)) adddefroute(conf.gaddr, conf.laddr, conf.laddr, conf.mask); return 0; } /* remove a logical interface from the ip stack */ void ipunconfig(void) { if(!validip(conf.laddr)) return; DEBUG("couldn't renew IP lease, releasing %I", conf.laddr); if(!validip(conf.mask)) ipmove(conf.mask, defmask(conf.laddr)); doremove(); ipmove(conf.laddr, IPnoaddr); ipmove(conf.raddr, IPnoaddr); ipmove(conf.mask, IPnoaddr); } /* return true if this is not a null address */ int validip(uchar *addr) { return ipcmp(addr, IPnoaddr) != 0 && ipcmp(addr, v4prefix) != 0; } /* put server ip addresses into the ndb entry */ static char* putaddrs(char *p, char *e, char *attr, uchar *a, int len) { int i; for(i = 0; i < len && validip(a); i += IPaddrlen, a += IPaddrlen) p = seprint(p, e, "%s=%I\n", attr, a); return p; } /* put space separated names into ndb entry */ static char* putnames(char *p, char *e, char *attr, char *s) { char *x; for(; *s != 0; s = x+1){ if((x = strchr(s, ' ')) == nil) x = strchr(s, 0); p = seprint(p, e, "%s=%.*s\n", attr, (int)(x - s), s); if(*x == 0) break; } return p; } /* make an ndb entry and put it into /net/ndb for the servers to see */ void putndb(void) { static char buf[16*1024]; char file[64], *p, *e, *np; Ndbtuple *t, *nt; Ndb *db; int fd; p = buf; e = buf + sizeof buf; p = seprint(p, e, "ip=%I ipmask=%M ipgw=%I\n", conf.laddr, conf.mask, conf.gaddr); if(np = strchr(conf.hostname, '.')){ if(*conf.domainname == 0) strcpy(conf.domainname, np+1); *np = 0; } if(*conf.hostname) p = seprint(p, e, "\tsys=%s\n", conf.hostname); if(*conf.domainname) p = seprint(p, e, "\tdom=%s.%s\n", conf.hostname, conf.domainname); if(*conf.dnsdomain) p = putnames(p, e, "\tdnsdomain", conf.dnsdomain); if(validip(conf.dns)) p = putaddrs(p, e, "\tdns", conf.dns, sizeof conf.dns); if(validip(conf.fs)) p = putaddrs(p, e, "\tfs", conf.fs, sizeof conf.fs); if(validip(conf.auth)) p = putaddrs(p, e, "\tauth", conf.auth, sizeof conf.auth); if(validip(conf.ntp)) p = putaddrs(p, e, "\tntp", conf.ntp, sizeof conf.ntp); if(ndboptions) p = seprint(p, e, "%s\n", ndboptions); /* append preexisting entries not matching our ip */ snprint(file, sizeof file, "%s/ndb", conf.mpoint); db = ndbopen(file); if(db != nil ){ while((t = ndbparse(db)) != nil){ uchar ip[IPaddrlen]; if((nt = ndbfindattr(t, t, "ip")) == nil || parseip(ip, nt->val) == -1 || ipcmp(ip, conf.laddr) != 0){ p = seprint(p, e, "\n"); for(nt = t; nt != nil; nt = nt->entry) p = seprint(p, e, "%s=%s%s", nt->attr, nt->val, nt->entry==nil? "\n": nt->line!=nt->entry? "\n\t": " "); } ndbfree(t); } ndbclose(db); } if((fd = open(file, OWRITE|OTRUNC)) < 0) return; write(fd, buf, p-buf); close(fd); } static int issrcspec(uchar *src, uchar *smask) { return isv4(src)? memcmp(smask+IPv4off, IPnoaddr+IPv4off, 4): ipcmp(smask, IPnoaddr); } void addroute(uchar *dst, uchar *mask, uchar *gate, uchar *ia, uchar *src, uchar *smask) { char *cmd; if(issrcspec(src, smask)) cmd = "add %I %M %I %I %I %M"; else cmd = "add %I %M %I %I"; fprint(conf.rfd, cmd, dst, mask, gate, ia, src, smask); } void removeroute(uchar *dst, uchar *mask, uchar *src, uchar *smask) { char *cmd; if(issrcspec(src, smask)) cmd = "remove %I %M %I %M"; else cmd = "remove %I %M"; fprint(conf.rfd, cmd, dst, mask, src, smask); } void adddefroute(uchar *gaddr, uchar *ia, uchar *src, uchar *smask) { uchar dst[IPaddrlen], mask[IPaddrlen]; if(isv4(gaddr)){ parseip(dst, "0.0.0.0"); parseipmask(mask, "0.0.0.0"); if(src == nil) src = dst; if(smask == nil) smask = mask; } else { parseip(dst, "2000::"); parseipmask(mask, "/3"); if(src == nil) src = IPnoaddr; if(smask == nil) smask = IPnoaddr; } addroute(dst, mask, gaddr, ia, src, smask); /* also add a source specific route */ if(ipcmp(src, IPnoaddr) != 0 && ipcmp(src, v4prefix) != 0) addroute(dst, mask, gaddr, ia, src, IPallbits); } void refresh(void) { char file[64]; int fd; snprint(file, sizeof file, "%s/cs", conf.mpoint); if((fd = open(file, OWRITE)) >= 0){ write(fd, "refresh", 7); close(fd); } snprint(file, sizeof file, "%s/dns", conf.mpoint); if((fd = open(file, OWRITE)) >= 0){ write(fd, "refresh", 7); close(fd); } } void catch(void*, char *msg) { if(strstr(msg, "alarm")) noted(NCONT); noted(NDFLT); } /* * based on libthread's threadsetname, but drags in less library code. * actually just sets the arguments displayed. */ void procsetname(char *fmt, ...) { int fd; char *cmdname; char buf[128]; va_list arg; va_start(arg, fmt); cmdname = vsmprint(fmt, arg); va_end(arg); if (cmdname == nil) return; snprint(buf, sizeof buf, "#p/%d/args", getpid()); if((fd = open(buf, OWRITE)) >= 0){ write(fd, cmdname, strlen(cmdname)+1); close(fd); } free(cmdname); } /* return pseudo-random integer in range low...(hi-1) */ ulong randint(ulong low, ulong hi) { if (hi < low) return low; return low + nrand(hi - low); } long jitter(void) /* compute small pseudo-random delay in ms */ { return randint(0, 10*1000); } int countaddrs(uchar *a, int len) { int i; for(i = 0; i < len && validip(a); i += IPaddrlen, a += IPaddrlen) ; return i / IPaddrlen; } void addaddrs(uchar *to, int nto, uchar *from, int nfrom) { int i, j; for(i = 0; i < nfrom; i += IPaddrlen, from += IPaddrlen){ if(!validip(from)) continue; for(j = 0; j < nto && validip(to+j); j += IPaddrlen){ if(ipcmp(to+j, from) == 0) return; } if(j == nto) return; ipmove(to+j, from); } } void addnames(char *d, char *s, int len) { char *p, *e, *f; int n; for(;;s++){ if((e = strchr(s, ' ')) == nil) e = strchr(s, 0); n = e - s; if(n == 0) goto next; for(p = d;;p++){ if((f = strchr(p, ' ')) == nil) f = strchr(p, 0); if(f - p == n && memcmp(s, p, n) == 0) goto next; p = f; if(*p == 0) break; } if(1 + n + p - d >= len) break; if(p > d) *p++ = ' '; p[n] = 0; memmove(p, s, n); next: s = e; if(*s == 0) break; } } static Ndbtuple* uniquent(Ndbtuple *t) { Ndbtuple **l, *x; l = &t->entry; while((x = *l) != nil){ if(strcmp(t->attr, x->attr) != 0){ l = &x->entry; continue; } *l = x->entry; x->entry = nil; ndbfree(x); } return t; } /* read configuration (except laddr) for myip from ndb */ void ndb2conf(Ndb *db, uchar *myip) { int nattr; char *attrs[10], val[64]; uchar ip[IPaddrlen]; Ndbtuple *t, *nt; ipmove(conf.mask, defmask(conf.laddr)); memset(conf.gaddr, 0, sizeof(conf.gaddr)); memset(conf.dns, 0, sizeof(conf.dns)); memset(conf.ntp, 0, sizeof(conf.ntp)); memset(conf.fs, 0, sizeof(conf.fs)); memset(conf.auth, 0, sizeof(conf.auth)); memset(conf.dnsdomain, 0, sizeof(conf.dnsdomain)); if(db == nil) return; nattr = 0; attrs[nattr++] = "ipmask"; attrs[nattr++] = "ipgw"; attrs[nattr++] = "@dns"; attrs[nattr++] = "@ntp"; attrs[nattr++] = "@fs"; attrs[nattr++] = "@auth"; attrs[nattr++] = "dnsdomain"; snprint(val, sizeof(val), "%I", myip); t = ndbipinfo(db, "ip", val, attrs, nattr); for(nt = t; nt != nil; nt = nt->entry) { if(strcmp(nt->attr, "dnsdomain") == 0) { addnames(conf.dnsdomain, nt->val, sizeof(conf.dnsdomain)); continue; } if(strcmp(nt->attr, "ipmask") == 0) { nt = uniquent(nt); parseipmask(conf.mask, nt->val); /* could be -1 */ continue; } if(parseip(ip, nt->val) == -1) { fprint(2, "%s: bad %s address in ndb: %s\n", argv0, nt->attr, nt->val); continue; } if(strcmp(nt->attr, "ipgw") == 0) { nt = uniquent(nt); ipmove(conf.gaddr, ip); } else if(strcmp(nt->attr, "dns") == 0) { addaddrs(conf.dns, sizeof(conf.dns), ip, IPaddrlen); } else if(strcmp(nt->attr, "ntp") == 0) { addaddrs(conf.ntp, sizeof(conf.ntp), ip, IPaddrlen); } else if(strcmp(nt->attr, "fs") == 0) { addaddrs(conf.fs, sizeof(conf.fs), ip, IPaddrlen); } else if(strcmp(nt->attr, "auth") == 0) { addaddrs(conf.auth, sizeof(conf.auth), ip, IPaddrlen); } } ndbfree(t); } Ndb* opendatabase(void) { static Ndb *db; if(db != nil) ndbclose(db); db = ndbopen(dbfile); return db; } /* add addresses for my ethernet address from ndb */ static void ndbconfig(void) { uchar ips[128*IPaddrlen]; char etheraddr[32], *attr; Ndbtuple *t, *nt; Ndb *db; int n, i; db = opendatabase(); if(db == nil) sysfatal("can't open ndb: %r"); if(validip(conf.laddr)){ ndb2conf(db, conf.laddr); doadd(); return; } memset(ips, 0, sizeof(ips)); if(conf.hwatype != 1) sysfatal("can't read hardware address"); snprint(etheraddr, sizeof(etheraddr), "%E", conf.hwa); attr = "ip"; t = ndbipinfo(db, "ether", etheraddr, &attr, 1); for(nt = t; nt != nil; nt = nt->entry) { if(parseip(conf.laddr, nt->val) == -1){ fprint(2, "%s: bad %s address in ndb: %s\n", argv0, nt->attr, nt->val); continue; } addaddrs(ips, sizeof(ips), conf.laddr, IPaddrlen); } ndbfree(t); n = countaddrs(ips, sizeof(ips)); if(n == 0) sysfatal("no ip addresses found in ndb"); /* add link local address first, if not already done */ if(!findllip(conf.lladdr, ifc)){ for(i = 0; i < n; i++){ ipmove(conf.laddr, ips+i*IPaddrlen); if(ISIPV6LINKLOCAL(conf.laddr)){ ipv6auto = 0; ipmove(conf.lladdr, conf.laddr); ndb2conf(db, conf.laddr); doadd(); break; } } if(ipv6auto){ ipmove(conf.laddr, IPnoaddr); doadd(); } } /* add v4 addresses and v6 if link local address is available */ for(i = 0; i < n; i++){ ipmove(conf.laddr, ips+i*IPaddrlen); if(isv4(conf.laddr) || ipcmp(conf.laddr, conf.lladdr) != 0){ ndb2conf(db, conf.laddr); doadd(); } } }