diff options
Diffstat (limited to 'src/cmd/ip/dhcpd/db.c')
-rwxr-xr-x | src/cmd/ip/dhcpd/db.c | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/src/cmd/ip/dhcpd/db.c b/src/cmd/ip/dhcpd/db.c new file mode 100755 index 00000000..414d85b9 --- /dev/null +++ b/src/cmd/ip/dhcpd/db.c @@ -0,0 +1,452 @@ +#include <u.h> +#include <libc.h> +#include <ip.h> +#include <bio.h> +#include <ndb.h> +#include <ctype.h> +#include "dat.h" + +/* + * format of a binding entry: + * char ipaddr[32]; + * char id[32]; + * char hwa[32]; + * char otime[10]; + */ +Binding *bcache; +uchar bfirst[IPaddrlen]; +char *binddir = nil; +char *xbinddir = "#9/ndb/dhcp"; + +/* + * convert a byte array to hex + */ +static char +hex(int x) +{ + if(x < 10) + return x + '0'; + return x - 10 + 'a'; +} +extern char* +tohex(char *hdr, uchar *p, int len) +{ + char *s, *sp; + int hlen; + + hlen = strlen(hdr); + s = malloc(hlen + 2*len + 1); + sp = s; + strcpy(sp, hdr); + sp += hlen; + for(; len > 0; len--){ + *sp++ = hex(*p>>4); + *sp++ = hex(*p & 0xf); + p++; + } + *sp = 0; + return s; +} + +/* + * convert a client id to a string. If it's already + * ascii, leave it be. Otherwise, convert it to hex. + */ +extern char* +toid(uchar *p, int n) +{ + int i; + char *s; + + for(i = 0; i < n; i++) + if(!isprint(p[i])) + return tohex("id", p, n); + s = malloc(n + 1); + memmove(s, p, n); + s[n] = 0; + return s; +} + +/* + * increment an ip address + */ +static void +incip(uchar *ip) +{ + int i, x; + + for(i = IPaddrlen-1; i >= 0; i--){ + x = ip[i]; + x++; + ip[i] = x; + if((x & 0x100) == 0) + break; + } +} + +/* + * find a binding for an id or hardware address + */ +static int +lockopen(char *file) +{ + char err[ERRMAX]; + int fd, tries; + + for(tries = 0; tries < 5; tries++){ + fd = open(file, OLOCK|ORDWR); + if(fd >= 0) + return fd; +print("open %s: %r\n", file); + errstr(err, sizeof err); + if(strstr(err, "lock")){ + /* wait for other process to let go of lock */ + sleep(250); + + /* try again */ + continue; + } + if(strstr(err, "exist") || strstr(err, "No such")){ + /* no file, create an exclusive access file */ + fd = create(file, ORDWR, DMEXCL|0666); + chmod(file, 0666); + if(fd >= 0) + return fd; + } + } + return -1; +} + +void +setbinding(Binding *b, char *id, long t) +{ + if(b->boundto) + free(b->boundto); + + b->boundto = strdup(id); + b->lease = t; +} + +static void +parsebinding(Binding *b, char *buf) +{ + long t; + char *id, *p; + + /* parse */ + t = atoi(buf); + id = strchr(buf, '\n'); + if(id){ + *id++ = 0; + p = strchr(id, '\n'); + if(p) + *p = 0; + } else + id = ""; + + /* replace any past info */ + setbinding(b, id, t); +} + +static int +writebinding(int fd, Binding *b) +{ + Dir *d; + + seek(fd, 0, 0); + if(fprint(fd, "%ld\n%s\n", b->lease, b->boundto) < 0) + return -1; + d = dirfstat(fd); + if(d == nil) + return -1; + b->q.type = d->qid.type; + b->q.path = d->qid.path; + b->q.vers = d->qid.vers; + free(d); + return 0; +} + +/* + * synchronize cached binding with file. the file always wins. + */ +int +syncbinding(Binding *b, int returnfd) +{ + char buf[512]; + int i, fd; + Dir *d; + + if(binddir == nil) + binddir = unsharp(xbinddir); + + snprint(buf, sizeof(buf), "%s/%I", binddir, b->ip); + fd = lockopen(buf); + if(fd < 0){ + /* assume someone else is using it */ + b->lease = time(0) + OfferTimeout; + return -1; + } + + /* reread if changed */ + d = dirfstat(fd); + if(d != nil) /* BUG? */ + if(d->qid.type != b->q.type || d->qid.path != b->q.path || d->qid.vers != b->q.vers){ + i = read(fd, buf, sizeof(buf)-1); + if(i < 0) + i = 0; + buf[i] = 0; + parsebinding(b, buf); + b->lasttouched = d->mtime; + b->q.path = d->qid.path; + b->q.vers = d->qid.vers; + } + + free(d); + + if(returnfd) + return fd; + + close(fd); + return 0; +} + +extern int +samenet(uchar *ip, Info *iip) +{ + uchar x[IPaddrlen]; + + maskip(iip->ipmask, ip, x); + return ipcmp(x, iip->ipnet) == 0; +} + +/* + * create a record for each binding + */ +extern void +initbinding(uchar *first, int n) +{ + while(n-- > 0){ + iptobinding(first, 1); + incip(first); + } +} + +/* + * find a binding for a specific ip address + */ +extern Binding* +iptobinding(uchar *ip, int mk) +{ + Binding *b; + + for(b = bcache; b; b = b->next){ + if(ipcmp(b->ip, ip) == 0){ + syncbinding(b, 0); + return b; + } + } + + if(mk == 0) + return 0; + b = malloc(sizeof(*b)); + memset(b, 0, sizeof(*b)); + ipmove(b->ip, ip); + b->next = bcache; + bcache = b; + syncbinding(b, 0); + return b; +} + +static void +lognolease(Binding *b) +{ + /* renew the old binding, and hope it eventually goes away */ + b->offer = 5*60; + commitbinding(b); + + /* complain if we haven't in the last 5 minutes */ + if(now - b->lastcomplained < 5*60) + return; + syslog(0, blog, "dhcp: lease for %I to %s ended at %ld but still in use\n", + b->ip, b->boundto != nil ? b->boundto : "?", b->lease); + b->lastcomplained = now; +} + +/* + * find a free binding for a hw addr or id on the same network as iip + */ +extern Binding* +idtobinding(char *id, Info *iip, int ping) +{ + Binding *b, *oldest; + int oldesttime; + + /* + * first look for an old binding that matches. that way + * clients will tend to keep the same ip addresses. + */ + for(b = bcache; b; b = b->next){ + if(b->boundto && strcmp(b->boundto, id) == 0){ + if(!samenet(b->ip, iip)) + continue; + + /* check with the other servers */ + syncbinding(b, 0); + if(strcmp(b->boundto, id) == 0) + return b; + } + } + +print("looking for old for %I\n", iip->ipnet); + + /* + * look for oldest binding that we think is unused + */ + for(;;){ + oldest = nil; + oldesttime = 0; + for(b = bcache; b; b = b->next){ +print("tried %d now %d lease %d exp %d %I\n", b->tried, now, b->lease, b->expoffer, b->ip); + if(b->tried != now) + if(b->lease < now && b->expoffer < now && samenet(b->ip, iip)) + if(oldest == nil || b->lasttouched < oldesttime){ + /* sync and check again */ + syncbinding(b, 0); + if(b->lease < now && b->expoffer < now && samenet(b->ip, iip)) + if(oldest == nil || b->lasttouched < oldesttime){ + oldest = b; +print("have oldest\n"); + oldesttime = b->lasttouched; + } + } + } + if(oldest == nil) + break; + + /* make sure noone is still using it */ + oldest->tried = now; +print("return oldest\n"); + if(ping == 0 || icmpecho(oldest->ip) == 0) + return oldest; + + lognolease(oldest); /* sets lastcomplained */ + } + + /* try all bindings */ + for(b = bcache; b; b = b->next){ + syncbinding(b, 0); + if(b->tried != now) + if(b->lease < now && b->expoffer < now && samenet(b->ip, iip)){ + b->tried = now; + if(ping == 0 || icmpecho(b->ip) == 0) + return b; + + lognolease(b); + } + } + + /* nothing worked, give up */ + return 0; +} + +/* + * create an offer + */ +extern void +mkoffer(Binding *b, char *id, long leasetime) +{ + if(leasetime <= 0){ + if(b->lease > now + minlease) + leasetime = b->lease - now; + else + leasetime = minlease; + } + if(b->offeredto) + free(b->offeredto); + b->offeredto = strdup(id); + b->offer = leasetime; + b->expoffer = now + OfferTimeout; +} + +/* + * find an offer for this id + */ +extern Binding* +idtooffer(char *id, Info *iip) +{ + Binding *b; + + /* look for an offer to this id */ + for(b = bcache; b; b = b->next){ +print("%I %I ? offeredto %s id %s\n", b->ip, iip->ipnet, b->offeredto, id); + if(b->offeredto && strcmp(b->offeredto, id) == 0 && samenet(b->ip, iip)){ + /* make sure some other system hasn't stolen it */ + syncbinding(b, 0); +print("b->lease %d now %d boundto %s offered %s\n", b->lease, now, b->boundto, b->offeredto); + if(b->lease < now + || (b->boundto && strcmp(b->boundto, b->offeredto) == 0)) + return b; + } + } + return 0; +} + +/* + * commit a lease, this could fail + */ +extern int +commitbinding(Binding *b) +{ + int fd; + long now; + + now = time(0); + + if(b->offeredto == 0) + return -1; + fd = syncbinding(b, 1); + if(fd < 0) + return -1; + if(b->lease > now && b->boundto && strcmp(b->boundto, b->offeredto) != 0){ + close(fd); + return -1; + } + setbinding(b, b->offeredto, now + b->offer); + b->lasttouched = now; + + if(writebinding(fd, b) < 0){ + close(fd); + return -1; + } + close(fd); + return 0; +} + +/* + * commit a lease, this could fail + */ +extern int +releasebinding(Binding *b, char *id) +{ + int fd; + long now; + + now = time(0); + + fd = syncbinding(b, 1); + if(fd < 0) + return -1; + if(b->lease > now && b->boundto && strcmp(b->boundto, id) != 0){ + close(fd); + return -1; + } + b->lease = 0; + b->expoffer = 0; + + if(writebinding(fd, b) < 0){ + close(fd); + return -1; + } + close(fd); + return 0; +} |