/* * 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. */ /* Portions of this file are Copyright (C) 2015-2018 Giacomo Tesio * See /doc/license/gpl-2.0.txt for details about the licensing. */ /* Portions of this file are Copyright (C) 9front's team. * See /doc/license/9front-mit for details about the licensing. * See http://git.9front.org/plan9front/plan9front/HEAD/info.html for a list of authors. */ #include #include #include #include #include #include #include "dat.h" /* * format of a binding entry: * char ipaddr[32]; * char id[32]; * char hwa[32]; * char otime[10]; */ Binding *bcache; uint8_t bfirst[IPaddrlen]; char *binddir = "/lib/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, uint8_t *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(uint8_t *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(uint8_t *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 = sys_open(file, ORDWR); if(fd >= 0) return fd; sys_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")){ /* no file, create an exclusive access file */ fd = ocreate(file, ORDWR, DMEXCL|0664); if(fd >= 0) return fd; } } return -1; } void setbinding(Binding *b, char *id, int32_t t) { if(b->boundto) free(b->boundto); b->boundto = strdup(id); b->lease = t; } static void parsebinding(Binding *b, char *buf) { int32_t 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; sys_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; 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 = jehanne_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; sys_close(fd); return 0; } extern int samenet(uint8_t *ip, Info *iip) { uint8_t x[IPaddrlen]; maskip(iip->ipmask, ip, x); return ipcmp(x, iip->ipnet) == 0; } /* * create a record for each binding */ extern void initbinding(uint8_t *first, int n) { while(n-- > 0){ iptobinding(first, 1); incip(first); } } /* * find a binding for a specific ip address */ extern Binding* iptobinding(uint8_t *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", 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, int32_t 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; int32_t 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){ sys_close(fd); return -1; } setbinding(b, b->offeredto, now + b->offer); b->lasttouched = now; if(writebinding(fd, b) < 0){ sys_close(fd); return -1; } sys_close(fd); return 0; } /* * commit a lease, this could fail */ extern int releasebinding(Binding *b, char *id) { int fd; int32_t now; now = time(0); fd = syncbinding(b, 1); if(fd < 0) return -1; if(b->lease > now && b->boundto && strcmp(b->boundto, id) != 0){ sys_close(fd); return -1; } b->lease = 0; b->expoffer = 0; if(writebinding(fd, b) < 0){ sys_close(fd); return -1; } sys_close(fd); return 0; }