1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <unistd.h> 29 #include <string.h> 30 #include <stdarg.h> 31 #include <sys/types.h> 32 #include <sys/sysmacros.h> 33 #include <assert.h> 34 #include <errno.h> 35 #include <syslog.h> 36 #include <fcntl.h> 37 #include <sys/stat.h> 38 #include <sys/mman.h> 39 #include <sys/socket.h> 40 #include <netinet/in.h> 41 #include <netinet/in_systm.h> 42 #include <arpa/inet.h> 43 #include <net/if.h> 44 #include <netinet/dhcp.h> 45 #include "dhcpd.h" 46 #include "interfaces.h" 47 #include <locale.h> 48 49 /* 50 * This file contains the access routines for the dhcp databases. 51 */ 52 53 static dsvc_dnet_t *get_dnet(struct in_addr *); 54 static boolean_t unhash_dnet(dsvc_dnet_t *, boolean_t); 55 static int dnet_cmp(dsvc_dnet_t *, dsvc_dnet_t *); 56 static dsvc_clnt_t *get_client(hash_tbl *, uchar_t *, uchar_t); 57 static int clnt_cmp(dsvc_clnt_t *, dsvc_clnt_t *); 58 static boolean_t unhash_clnt(dsvc_clnt_t *, boolean_t); 59 static boolean_t unhash_offer(dsvc_clnt_t *, boolean_t); 60 61 static hash_tbl *ntable; /* global per net datastore table */ 62 63 /* 64 * Initialize global per network hash table. 65 * 66 * Per-bucket rwlocks reduce lock contention between interface and 67 * client threads. 68 * 69 * Performance: dynamically calculate hash table size. 70 */ 71 int 72 initntab(void) 73 { 74 char **listppp; 75 uint_t cnt = 0; 76 uint_t ind; 77 78 if (list_dd(&datastore, DSVC_DHCPNETWORK, &listppp, &cnt) == 79 DSVC_SUCCESS) { 80 if (listppp) { 81 for (ind = 0; listppp[ind] != NULL && ind < cnt; 82 ind++) 83 free(listppp[ind]); 84 free(listppp); 85 } 86 } 87 ntable = hash_Init(cnt, unhash_dnet, MAX(net_thresh, clnt_thresh), 88 B_TRUE); 89 return (ntable == NULL ? -1 : 0); 90 } 91 92 /* 93 * open_dnet: Open the appropriate dhcp database given a network address and 94 * a subnet mask. These in_addr's are expected in network order. 95 * 96 * Returns: DSVC_SUCCESS for success or dsvc error. 97 */ 98 int 99 open_dnet(dsvc_dnet_t **pndp, struct in_addr *net, struct in_addr *mask) 100 { 101 int err; 102 dsvc_dnet_t *pnd; 103 struct in_addr datum; 104 int hsize = 0; 105 uint32_t query; 106 dn_rec_t dn; 107 uint_t count; 108 struct in_addr *oip; 109 110 datum.s_addr = net->s_addr; 111 datum.s_addr &= mask->s_addr; 112 113 *pndp = NULL; 114 /* Locate existing dnet. */ 115 if ((pnd = get_dnet(&datum)) != NULL) { 116 (void) mutex_lock(&pnd->pnd_mtx); 117 if ((pnd->flags & DHCP_PND_ERROR) != 0) { 118 (void) mutex_unlock(&pnd->pnd_mtx); 119 close_dnet(pnd, B_TRUE); 120 return (DSVC_INTERNAL); 121 } else if ((pnd->flags & DHCP_PND_CLOSING) != 0) { 122 (void) mutex_unlock(&pnd->pnd_mtx); 123 close_dnet(pnd, B_FALSE); 124 return (DSVC_BUSY); 125 } else { 126 (void) mutex_unlock(&pnd->pnd_mtx); 127 *pndp = pnd; 128 return (DSVC_SUCCESS); 129 } 130 } 131 132 /* Allocate new dnet. */ 133 134 pnd = (dsvc_dnet_t *)smalloc(sizeof (dsvc_dnet_t)); 135 pnd->net.s_addr = datum.s_addr; 136 pnd->subnet.s_addr = mask != 0 ? mask->s_addr : htonl(INADDR_ANY); 137 (void) inet_ntop(AF_INET, &datum, pnd->network, sizeof (pnd->network)); 138 139 /* Allocate hash tables. */ 140 if (max_clients != -1) 141 hsize = max_clients; 142 if ((pnd->ctable = 143 hash_Init(hsize, unhash_clnt, MAX(off_secs, clnt_thresh), 144 B_TRUE)) == NULL) { 145 free(pnd); 146 return (DSVC_INTERNAL); 147 } 148 if ((pnd->itable = 149 hash_Init(hsize, unhash_offer, off_secs, B_TRUE)) == NULL) { 150 free(pnd->ctable); 151 free(pnd); 152 return (DSVC_INTERNAL); 153 } 154 155 err = dhcp_open_dd(&pnd->dh, &datastore, DSVC_DHCPNETWORK, pnd->network, 156 DSVC_READ|DSVC_WRITE); 157 158 if (err != DSVC_SUCCESS) { 159 free(pnd->ctable); 160 free(pnd->itable); 161 free(pnd); 162 return (err); 163 } 164 165 /* Find out how many addresses the server owns in this datastore */ 166 167 pnd->naddrs = 0; 168 for (oip = owner_ip; oip->s_addr != INADDR_ANY; oip++) { 169 170 DSVC_QINIT(query); 171 DSVC_QEQ(query, DN_QSIP); 172 dn.dn_sip.s_addr = ntohl(oip->s_addr); 173 174 err = lookup_dd(pnd->dh, B_FALSE, query, -1, &dn, NULL, &count); 175 176 if (err != DSVC_SUCCESS) { 177 free(pnd->ctable); 178 free(pnd->itable); 179 free(pnd); 180 return (err); 181 } 182 183 pnd->naddrs += count; 184 } 185 186 if ((pnd->hand = hash_Insert(ntable, &pnd->net, sizeof (struct in_addr), 187 dnet_cmp, pnd, pnd)) == NULL) { 188 /* Another thread has begun work on this net. */ 189 #ifdef DEBUG 190 dhcpmsg(LOG_DEBUG, "Duplicate network: %s\n", pnd->network); 191 #endif /* DEBUG */ 192 free(pnd->ctable); 193 free(pnd->itable); 194 free(pnd); 195 return (DSVC_BUSY); 196 } 197 198 (void) mutex_init(&pnd->pnd_mtx, USYNC_THREAD, NULL); 199 (void) mutex_init(&pnd->thr_mtx, USYNC_THREAD, NULL); 200 (void) mutex_init(&pnd->free_mtx, USYNC_THREAD, NULL); 201 (void) mutex_init(&pnd->lru_mtx, USYNC_THREAD, NULL); 202 (void) mutex_init(&pnd->lrupage_mtx, USYNC_THREAD, NULL); 203 204 *pndp = pnd; 205 return (DSVC_SUCCESS); 206 } 207 208 /* 209 * close_dnet: Closes specified dhcp-network database. 210 * 211 * delete - immediately delete. 212 */ 213 void 214 close_dnet(dsvc_dnet_t *pnd, boolean_t delete) 215 { 216 hash_Rele(pnd->hand, delete); 217 } 218 219 /* 220 * get_dnet: Given a network name, look it up in the hash table. 221 * Returns ptr to dsvc_dnet_t structure, NULL if error occurs. 222 */ 223 static dsvc_dnet_t * 224 get_dnet(struct in_addr *netp) 225 { 226 dsvc_dnet_t tpnd; 227 dsvc_dnet_t *pnd; 228 229 tpnd.net.s_addr = netp->s_addr; 230 pnd = (dsvc_dnet_t *)hash_Lookup(ntable, netp, 231 sizeof (struct in_addr), dnet_cmp, &tpnd, B_TRUE); 232 233 /* refresh pnd hash entry timer */ 234 if (pnd != NULL) 235 hash_Dtime(pnd->hand, time(NULL) + ntable->dfree_time); 236 return (pnd); 237 } 238 239 /* 240 * unhash_dnet: Free a datastore reference. 241 * 242 * Aging in hash routines will trigger freeing of unused references. 243 */ 244 /*ARGSUSED*/ 245 static boolean_t 246 unhash_dnet(dsvc_dnet_t *pnd, boolean_t force) 247 { 248 int err = 0; 249 dsvc_pendclnt_t *workp; 250 dsvc_thr_t *thrp; 251 timestruc_t tm; 252 int nthreads; 253 int refcnt; 254 255 if (pnd == NULL) 256 return (B_FALSE); 257 258 /* Mark as closing. */ 259 (void) mutex_lock(&pnd->pnd_mtx); 260 pnd->flags |= DHCP_PND_CLOSING; 261 (void) mutex_unlock(&pnd->pnd_mtx); 262 263 /* 264 * Wait for any remaining thread(s) to exit. 265 */ 266 refcnt = hash_Refcount(pnd->hand); 267 268 (void) mutex_lock(&pnd->thr_mtx); 269 nthreads = pnd->nthreads; 270 while (nthreads > 0 || refcnt > 0) { 271 /* 272 * Wait for 1ms to avoid stalling monitor threads. 273 * cond_wait() not used to avoid thread synchronization 274 * overhead. 275 */ 276 tm.tv_sec = 0; 277 tm.tv_nsec = 1000 * 10; 278 (void) cond_reltimedwait(&pnd->thr_cv, &pnd->thr_mtx, &tm); 279 nthreads = pnd->nthreads; 280 (void) mutex_unlock(&pnd->thr_mtx); 281 /* Threads will exit. */ 282 for (thrp = pnd->thrhead; thrp; thrp = thrp->thr_next) { 283 (void) mutex_lock(&thrp->thr_mtx); 284 thrp->thr_flags |= DHCP_THR_EXITING; 285 (void) mutex_unlock(&thrp->thr_mtx); 286 (void) cond_signal(&thrp->thr_cv); 287 } 288 refcnt = hash_Refcount(pnd->hand); 289 (void) mutex_lock(&pnd->thr_mtx); 290 } 291 292 /* Free threads. */ 293 while ((thrp = pnd->thrhead) != NULL) { 294 pnd->thrhead = pnd->thrhead->thr_next; 295 (void) mutex_destroy(&thrp->thr_mtx); 296 free(thrp); 297 } 298 pnd->thrtail = NULL; 299 300 /* Free deferred thread work. */ 301 while ((workp = pnd->workhead) != NULL) { 302 pnd->workhead = pnd->workhead->pnd_next; 303 free(workp); 304 } 305 pnd->worktail = NULL; 306 (void) mutex_unlock(&pnd->thr_mtx); 307 308 /* Free clients. */ 309 if (pnd->ctable) { 310 hash_Reset(pnd->ctable, unhash_clnt); 311 free(pnd->ctable); 312 } 313 314 /* Free cached datastore records. */ 315 (void) mutex_lock(&pnd->free_mtx); 316 if (pnd->freerec != NULL) 317 dhcp_free_dd_list(pnd->dh, pnd->freerec); 318 (void) mutex_unlock(&pnd->free_mtx); 319 320 (void) mutex_lock(&pnd->lru_mtx); 321 if (pnd->lrurec != NULL) 322 dhcp_free_dd_list(pnd->dh, pnd->lrurec); 323 (void) mutex_unlock(&pnd->lru_mtx); 324 325 if (pnd->itable) { 326 hash_Reset(pnd->itable, unhash_offer); 327 free(pnd->itable); 328 } 329 330 (void) mutex_lock(&pnd->lrupage_mtx); 331 if (pnd->lrupage) { 332 free(pnd->lrupage); 333 pnd->lrupage = NULL; 334 } 335 (void) mutex_unlock(&pnd->lrupage_mtx); 336 337 if (pnd->dh != NULL) { 338 if (dhcp_close_dd(&pnd->dh) != DSVC_SUCCESS) { 339 dhcpmsg(LOG_ERR, 340 "Error %d while closing for network %s\n", 341 err, pnd->network); 342 } 343 pnd->dh = NULL; 344 } 345 346 (void) mutex_destroy(&pnd->pnd_mtx); 347 (void) mutex_destroy(&pnd->thr_mtx); 348 (void) mutex_destroy(&pnd->free_mtx); 349 (void) mutex_destroy(&pnd->lru_mtx); 350 (void) mutex_destroy(&pnd->lrupage_mtx); 351 free(pnd); 352 353 return (B_TRUE); 354 } 355 356 /* 357 * dnet_cmp: Compare datastore references by network address. 358 */ 359 static int 360 dnet_cmp(dsvc_dnet_t *m1, dsvc_dnet_t *m2) 361 { 362 return (m1->net.s_addr == m2->net.s_addr); 363 } 364 365 /* 366 * open_clnt: Open the appropriate dhcp client given a network 367 * database and client id. 368 * 369 * Returns: DSVC_SUCCESS for success or errno if an error occurs. 370 * 371 * pnd - per net struct 372 * pcdp - client struct returned here 373 * cid - clientid 374 * cid_len - cid length 375 * nocreate - if set, client struct must previously exist 376 */ 377 int 378 open_clnt(dsvc_dnet_t *pnd, dsvc_clnt_t **pcdp, uchar_t *cid, 379 uchar_t cid_len, boolean_t nocreate) 380 { 381 dsvc_clnt_t *pcd; 382 time_t now; 383 uint_t blen; 384 385 *pcdp = NULL; 386 387 /* Network is closing. */ 388 if ((pnd->flags & DHCP_PND_CLOSING) != 0) 389 return (DSVC_BUSY); 390 391 /* Locate existing client. */ 392 if ((pcd = get_client(pnd->ctable, cid, cid_len)) != NULL) { 393 (void) mutex_lock(&pcd->pcd_mtx); 394 /* Client is closing - temporarily busy. */ 395 if ((pcd->flags & DHCP_PCD_CLOSING) != 0) { 396 (void) mutex_unlock(&pcd->pcd_mtx); 397 close_clnt(pcd, B_FALSE); 398 return (DSVC_BUSY); 399 } 400 (void) mutex_unlock(&pcd->pcd_mtx); 401 *pcdp = pcd; 402 return (DSVC_SUCCESS); 403 } 404 if (nocreate == B_TRUE) 405 return (DSVC_NOENT); 406 407 /* Allocate new client. */ 408 (void) mutex_lock(&pnd->thr_mtx); 409 if (max_clients != -1) { 410 now = time(NULL); 411 /* 412 * Performance/DOS: dsvc_clnt_t structs are normally 413 * freed when the protocol conversation completes, 414 * or when garbage collected (see hash.c). In 415 * certain error scenarios (e.g. DOS attacks, or 416 * network failures where large numbers of clients 417 * begin protocol conversations that never complete) 418 * the server will become unresponsive. To detect 419 * these scenarios, free slot time is observed, and 420 * after a grace period (2 * the offer time the currently 421 * allocated clients are allowed), clients are randomly 422 * deleted. 423 */ 424 if (pnd->nclients < max_clients) { 425 /* Keep track of last time there were free slots. */ 426 pnd->clnt_stamp = now; 427 (void) mutex_unlock(&pnd->thr_mtx); 428 } else if (pnd->clnt_stamp + off_secs > now) { 429 /* Wait for other clients to complete. */ 430 (void) mutex_unlock(&pnd->thr_mtx); 431 return (DSVC_INTERNAL); 432 } else { 433 /* Forcibly delete a client to free a slot. */ 434 pnd->clnt_stamp = now; 435 (void) mutex_unlock(&pnd->thr_mtx); 436 hash_Reap(pnd->ctable, unhash_clnt); 437 } 438 } else 439 (void) mutex_unlock(&pnd->thr_mtx); 440 441 pcd = (dsvc_clnt_t *)smalloc(sizeof (dsvc_clnt_t)); 442 (void) mutex_init(&pcd->pcd_mtx, USYNC_THREAD, NULL); 443 (void) mutex_init(&pcd->pkt_mtx, USYNC_THREAD, NULL); 444 (void) mutex_lock(&pcd->pcd_mtx); 445 pcd->pkthead = pcd->pkttail = NULL; 446 pcd->pnd = pnd; 447 (void) memcpy(pcd->cid, cid, cid_len); 448 pcd->cid_len = cid_len; 449 blen = sizeof (pcd->cidbuf); 450 (void) octet_to_hexascii(cid, cid_len, pcd->cidbuf, &blen); 451 452 if ((pcd->chand = hash_Insert(pnd->ctable, cid, cid_len, clnt_cmp, 453 pcd, pcd)) == NULL) { 454 /* Another thread has begun work on this client */ 455 #ifdef DEBUG 456 dhcpmsg(LOG_DEBUG, "Duplicate client\n"); 457 #endif /* DEBUG */ 458 (void) mutex_unlock(&pcd->pcd_mtx); 459 (void) mutex_destroy(&pcd->pcd_mtx); 460 (void) mutex_destroy(&pcd->pkt_mtx); 461 free(pcd); 462 return (DSVC_BUSY); 463 } 464 (void) mutex_unlock(&pcd->pcd_mtx); 465 (void) mutex_lock(&pnd->thr_mtx); 466 pnd->nclients++; 467 (void) mutex_unlock(&pnd->thr_mtx); 468 *pcdp = pcd; 469 return (DSVC_SUCCESS); 470 } 471 472 /* 473 * close_clnt: Closes specified client. 474 * 475 * delete - immediately delete. 476 */ 477 void 478 close_clnt(dsvc_clnt_t *pcd, boolean_t delete) 479 { 480 hash_Rele(pcd->chand, delete); 481 } 482 483 /* 484 * get_client: Given a client name, look it up in the per client hash table. 485 * Returns ptr to dsvc_clnt_t structure, NULL if error occurs. 486 */ 487 static dsvc_clnt_t * 488 get_client(hash_tbl *table, uchar_t *cid, uchar_t cid_len) 489 { 490 dsvc_clnt_t tpcd; 491 dsvc_clnt_t *pcd; 492 493 (void) memcpy(tpcd.cid, cid, cid_len); 494 tpcd.cid_len = cid_len; 495 496 pcd = (dsvc_clnt_t *)hash_Lookup(table, cid, cid_len, clnt_cmp, 497 &tpcd, B_TRUE); 498 499 /* refresh client hash entry's timer */ 500 if (pcd != NULL) { 501 (void) mutex_lock(&pcd->pcd_mtx); 502 hash_Dtime(pcd->chand, time(NULL) + table->dfree_time); 503 (void) mutex_unlock(&pcd->pcd_mtx); 504 } 505 return (pcd); 506 } 507 508 /* 509 * unhash_clnt: Free a client structure. 510 * 511 * Aging in hash routines will trigger freeing of unused references. 512 */ 513 /*ARGSUSED*/ 514 static boolean_t 515 unhash_clnt(dsvc_clnt_t *pcd, boolean_t force) 516 { 517 dsvc_dnet_t *pnd = pcd->pnd; 518 timestruc_t tm; 519 int refcnt; 520 struct in_addr off_ip; 521 522 523 refcnt = hash_Refcount(pcd->chand); 524 525 /* 526 * Wait for thread(s) accessing pcd to drop references. 527 */ 528 (void) mutex_lock(&pcd->pcd_mtx); 529 pcd->flags |= DHCP_PCD_CLOSING; /* client no longer usable... */ 530 while (pcd->clnt_thread != NULL || refcnt > 0) { 531 /* 532 * Wait for 1ms to avoid stalling monitor threads. 533 * cond_wait() not used to avoid thread synchronization 534 * overhead. 535 */ 536 tm.tv_sec = 0; 537 tm.tv_nsec = 1000 * 10; 538 (void) cond_reltimedwait(&pcd->pcd_cv, &pcd->pcd_mtx, &tm); 539 (void) mutex_unlock(&pcd->pcd_mtx); 540 refcnt = hash_Refcount(pcd->chand); 541 (void) mutex_lock(&pcd->pcd_mtx); 542 } 543 544 if (pcd->pkthead != NULL) 545 free_pktlist(pcd); 546 547 off_ip.s_addr = pcd->off_ip.s_addr; 548 (void) mutex_unlock(&pcd->pcd_mtx); 549 550 if (off_ip.s_addr != htonl(INADDR_ANY)) 551 purge_offer(pcd, B_TRUE, B_FALSE); 552 553 if (pcd->dnlp != NULL) 554 dhcp_free_dd_list(pnd->dh, pcd->dnlp); 555 556 (void) mutex_destroy(&pcd->pcd_mtx); 557 (void) mutex_destroy(&pcd->pkt_mtx); 558 free(pcd); 559 560 (void) mutex_lock(&pnd->thr_mtx); 561 pnd->nclients--; 562 (void) mutex_unlock(&pnd->thr_mtx); 563 564 return (B_TRUE); 565 } 566 567 /* 568 * unhash_offer: Free offer associated with a client structure. 569 * 570 * Aging in hash routines will trigger freeing of expired offers. 571 */ 572 static boolean_t 573 unhash_offer(dsvc_clnt_t *pcd, boolean_t force) 574 { 575 IF *ifp = pcd->ifp; 576 boolean_t ret = B_TRUE; 577 char ntoab[INET_ADDRSTRLEN]; 578 579 (void) mutex_lock(&pcd->pcd_mtx); 580 if (pcd->off_ip.s_addr != htonl(INADDR_ANY) && 581 PCD_OFFER_TIMEOUT(pcd, time(NULL))) { 582 if (pcd->clnt_thread == NULL) { 583 if (debug) 584 dhcpmsg(LOG_INFO, "Freeing offer: %s\n", 585 inet_ntop(AF_INET, &pcd->off_ip, 586 ntoab, sizeof (ntoab))); 587 pcd->off_ip.s_addr = htonl(INADDR_ANY); 588 (void) mutex_unlock(&pcd->pcd_mtx); 589 (void) mutex_lock(&ifp->ifp_mtx); 590 if (ifp->offers > 0) 591 ifp->offers--; 592 ifp->expired++; 593 (void) mutex_unlock(&ifp->ifp_mtx); 594 } else if (force == B_FALSE) { 595 /* 596 * Worker thread is currently active. To avoid 597 * unnecessary thread synchronization, defer 598 * freeing the offer until the worker thread has 599 * completed. 600 */ 601 (void) mutex_unlock(&pcd->pcd_mtx); 602 hash_Age(pcd->ihand); 603 ret = B_FALSE; 604 } else 605 (void) mutex_unlock(&pcd->pcd_mtx); 606 } else 607 (void) mutex_unlock(&pcd->pcd_mtx); 608 return (ret); 609 } 610 611 /* 612 * clnt_cmp: Compare client structures by cid. 613 */ 614 static int 615 clnt_cmp(dsvc_clnt_t *m1, dsvc_clnt_t *m2) 616 { 617 return (m1->cid_len == m2->cid_len && 618 memcmp((char *)m1->cid, (char *)m2->cid, m1->cid_len) == 0); 619 } 620 621 /* 622 * clnt_netcmp Compare clients by network address. This is used to maintain 623 * the itable hash table of client addresses. 624 */ 625 int 626 clnt_netcmp(dsvc_clnt_t *d1, dsvc_clnt_t *d2) 627 { 628 return (d1->off_ip.s_addr == d2->off_ip.s_addr); 629 } 630 631 /* 632 * close_clnts: Free the ntable hash table and associated client structs. 633 * Table walk frees each per network and client struct. 634 */ 635 void 636 close_clnts(void) 637 { 638 hash_Reset(ntable, unhash_dnet); 639 }