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 }