db.c (7590B)
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 = nil; 19 char *xbinddir = "#9/ndb/dhcp"; 20 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; 36 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 } 50 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; 60 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 } 69 70 /* 71 * increment an ip address 72 */ 73 static void 74 incip(uchar *ip) 75 { 76 int i, x; 77 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 } 86 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; 95 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); 104 105 /* try again */ 106 continue; 107 } 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; 114 } 115 } 116 return -1; 117 } 118 119 void 120 setbinding(Binding *b, char *id, long t) 121 { 122 if(b->boundto) 123 free(b->boundto); 124 125 b->boundto = strdup(id); 126 b->lease = t; 127 } 128 129 static void 130 parsebinding(Binding *b, char *buf) 131 { 132 long t; 133 char *id, *p; 134 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 = ""; 145 146 /* replace any past info */ 147 setbinding(b, id, t); 148 } 149 150 static int 151 writebinding(int fd, Binding *b) 152 { 153 Dir *d; 154 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; 166 } 167 168 /* 169 * synchronize cached binding with file. the file always wins. 170 */ 171 int 172 syncbinding(Binding *b, int returnfd) 173 { 174 char buf[512]; 175 int i, fd; 176 Dir *d; 177 178 if(binddir == nil) 179 binddir = unsharp(xbinddir); 180 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; 187 } 188 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; 201 } 202 203 free(d); 204 205 if(returnfd) 206 return fd; 207 208 close(fd); 209 return 0; 210 } 211 212 extern int 213 samenet(uchar *ip, Info *iip) 214 { 215 uchar x[IPaddrlen]; 216 217 maskip(iip->ipmask, ip, x); 218 return ipcmp(x, iip->ipnet) == 0; 219 } 220 221 /* 222 * create a record for each binding 223 */ 224 extern void 225 initbinding(uchar *first, int n) 226 { 227 while(n-- > 0){ 228 iptobinding(first, 1); 229 incip(first); 230 } 231 } 232 233 /* 234 * find a binding for a specific ip address 235 */ 236 extern Binding* 237 iptobinding(uchar *ip, int mk) 238 { 239 Binding *b; 240 241 for(b = bcache; b; b = b->next){ 242 if(ipcmp(b->ip, ip) == 0){ 243 syncbinding(b, 0); 244 return b; 245 } 246 } 247 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; 257 } 258 259 static void 260 lognolease(Binding *b) 261 { 262 /* renew the old binding, and hope it eventually goes away */ 263 b->offer = 5*60; 264 commitbinding(b); 265 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; 272 } 273 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) 279 { 280 Binding *b, *oldest; 281 int oldesttime; 282 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; 291 292 /* check with the other servers */ 293 syncbinding(b, 0); 294 if(strcmp(b->boundto, id) == 0) 295 return b; 296 } 297 } 298 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; 315 } 316 } 317 } 318 if(oldest == nil) 319 break; 320 321 /* make sure noone is still using it */ 322 oldest->tried = now; 323 if(ping == 0 || icmpecho(oldest->ip) == 0) 324 return oldest; 325 326 lognolease(oldest); /* sets lastcomplained */ 327 } 328 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; 337 338 lognolease(b); 339 } 340 } 341 342 /* nothing worked, give up */ 343 return 0; 344 } 345 346 /* 347 * create an offer 348 */ 349 extern void 350 mkoffer(Binding *b, char *id, long leasetime) 351 { 352 if(leasetime <= 0){ 353 if(b->lease > now + minlease) 354 leasetime = b->lease - now; 355 else 356 leasetime = minlease; 357 } 358 if(b->offeredto) 359 free(b->offeredto); 360 b->offeredto = strdup(id); 361 b->offer = leasetime; 362 b->expoffer = now + OfferTimeout; 363 } 364 365 /* 366 * find an offer for this id 367 */ 368 extern Binding* 369 idtooffer(char *id, Info *iip) 370 { 371 Binding *b; 372 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; 381 } 382 } 383 return 0; 384 } 385 386 /* 387 * commit a lease, this could fail 388 */ 389 extern int 390 commitbinding(Binding *b) 391 { 392 int fd; 393 long now; 394 395 now = time(0); 396 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; 405 } 406 setbinding(b, b->offeredto, now + b->offer); 407 b->lasttouched = now; 408 409 if(writebinding(fd, b) < 0){ 410 close(fd); 411 return -1; 412 } 413 close(fd); 414 return 0; 415 } 416 417 /* 418 * commit a lease, this could fail 419 */ 420 extern int 421 releasebinding(Binding *b, char *id) 422 { 423 int fd; 424 long now; 425 426 now = time(0); 427 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; 434 } 435 b->lease = 0; 436 b->expoffer = 0; 437 438 if(writebinding(fd, b) < 0){ 439 close(fd); 440 return -1; 441 } 442 close(fd); 443 return 0; 444 }