| #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; |
| 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; |
| } |
| } |
| |
| /* |
| * look for oldest binding that we think is unused |
| */ |
| for(;;){ |
| oldest = nil; |
| oldesttime = 0; |
| for(b = bcache; b; b = b->next){ |
| 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; |
| oldesttime = b->lasttouched; |
| } |
| } |
| } |
| if(oldest == nil) |
| break; |
| |
| /* make sure noone is still using it */ |
| oldest->tried = now; |
| 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){ |
| if(b->offeredto && strcmp(b->offeredto, id) == 0 && samenet(b->ip, iip)){ |
| /* make sure some other system hasn't stolen it */ |
| syncbinding(b, 0); |
| 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; |
| } |