per_dnet.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <assert.h>
#include <errno.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/dhcp.h>
#include "dhcpd.h"
#include "interfaces.h"
#include <locale.h>
/*
* This file contains the access routines for the dhcp databases.
*/
static dsvc_dnet_t *get_dnet(struct in_addr *);
static boolean_t unhash_dnet(dsvc_dnet_t *, boolean_t);
static int dnet_cmp(dsvc_dnet_t *, dsvc_dnet_t *);
static dsvc_clnt_t *get_client(hash_tbl *, uchar_t *, uchar_t);
static int clnt_cmp(dsvc_clnt_t *, dsvc_clnt_t *);
static boolean_t unhash_clnt(dsvc_clnt_t *, boolean_t);
static boolean_t unhash_offer(dsvc_clnt_t *, boolean_t);
static hash_tbl *ntable; /* global per net datastore table */
/*
* Initialize global per network hash table.
*
* Per-bucket rwlocks reduce lock contention between interface and
* client threads.
*
* Performance: dynamically calculate hash table size.
*/
int
initntab(void)
{
char **listppp;
uint_t cnt = 0;
uint_t ind;
if (list_dd(&datastore, DSVC_DHCPNETWORK, &listppp, &cnt) ==
DSVC_SUCCESS) {
if (listppp) {
for (ind = 0; listppp[ind] != NULL && ind < cnt;
ind++)
free(listppp[ind]);
free(listppp);
}
}
ntable = hash_Init(cnt, unhash_dnet, MAX(net_thresh, clnt_thresh),
B_TRUE);
return (ntable == NULL ? -1 : 0);
}
/*
* open_dnet: Open the appropriate dhcp database given a network address and
* a subnet mask. These in_addr's are expected in network order.
*
* Returns: DSVC_SUCCESS for success or dsvc error.
*/
int
open_dnet(dsvc_dnet_t **pndp, struct in_addr *net, struct in_addr *mask)
{
int err;
dsvc_dnet_t *pnd;
struct in_addr datum;
int hsize = 0;
uint32_t query;
dn_rec_t dn;
uint_t count;
struct in_addr *oip;
datum.s_addr = net->s_addr;
datum.s_addr &= mask->s_addr;
*pndp = NULL;
/* Locate existing dnet. */
if ((pnd = get_dnet(&datum)) != NULL) {
(void) mutex_lock(&pnd->pnd_mtx);
if ((pnd->flags & DHCP_PND_ERROR) != 0) {
(void) mutex_unlock(&pnd->pnd_mtx);
close_dnet(pnd, B_TRUE);
return (DSVC_INTERNAL);
} else if ((pnd->flags & DHCP_PND_CLOSING) != 0) {
(void) mutex_unlock(&pnd->pnd_mtx);
close_dnet(pnd, B_FALSE);
return (DSVC_BUSY);
} else {
(void) mutex_unlock(&pnd->pnd_mtx);
*pndp = pnd;
return (DSVC_SUCCESS);
}
}
/* Allocate new dnet. */
pnd = (dsvc_dnet_t *)smalloc(sizeof (dsvc_dnet_t));
pnd->net.s_addr = datum.s_addr;
pnd->subnet.s_addr = mask != 0 ? mask->s_addr : htonl(INADDR_ANY);
(void) inet_ntop(AF_INET, &datum, pnd->network, sizeof (pnd->network));
/* Allocate hash tables. */
if (max_clients != -1)
hsize = max_clients;
if ((pnd->ctable =
hash_Init(hsize, unhash_clnt, MAX(off_secs, clnt_thresh),
B_TRUE)) == NULL) {
free(pnd);
return (DSVC_INTERNAL);
}
if ((pnd->itable =
hash_Init(hsize, unhash_offer, off_secs, B_TRUE)) == NULL) {
free(pnd->ctable);
free(pnd);
return (DSVC_INTERNAL);
}
err = dhcp_open_dd(&pnd->dh, &datastore, DSVC_DHCPNETWORK, pnd->network,
DSVC_READ|DSVC_WRITE);
if (err != DSVC_SUCCESS) {
free(pnd->ctable);
free(pnd->itable);
free(pnd);
return (err);
}
/* Find out how many addresses the server owns in this datastore */
pnd->naddrs = 0;
for (oip = owner_ip; oip->s_addr != INADDR_ANY; oip++) {
DSVC_QINIT(query);
DSVC_QEQ(query, DN_QSIP);
dn.dn_sip.s_addr = ntohl(oip->s_addr);
err = lookup_dd(pnd->dh, B_FALSE, query, -1, &dn, NULL, &count);
if (err != DSVC_SUCCESS) {
free(pnd->ctable);
free(pnd->itable);
free(pnd);
return (err);
}
pnd->naddrs += count;
}
if ((pnd->hand = hash_Insert(ntable, &pnd->net, sizeof (struct in_addr),
dnet_cmp, pnd, pnd)) == NULL) {
/* Another thread has begun work on this net. */
#ifdef DEBUG
dhcpmsg(LOG_DEBUG, "Duplicate network: %s\n", pnd->network);
#endif /* DEBUG */
free(pnd->ctable);
free(pnd->itable);
free(pnd);
return (DSVC_BUSY);
}
(void) mutex_init(&pnd->pnd_mtx, USYNC_THREAD, NULL);
(void) mutex_init(&pnd->thr_mtx, USYNC_THREAD, NULL);
(void) mutex_init(&pnd->free_mtx, USYNC_THREAD, NULL);
(void) mutex_init(&pnd->lru_mtx, USYNC_THREAD, NULL);
(void) mutex_init(&pnd->lrupage_mtx, USYNC_THREAD, NULL);
*pndp = pnd;
return (DSVC_SUCCESS);
}
/*
* close_dnet: Closes specified dhcp-network database.
*
* delete - immediately delete.
*/
void
close_dnet(dsvc_dnet_t *pnd, boolean_t delete)
{
hash_Rele(pnd->hand, delete);
}
/*
* get_dnet: Given a network name, look it up in the hash table.
* Returns ptr to dsvc_dnet_t structure, NULL if error occurs.
*/
static dsvc_dnet_t *
get_dnet(struct in_addr *netp)
{
dsvc_dnet_t tpnd;
dsvc_dnet_t *pnd;
tpnd.net.s_addr = netp->s_addr;
pnd = (dsvc_dnet_t *)hash_Lookup(ntable, netp,
sizeof (struct in_addr), dnet_cmp, &tpnd, B_TRUE);
/* refresh pnd hash entry timer */
if (pnd != NULL)
hash_Dtime(pnd->hand, time(NULL) + ntable->dfree_time);
return (pnd);
}
/*
* unhash_dnet: Free a datastore reference.
*
* Aging in hash routines will trigger freeing of unused references.
*/
/*ARGSUSED*/
static boolean_t
unhash_dnet(dsvc_dnet_t *pnd, boolean_t force)
{
int err = 0;
dsvc_pendclnt_t *workp;
dsvc_thr_t *thrp;
timestruc_t tm;
int nthreads;
int refcnt;
if (pnd == NULL)
return (B_FALSE);
/* Mark as closing. */
(void) mutex_lock(&pnd->pnd_mtx);
pnd->flags |= DHCP_PND_CLOSING;
(void) mutex_unlock(&pnd->pnd_mtx);
/*
* Wait for any remaining thread(s) to exit.
*/
refcnt = hash_Refcount(pnd->hand);
(void) mutex_lock(&pnd->thr_mtx);
nthreads = pnd->nthreads;
while (nthreads > 0 || refcnt > 0) {
/*
* Wait for 1ms to avoid stalling monitor threads.
* cond_wait() not used to avoid thread synchronization
* overhead.
*/
tm.tv_sec = 0;
tm.tv_nsec = 1000 * 10;
(void) cond_reltimedwait(&pnd->thr_cv, &pnd->thr_mtx, &tm);
nthreads = pnd->nthreads;
(void) mutex_unlock(&pnd->thr_mtx);
/* Threads will exit. */
for (thrp = pnd->thrhead; thrp; thrp = thrp->thr_next) {
(void) mutex_lock(&thrp->thr_mtx);
thrp->thr_flags |= DHCP_THR_EXITING;
(void) mutex_unlock(&thrp->thr_mtx);
(void) cond_signal(&thrp->thr_cv);
}
refcnt = hash_Refcount(pnd->hand);
(void) mutex_lock(&pnd->thr_mtx);
}
/* Free threads. */
while ((thrp = pnd->thrhead) != NULL) {
pnd->thrhead = pnd->thrhead->thr_next;
(void) mutex_destroy(&thrp->thr_mtx);
free(thrp);
}
pnd->thrtail = NULL;
/* Free deferred thread work. */
while ((workp = pnd->workhead) != NULL) {
pnd->workhead = pnd->workhead->pnd_next;
free(workp);
}
pnd->worktail = NULL;
(void) mutex_unlock(&pnd->thr_mtx);
/* Free clients. */
if (pnd->ctable) {
hash_Reset(pnd->ctable, unhash_clnt);
free(pnd->ctable);
}
/* Free cached datastore records. */
(void) mutex_lock(&pnd->free_mtx);
if (pnd->freerec != NULL)
dhcp_free_dd_list(pnd->dh, pnd->freerec);
(void) mutex_unlock(&pnd->free_mtx);
(void) mutex_lock(&pnd->lru_mtx);
if (pnd->lrurec != NULL)
dhcp_free_dd_list(pnd->dh, pnd->lrurec);
(void) mutex_unlock(&pnd->lru_mtx);
if (pnd->itable) {
hash_Reset(pnd->itable, unhash_offer);
free(pnd->itable);
}
(void) mutex_lock(&pnd->lrupage_mtx);
if (pnd->lrupage) {
free(pnd->lrupage);
pnd->lrupage = NULL;
}
(void) mutex_unlock(&pnd->lrupage_mtx);
if (pnd->dh != NULL) {
if (dhcp_close_dd(&pnd->dh) != DSVC_SUCCESS) {
dhcpmsg(LOG_ERR,
"Error %d while closing for network %s\n",
err, pnd->network);
}
pnd->dh = NULL;
}
(void) mutex_destroy(&pnd->pnd_mtx);
(void) mutex_destroy(&pnd->thr_mtx);
(void) mutex_destroy(&pnd->free_mtx);
(void) mutex_destroy(&pnd->lru_mtx);
(void) mutex_destroy(&pnd->lrupage_mtx);
free(pnd);
return (B_TRUE);
}
/*
* dnet_cmp: Compare datastore references by network address.
*/
static int
dnet_cmp(dsvc_dnet_t *m1, dsvc_dnet_t *m2)
{
return (m1->net.s_addr == m2->net.s_addr);
}
/*
* open_clnt: Open the appropriate dhcp client given a network
* database and client id.
*
* Returns: DSVC_SUCCESS for success or errno if an error occurs.
*
* pnd - per net struct
* pcdp - client struct returned here
* cid - clientid
* cid_len - cid length
* nocreate - if set, client struct must previously exist
*/
int
open_clnt(dsvc_dnet_t *pnd, dsvc_clnt_t **pcdp, uchar_t *cid,
uchar_t cid_len, boolean_t nocreate)
{
dsvc_clnt_t *pcd;
time_t now;
uint_t blen;
*pcdp = NULL;
/* Network is closing. */
if ((pnd->flags & DHCP_PND_CLOSING) != 0)
return (DSVC_BUSY);
/* Locate existing client. */
if ((pcd = get_client(pnd->ctable, cid, cid_len)) != NULL) {
(void) mutex_lock(&pcd->pcd_mtx);
/* Client is closing - temporarily busy. */
if ((pcd->flags & DHCP_PCD_CLOSING) != 0) {
(void) mutex_unlock(&pcd->pcd_mtx);
close_clnt(pcd, B_FALSE);
return (DSVC_BUSY);
}
(void) mutex_unlock(&pcd->pcd_mtx);
*pcdp = pcd;
return (DSVC_SUCCESS);
}
if (nocreate == B_TRUE)
return (DSVC_NOENT);
/* Allocate new client. */
(void) mutex_lock(&pnd->thr_mtx);
if (max_clients != -1) {
now = time(NULL);
/*
* Performance/DOS: dsvc_clnt_t structs are normally
* freed when the protocol conversation completes,
* or when garbage collected (see hash.c). In
* certain error scenarios (e.g. DOS attacks, or
* network failures where large numbers of clients
* begin protocol conversations that never complete)
* the server will become unresponsive. To detect
* these scenarios, free slot time is observed, and
* after a grace period (2 * the offer time the currently
* allocated clients are allowed), clients are randomly
* deleted.
*/
if (pnd->nclients < max_clients) {
/* Keep track of last time there were free slots. */
pnd->clnt_stamp = now;
(void) mutex_unlock(&pnd->thr_mtx);
} else if (pnd->clnt_stamp + off_secs > now) {
/* Wait for other clients to complete. */
(void) mutex_unlock(&pnd->thr_mtx);
return (DSVC_INTERNAL);
} else {
/* Forcibly delete a client to free a slot. */
pnd->clnt_stamp = now;
(void) mutex_unlock(&pnd->thr_mtx);
hash_Reap(pnd->ctable, unhash_clnt);
}
} else
(void) mutex_unlock(&pnd->thr_mtx);
pcd = (dsvc_clnt_t *)smalloc(sizeof (dsvc_clnt_t));
(void) mutex_init(&pcd->pcd_mtx, USYNC_THREAD, NULL);
(void) mutex_init(&pcd->pkt_mtx, USYNC_THREAD, NULL);
pcd->pkthead = pcd->pkttail = NULL;
pcd->pnd = pnd;
(void) memcpy(pcd->cid, cid, cid_len);
pcd->cid_len = cid_len;
blen = sizeof (pcd->cidbuf);
(void) octet_to_hexascii(cid, cid_len, pcd->cidbuf, &blen);
if ((pcd->chand = hash_Insert(pnd->ctable, cid, cid_len, clnt_cmp,
pcd, pcd)) == NULL) {
/* Another thread has begun work on this client */
#ifdef DEBUG
dhcpmsg(LOG_DEBUG, "Duplicate client\n");
#endif /* DEBUG */
(void) mutex_destroy(&pcd->pcd_mtx);
(void) mutex_destroy(&pcd->pkt_mtx);
free(pcd);
return (DSVC_BUSY);
}
(void) mutex_lock(&pnd->thr_mtx);
pnd->nclients++;
(void) mutex_unlock(&pnd->thr_mtx);
*pcdp = pcd;
return (DSVC_SUCCESS);
}
/*
* close_clnt: Closes specified client.
*
* delete - immediately delete.
*/
void
close_clnt(dsvc_clnt_t *pcd, boolean_t delete)
{
hash_Rele(pcd->chand, delete);
}
/*
* get_client: Given a client name, look it up in the per client hash table.
* Returns ptr to dsvc_clnt_t structure, NULL if error occurs.
*/
static dsvc_clnt_t *
get_client(hash_tbl *table, uchar_t *cid, uchar_t cid_len)
{
dsvc_clnt_t tpcd;
dsvc_clnt_t *pcd;
(void) memcpy(tpcd.cid, cid, cid_len);
tpcd.cid_len = cid_len;
pcd = (dsvc_clnt_t *)hash_Lookup(table, cid, cid_len, clnt_cmp,
&tpcd, B_TRUE);
/* refresh client hash entry's timer */
if (pcd != NULL)
hash_Dtime(pcd->chand, time(NULL) + table->dfree_time);
return (pcd);
}
/*
* unhash_clnt: Free a client structure.
*
* Aging in hash routines will trigger freeing of unused references.
*/
/*ARGSUSED*/
static boolean_t
unhash_clnt(dsvc_clnt_t *pcd, boolean_t force)
{
dsvc_dnet_t *pnd = pcd->pnd;
timestruc_t tm;
int refcnt;
struct in_addr off_ip;
refcnt = hash_Refcount(pcd->chand);
/*
* Wait for thread(s) accessing pcd to drop references.
*/
(void) mutex_lock(&pcd->pcd_mtx);
pcd->flags |= DHCP_PCD_CLOSING; /* client no longer usable... */
while (pcd->clnt_thread != NULL || refcnt > 0) {
/*
* Wait for 1ms to avoid stalling monitor threads.
* cond_wait() not used to avoid thread synchronization
* overhead.
*/
tm.tv_sec = 0;
tm.tv_nsec = 1000 * 10;
(void) cond_reltimedwait(&pcd->pcd_cv, &pcd->pcd_mtx, &tm);
(void) mutex_unlock(&pcd->pcd_mtx);
refcnt = hash_Refcount(pcd->chand);
(void) mutex_lock(&pcd->pcd_mtx);
}
if (pcd->pkthead != NULL)
free_pktlist(pcd);
off_ip.s_addr = pcd->off_ip.s_addr;
(void) mutex_unlock(&pcd->pcd_mtx);
if (off_ip.s_addr != htonl(INADDR_ANY))
purge_offer(pcd, B_TRUE, B_FALSE);
if (pcd->dnlp != NULL)
dhcp_free_dd_list(pnd->dh, pcd->dnlp);
(void) mutex_destroy(&pcd->pcd_mtx);
(void) mutex_destroy(&pcd->pkt_mtx);
free(pcd);
(void) mutex_lock(&pnd->thr_mtx);
pnd->nclients--;
(void) mutex_unlock(&pnd->thr_mtx);
return (B_TRUE);
}
/*
* unhash_offer: Free offer associated with a client structure.
*
* Aging in hash routines will trigger freeing of expired offers.
*/
static boolean_t
unhash_offer(dsvc_clnt_t *pcd, boolean_t force)
{
IF *ifp = pcd->ifp;
boolean_t ret = B_TRUE;
char ntoab[INET_ADDRSTRLEN];
(void) mutex_lock(&pcd->pcd_mtx);
if (pcd->off_ip.s_addr != htonl(INADDR_ANY) &&
PCD_OFFER_TIMEOUT(pcd, time(NULL))) {
if (pcd->clnt_thread == NULL) {
if (debug)
dhcpmsg(LOG_INFO, "Freeing offer: %s\n",
inet_ntop(AF_INET, &pcd->off_ip,
ntoab, sizeof (ntoab)));
pcd->off_ip.s_addr = htonl(INADDR_ANY);
(void) mutex_unlock(&pcd->pcd_mtx);
(void) mutex_lock(&ifp->ifp_mtx);
ifp->offers--;
assert((int)ifp->offers >= 0);
ifp->expired++;
(void) mutex_unlock(&ifp->ifp_mtx);
} else if (force == B_FALSE) {
/*
* Worker thread is currently active. To avoid
* unnecessary thread synchronization, defer
* freeing the offer until the worker thread has
* completed.
*/
(void) mutex_unlock(&pcd->pcd_mtx);
hash_Age(pcd->ihand);
ret = B_FALSE;
} else
(void) mutex_unlock(&pcd->pcd_mtx);
} else
(void) mutex_unlock(&pcd->pcd_mtx);
return (ret);
}
/*
* clnt_cmp: Compare client structures by cid.
*/
static int
clnt_cmp(dsvc_clnt_t *m1, dsvc_clnt_t *m2)
{
return (m1->cid_len == m2->cid_len &&
memcmp((char *)m1->cid, (char *)m2->cid, m1->cid_len) == 0);
}
/*
* clnt_netcmp Compare clients by network address. This is used to maintain
* the itable hash table of client addresses.
*/
int
clnt_netcmp(dsvc_clnt_t *d1, dsvc_clnt_t *d2)
{
return (d1->off_ip.s_addr == d2->off_ip.s_addr);
}
/*
* close_clnts: Free the ntable hash table and associated client structs.
* Table walk frees each per network and client struct.
*/
void
close_clnts(void)
{
hash_Reset(ntable, unhash_dnet);
}