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