blob: f4b48552e3b14f4fe0dcf3f73092b52c1651a8a6 [file] [log] [blame]
#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;
}