xref: /plan9/sys/src/cmd/ip/dhcpd/db.c (revision ec46fab06dcae3e636b775c4eaa679036316e1d8)
1 #include <u.h>
2 #include <libc.h>
3 #include <ip.h>
4 #include <bio.h>
5 #include <ndb.h>
6 #include <ctype.h>
7 #include "dat.h"
8 
9 /*
10  *  format of a binding entry:
11  *	char ipaddr[32];
12  *	char id[32];
13  *	char hwa[32];
14  *	char otime[10];
15  */
16 Binding *bcache;
17 uchar bfirst[IPaddrlen];
18 char *binddir = "/lib/ndb/dhcp";
19 
20 /*
21  *  convert a byte array to hex
22  */
23 static char
hex(int x)24 hex(int x)
25 {
26 	if(x < 10)
27 		return x + '0';
28 	return x - 10 + 'a';
29 }
30 extern char*
tohex(char * hdr,uchar * p,int len)31 tohex(char *hdr, uchar *p, int len)
32 {
33 	char *s, *sp;
34 	int hlen;
35 
36 	hlen = strlen(hdr);
37 	s = malloc(hlen + 2*len + 1);
38 	sp = s;
39 	strcpy(sp, hdr);
40 	sp += hlen;
41 	for(; len > 0; len--){
42 		*sp++ = hex(*p>>4);
43 		*sp++ = hex(*p & 0xf);
44 		p++;
45 	}
46 	*sp = 0;
47 	return s;
48 }
49 
50 /*
51  *  convert a client id to a string.  If it's already
52  *  ascii, leave it be.  Otherwise, convert it to hex.
53  */
54 extern char*
toid(uchar * p,int n)55 toid(uchar *p, int n)
56 {
57 	int i;
58 	char *s;
59 
60 	for(i = 0; i < n; i++)
61 		if(!isprint(p[i]))
62 			return tohex("id", p, n);
63 	s = malloc(n + 1);
64 	memmove(s, p, n);
65 	s[n] = 0;
66 	return s;
67 }
68 
69 /*
70  *  increment an ip address
71  */
72 static void
incip(uchar * ip)73 incip(uchar *ip)
74 {
75 	int i, x;
76 
77 	for(i = IPaddrlen-1; i >= 0; i--){
78 		x = ip[i];
79 		x++;
80 		ip[i] = x;
81 		if((x & 0x100) == 0)
82 			break;
83 	}
84 }
85 
86 /*
87  *  find a binding for an id or hardware address
88  */
89 static int
lockopen(char * file)90 lockopen(char *file)
91 {
92 	char err[ERRMAX];
93 	int fd, tries;
94 
95 	for(tries = 0; tries < 5; tries++){
96 		fd = open(file, ORDWR);
97 		if(fd >= 0)
98 			return fd;
99 		errstr(err, sizeof err);
100 		if(strstr(err, "lock")){
101 			/* wait for other process to let go of lock */
102 			sleep(250);
103 
104 			/* try again */
105 			continue;
106 		}
107 		if(strstr(err, "exist")){
108 			/* no file, create an exclusive access file */
109 			fd = create(file, ORDWR, DMEXCL|0664);
110 			if(fd >= 0)
111 				return fd;
112 		}
113 	}
114 	return -1;
115 }
116 
117 void
setbinding(Binding * b,char * id,long t)118 setbinding(Binding *b, char *id, long t)
119 {
120 	if(b->boundto)
121 		free(b->boundto);
122 
123 	b->boundto = strdup(id);
124 	b->lease = t;
125 }
126 
127 static void
parsebinding(Binding * b,char * buf)128 parsebinding(Binding *b, char *buf)
129 {
130 	long t;
131 	char *id, *p;
132 
133 	/* parse */
134 	t = atoi(buf);
135 	id = strchr(buf, '\n');
136 	if(id){
137 		*id++ = 0;
138 		p = strchr(id, '\n');
139 		if(p)
140 			*p = 0;
141 	} else
142 		id = "";
143 
144 	/* replace any past info */
145 	setbinding(b, id, t);
146 }
147 
148 static int
writebinding(int fd,Binding * b)149 writebinding(int fd, Binding *b)
150 {
151 	Dir *d;
152 
153 	seek(fd, 0, 0);
154 	if(fprint(fd, "%ld\n%s\n", b->lease, b->boundto) < 0)
155 		return -1;
156 	d = dirfstat(fd);
157 	if(d == nil)
158 		return -1;
159 	b->q.type = d->qid.type;
160 	b->q.path = d->qid.path;
161 	b->q.vers = d->qid.vers;
162 	free(d);
163 	return 0;
164 }
165 
166 /*
167  *  synchronize cached binding with file.  the file always wins.
168  */
169 int
syncbinding(Binding * b,int returnfd)170 syncbinding(Binding *b, int returnfd)
171 {
172 	char buf[512];
173 	int i, fd;
174 	Dir *d;
175 
176 	snprint(buf, sizeof(buf), "%s/%I", binddir, b->ip);
177 	fd = lockopen(buf);
178 	if(fd < 0){
179 		/* assume someone else is using it */
180 		b->lease = time(0) + OfferTimeout;
181 		return -1;
182 	}
183 
184 	/* reread if changed */
185 	d = dirfstat(fd);
186 	if(d != nil)	/* BUG? */
187 	if(d->qid.type != b->q.type || d->qid.path != b->q.path || d->qid.vers != b->q.vers){
188 		i = read(fd, buf, sizeof(buf)-1);
189 		if(i < 0)
190 			i = 0;
191 		buf[i] = 0;
192 		parsebinding(b, buf);
193 		b->lasttouched = d->mtime;
194 		b->q.path = d->qid.path;
195 		b->q.vers = d->qid.vers;
196 	}
197 
198 	free(d);
199 
200 	if(returnfd)
201 		return fd;
202 
203 	close(fd);
204 	return 0;
205 }
206 
207 extern int
samenet(uchar * ip,Info * iip)208 samenet(uchar *ip, Info *iip)
209 {
210 	uchar x[IPaddrlen];
211 
212 	maskip(iip->ipmask, ip, x);
213 	return ipcmp(x, iip->ipnet) == 0;
214 }
215 
216 /*
217  *  create a record for each binding
218  */
219 extern void
initbinding(uchar * first,int n)220 initbinding(uchar *first, int n)
221 {
222 	while(n-- > 0){
223 		iptobinding(first, 1);
224 		incip(first);
225 	}
226 }
227 
228 /*
229  *  find a binding for a specific ip address
230  */
231 extern Binding*
iptobinding(uchar * ip,int mk)232 iptobinding(uchar *ip, int mk)
233 {
234 	Binding *b;
235 
236 	for(b = bcache; b; b = b->next){
237 		if(ipcmp(b->ip, ip) == 0){
238 			syncbinding(b, 0);
239 			return b;
240 		}
241 	}
242 
243 	if(mk == 0)
244 		return 0;
245 	b = malloc(sizeof(*b));
246 	memset(b, 0, sizeof(*b));
247 	ipmove(b->ip, ip);
248 	b->next = bcache;
249 	bcache = b;
250 	syncbinding(b, 0);
251 	return b;
252 }
253 
254 static void
lognolease(Binding * b)255 lognolease(Binding *b)
256 {
257 	/* renew the old binding, and hope it eventually goes away */
258 	b->offer = 5*60;
259 	commitbinding(b);
260 
261 	/* complain if we haven't in the last 5 minutes */
262 	if(now - b->lastcomplained < 5*60)
263 		return;
264 	syslog(0, blog, "dhcp: lease for %I to %s ended at %ld but still in use\n",
265 		b->ip, b->boundto != nil ? b->boundto : "?", b->lease);
266 	b->lastcomplained = now;
267 }
268 
269 /*
270  *  find a free binding for a hw addr or id on the same network as iip
271  */
272 extern Binding*
idtobinding(char * id,Info * iip,int ping)273 idtobinding(char *id, Info *iip, int ping)
274 {
275 	Binding *b, *oldest;
276 	int oldesttime;
277 
278 	/*
279 	 *  first look for an old binding that matches.  that way
280 	 *  clients will tend to keep the same ip addresses.
281 	 */
282 	for(b = bcache; b; b = b->next){
283 		if(b->boundto && strcmp(b->boundto, id) == 0){
284 			if(!samenet(b->ip, iip))
285 				continue;
286 
287 			/* check with the other servers */
288 			syncbinding(b, 0);
289 			if(strcmp(b->boundto, id) == 0)
290 				return b;
291 		}
292 	}
293 
294 	/*
295 	 *  look for oldest binding that we think is unused
296 	 */
297 	for(;;){
298 		oldest = nil;
299 		oldesttime = 0;
300 		for(b = bcache; b; b = b->next){
301 			if(b->tried != now)
302 			if(b->lease < now && b->expoffer < now && samenet(b->ip, iip))
303 			if(oldest == nil || b->lasttouched < oldesttime){
304 				/* sync and check again */
305 				syncbinding(b, 0);
306 				if(b->lease < now && b->expoffer < now && samenet(b->ip, iip))
307 				if(oldest == nil || b->lasttouched < oldesttime){
308 					oldest = b;
309 					oldesttime = b->lasttouched;
310 				}
311 			}
312 		}
313 		if(oldest == nil)
314 			break;
315 
316 		/* make sure noone is still using it */
317 		oldest->tried = now;
318 		if(ping == 0 || icmpecho(oldest->ip) == 0)
319 			return oldest;
320 
321 		lognolease(oldest);	/* sets lastcomplained */
322 	}
323 
324 	/* try all bindings */
325 	for(b = bcache; b; b = b->next){
326 		syncbinding(b, 0);
327 		if(b->tried != now)
328 		if(b->lease < now && b->expoffer < now && samenet(b->ip, iip)){
329 			b->tried = now;
330 			if(ping == 0 || icmpecho(b->ip) == 0)
331 				return b;
332 
333 			lognolease(b);
334 		}
335 	}
336 
337 	/* nothing worked, give up */
338 	return 0;
339 }
340 
341 /*
342  *  create an offer
343  */
344 extern void
mkoffer(Binding * b,char * id,long leasetime)345 mkoffer(Binding *b, char *id, long leasetime)
346 {
347 	if(leasetime <= 0){
348 		if(b->lease > now + minlease)
349 			leasetime = b->lease - now;
350 		else
351 			leasetime = minlease;
352 	}
353 	if(b->offeredto)
354 		free(b->offeredto);
355 	b->offeredto = strdup(id);
356 	b->offer = leasetime;
357 	b->expoffer = now + OfferTimeout;
358 }
359 
360 /*
361  *  find an offer for this id
362  */
363 extern Binding*
idtooffer(char * id,Info * iip)364 idtooffer(char *id, Info *iip)
365 {
366 	Binding *b;
367 
368 	/* look for an offer to this id */
369 	for(b = bcache; b; b = b->next){
370 		if(b->offeredto && strcmp(b->offeredto, id) == 0 && samenet(b->ip, iip)){
371 			/* make sure some other system hasn't stolen it */
372 			syncbinding(b, 0);
373 			if(b->lease < now
374 			|| (b->boundto && strcmp(b->boundto, b->offeredto) == 0))
375 				return b;
376 		}
377 	}
378 	return 0;
379 }
380 
381 /*
382  *  commit a lease, this could fail
383  */
384 extern int
commitbinding(Binding * b)385 commitbinding(Binding *b)
386 {
387 	int fd;
388 	long now;
389 
390 	now = time(0);
391 
392 	if(b->offeredto == 0)
393 		return -1;
394 	fd = syncbinding(b, 1);
395 	if(fd < 0)
396 		return -1;
397 	if(b->lease > now && b->boundto && strcmp(b->boundto, b->offeredto) != 0){
398 		close(fd);
399 		return -1;
400 	}
401 	setbinding(b, b->offeredto, now + b->offer);
402 	b->lasttouched = now;
403 
404 	if(writebinding(fd, b) < 0){
405 		close(fd);
406 		return -1;
407 	}
408 	close(fd);
409 	return 0;
410 }
411 
412 /*
413  *  commit a lease, this could fail
414  */
415 extern int
releasebinding(Binding * b,char * id)416 releasebinding(Binding *b, char *id)
417 {
418 	int fd;
419 	long now;
420 
421 	now = time(0);
422 
423 	fd = syncbinding(b, 1);
424 	if(fd < 0)
425 		return -1;
426 	if(b->lease > now && b->boundto && strcmp(b->boundto, id) != 0){
427 		close(fd);
428 		return -1;
429 	}
430 	b->lease = 0;
431 	b->expoffer = 0;
432 
433 	if(writebinding(fd, b) < 0){
434 		close(fd);
435 		return -1;
436 	}
437 	close(fd);
438 	return 0;
439 }
440