Blob


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