]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/dhcpd/db.c
socksd: setnetmtpt
[plan9front.git] / sys / src / cmd / ip / dhcpd / db.c
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
24 hex(int x)
25 {
26         if(x < 10)
27                 return x + '0';
28         return x - 10 + 'a';
29 }
30 extern char*
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*
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
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
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
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
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
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
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
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
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*
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
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*
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
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*
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
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
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 }