main.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
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* This file contains the argument parsing routines of the dhcpd daemon.
* It corresponds to the START state as spec'ed.
*/
/*
* Multithreading Notes:
* =====================
*
* For Enterprise DHCP scalability, libdhcpsvc has been made reentrant,
* and the server has been organized with a worker thread per client.
*
* There is a thread per configured interface which reads requests,
* determines if they are for this server, and appends them to the
* interface's PKT list. This thread spawns worker threads as needed
* to service incoming clients.
*
* The main thread creates a thread to handle signals. All subsequent threads
* (and the main thread) mask out all signals.
*
* The signal thread will deal with the -t option. This is done by
* waiting in sigtimedwait() for the timeout period, then spawning
* a reinitialization thread.
*
* dhcp: each client worker thread moves through the multi-packet
* state machine inline, performing icmp_echo_check() as needed.
* We prevent multiple threads from registering the same address for ICMP
* validation due to multiple DISCOVERS by reserving addresses in
* select_offer() to ensure we don't offer IP addresses currently
* undergoing ICMP validation.
*
* bootp: If automatic allocation is in effect,
* bootp behaves in the same fashion as dhcp_offer.
*
* Summary:
*
* Threads:
* 1) Main thread: Handles startup and shutdown chores.
*
* 2) Signal thread: The main thread creates this thread, and
* then masks out all signals. The signal thread waits on
* sigwait(), and processes all signals. It notifies the
* main thread of EINTR or ETERM via a global variable, which
* the main thread checks upon the exit to cond_wait.
* This thread is on it's own LWP, and is DETACHED | DAEMON.
* The thread function is sig_handle().
*
* 3) Interface threads: Each interface structure has a thread
* associated with it (created in open_interfaces) which is
* responsible for polling the interface, validating bootp
* packets received, and placing them on the client's
* PKT_LIST. The thread function is monitor_interface().
* When notified by the main thread via the thr_exit flag,
* the thread prints interface statistics for the interface,
* and then exits.
*
* 4) Client threads: Created as needed when the interface
* thread processes each incoming packet. These threads are
* created DETACHED and SUSPENDED by the interface thread,
* which then places each plp structure on the client's
* PKT_LIST, then continues the thread. A client thread exits
* when it has processed all incoming packets, and no
* deferred client work is queued. See per_dnet.h for
* more information on client locks.
*
* Locks:
* 1) if_head_mtx - Locks the global interface list.
*
* 2) ifp_mtx - Locks contents of the enclosed
* interface (IF) structure, including
* such things as thr_exit flag and
* statistics counters.
*
* 3) pkt_mtx - Locks PKT_LIST head list within the
* enclosed client (dsvc_clnt_t) struct.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <signal.h>
#include <time.h>
#include <limits.h>
#include <sys/resource.h>
#include <sys/fcntl.h>
#include <stdarg.h>
#include <sys/types.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/systeminfo.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/dhcp.h>
#include <synch.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <netdb.h>
#include <dhcp_svc_confkey.h>
#include "dhcpd.h"
#include "per_dnet.h"
#include "interfaces.h"
#include <locale.h>
#include <mtmalloc.h>
#include <resolv.h>
extern int optind, opterr;
extern char *optarg;
typedef struct dhcp_cops {
char *cop_name; /* opt name */
boolean_t cop_present; /* opt present? */
boolean_t (*cop_vinit)(struct dhcp_cops *, const char *);
union {
char *ucop_str;
boolean_t ucop_bool;
int ucop_num;
} dhcp_cops_un;
#define cop_bool dhcp_cops_un.ucop_bool /* opt val: boolean_t */
#define cop_num dhcp_cops_un.ucop_num /* opt val: int */
#define cop_str dhcp_cops_un.ucop_str /* opt val: string */
} DHCP_COP;
static boolean_t bool_v(DHCP_COP *, const char *);
static boolean_t uchar_v(DHCP_COP *, const char *);
static boolean_t int_v(DHCP_COP *, const char *);
static boolean_t uint_v(DHCP_COP *, const char *);
static boolean_t str_v(DHCP_COP *, const char *);
static boolean_t bootp_v(DHCP_COP *, const char *);
static boolean_t logging_v(DHCP_COP *, const char *);
static boolean_t runmode_v(DHCP_COP *, const char *);
static int collect_options(int, char **);
static void usage(void);
static void local_closelog(void);
static void *sig_handle(void *);
#define C_RUNMODE 0
#define C_DEBUG 1
#define C_VERBOSE 2
#define C_HOPS 3
#define C_LOGGING 4
#define C_IF 5
#define C_OFFER 6
#define C_ICMP 7
#define C_RESCAN 8
#define C_BOOTP 9
#define C_CLIENT 10
#define C_THREADS 11
#define C_MINLRU 12
#define C_RELAY 13
#define C_NSUPDATE 14
#define C_CACHE 15
#define C_DBGPORT 16
#define C_RENOG 17
#define C_OWNER 18
#ifdef DEBUG
#define C_DBGNET 19
#define C_LAST C_DBGNET
#else /* DEBUG */
#define C_LAST C_OWNER
#endif /* DEBUG */
static DHCP_COP options[C_LAST + 1] = {
/* name Present? Verify func Value */
/* ==== ======== =========== ===== */
/* Run mode / BOOTP relay agent selection option */
{ DSVC_CK_RUN_MODE, B_FALSE, runmode_v, DSVC_CV_SERVER },
/* Generic daemon options */
{ "DEBUG", B_FALSE, bool_v, B_FALSE },
{ DSVC_CK_VERBOSE, B_FALSE, bool_v, B_FALSE },
{ DSVC_CK_RELAY_HOPS, B_FALSE, uchar_v, (char *)DSVC_CV_HOPS },
{ DSVC_CK_LOGGING_FACILITY, B_FALSE, logging_v, 0 },
{ DSVC_CK_INTERFACES, B_FALSE, str_v, NULL },
/* DHCP server run mode options */
{ DSVC_CK_OFFER_CACHE_TIMEOUT, B_FALSE, uint_v, (char *)DSVC_CV_OFFER_TTL },
{ DSVC_CK_ICMP_VERIFY, B_FALSE, bool_v, (char *)B_TRUE },
{ DSVC_CK_RESCAN_INTERVAL, B_FALSE, int_v, 0 },
{ DSVC_CK_BOOTP_COMPAT, B_FALSE, bootp_v, NULL },
{ DSVC_CK_MAX_CLIENTS, B_FALSE, int_v, (char *)0 },
{ DSVC_CK_MAX_THREADS, B_FALSE, int_v, (char *)0 },
{ DSVC_CK_LEASE_MIN_LRU, B_FALSE, int_v, (char *)DSVC_CV_MIN_LRU },
/* BOOTP relay agent options */
{ DSVC_CK_RELAY_DESTINATIONS, B_FALSE, str_v, NULL },
/* Name service update timeout */
{ DSVC_CK_NSU_TIMEOUT, B_FALSE, uint_v, (char *)DSVC_CV_NSU_TO },
{ DSVC_CK_CACHE_TIMEOUT, B_FALSE, int_v, (char *)DSVC_CV_CACHE_TTL },
{ DSVC_CK_DBG_PORT_OFFSET, B_FALSE, int_v, 0 },
{ DSVC_CK_RENOG_INTERVAL, B_FALSE, uint_v, (char *)DSVC_CV_RENOG_INT },
{ DSVC_CK_OWNER_IP, B_FALSE, str_v, NULL },
#ifdef DEBUG
{ DSVC_CK_DBG_MEMORY_NET, B_FALSE, str_v, NULL }
#endif /* DEBUG */
};
#define DHCPCOP_NAME(x) (options[x].cop_name)
#define DHCPCOP_PRES(x) (options[x].cop_present)
#define DHCPCOP_VINIT(x, y) (options[x].cop_vinit(&options[x], y))
#define DHCPCOP_BOOL(x) (options[x].cop_bool)
#define DHCPCOP_NUM(x) (options[x].cop_num)
#define DHCPCOP_STR(x) (options[x].cop_str)
int debug;
boolean_t verbose;
boolean_t noping; /* Always ping before offer by default */
boolean_t no_dhcptab; /* set if no dhcptab exists */
boolean_t server_mode; /* set if running in server mode */
static boolean_t bootp_compat; /* bootp compatibility */
boolean_t be_automatic; /* set if bootp server should allocate IPs */
uchar_t max_hops; /* max relay hops before discard */
int log_local; /* syslog local facility number */
int icmp_tries = DHCP_ICMP_ATTEMPTS; /* Number of attempts @ icmp_timeout */
time_t off_secs; /* def ttl of an offer */
time_t cache_secs; /* def ttl of netmask and table caches */
time_t renog_secs; /* def wait time for secondary server timeout */
time_t min_lru; /* def minimum lru of a reclaimed lease */
time_t icmp_timeout = DHCP_ICMP_TIMEOUT; /* milliseconds to wait for response */
time_t nsutimeout_secs; /* seconds to wait for a name service up date */
struct in_addr server_ip; /* IP address of server's primary interface */
struct in_addr *owner_ip; /* owner IP address list */
static dhcp_confopt_t *dsp; /* Confopt for datastore access */
dsvc_datastore_t datastore; /* Datastore for container access */
int max_threads; /* maximum number of worker threads per net */
int max_clients; /* maximum number of active clients per net */
ushort_t port_offset = 0; /* offset to port for multiple server */
int net_thresh = DHCP_NET_THRESHOLD; /* secs to keep pernet reference */
int clnt_thresh = DHCP_CLIENT_THRESHOLD; /* secs to keep client reference */
struct __res_state resolv_conf; /* DNS resolver data, includes domain-name */
static int rescan_scale = DHCP_RESCAN_SCALE; /* secs to scale */
#ifdef DEBUG
char *dbg_net; /* Simulated debug net (see misc.c) */
#endif /* DEBUG */
static time_t rescan_interval; /* dhcptab rescan interval */
/*
* This global is set by the signal handler when the main thread (and thus
* the daemon) should exit. We only use the mutex in this file, since we make
* the main thread wait on it becoming true using a condition variable.
*/
boolean_t time_to_go = B_FALSE;
static mutex_t ttg_mtx;
static cond_t ttg_cv;
/* local syslog facilities */
static int log_facilities[] = {
LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4,
LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7
};
time_t reinit_time; /* reinitialization time */
static thread_t init_thread; /* reinitialization thread */
int
main(int argc, char *argv[])
{
sigset_t set;
int i, ns, err = 0;
struct rlimit rl;
struct hostent *hp;
thread_t sigthread;
int nss_lwp = 0;
int32_t ncpus;
char scratch[MAXHOSTNAMELEN + 1];
char ntoab[INET_ADDRSTRLEN];
char *ownerip_args, *sip, *lasts;
int np = 1;
struct in_addr *oip;
#ifdef DEBUG
mallocctl(MTDEBUGPATTERN, 1);
mallocctl(MTINITBUFFER, 1);
#endif /* DEBUG */
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEXT"
#endif /* ! TEXT_DOMAIN */
(void) textdomain(TEXT_DOMAIN);
if (geteuid() != (uid_t)0) {
(void) fprintf(stderr, gettext("Must be 'root' to run %s.\n"),
DHCPD);
return (EPERM);
}
if ((err = collect_options(argc, argv)) != 0) {
if (errno == EAGAIN) {
(void) fprintf(stderr, gettext("DHCP daemon config "
"file locked.\n"));
err = EAGAIN;
} else {
usage();
err = EINVAL;
}
return (err);
}
/* Deal with run mode generic options first */
debug = DHCPCOP_BOOL(C_DEBUG);
verbose = DHCPCOP_BOOL(C_VERBOSE);
max_hops = DHCPCOP_NUM(C_HOPS);
interfaces = DHCPCOP_STR(C_IF);
bootp_compat = DHCPCOP_PRES(C_BOOTP); /* present then yes */
max_clients = DHCPCOP_NUM(C_CLIENT);
max_threads = DHCPCOP_NUM(C_THREADS);
log_local = DHCPCOP_PRES(C_LOGGING) ?
log_facilities[DHCPCOP_NUM(C_LOGGING)] : -1;
server_mode = (strcasecmp(DHCPCOP_STR(C_RUNMODE), DSVC_CV_SERVER) == 0);
if (server_mode) {
if (bootp_compat) {
be_automatic = (strcasecmp(DHCPCOP_STR(C_BOOTP),
DSVC_CV_AUTOMATIC) == 0);
}
if (DHCPCOP_BOOL(C_ICMP) == B_FALSE) {
(void) fprintf(stderr, gettext("\nWARNING: Disabling \
duplicate IP address detection!\n\n"));
noping = B_TRUE;
} else {
noping = B_FALSE;
}
off_secs = DHCPCOP_NUM(C_OFFER);
cache_secs = DHCPCOP_NUM(C_CACHE);
renog_secs = DHCPCOP_NUM(C_RENOG);
min_lru = DHCPCOP_NUM(C_MINLRU);
port_offset = DHCPCOP_NUM(C_DBGPORT); /* Private debug flag */
#ifdef DEBUG
dbg_net = DHCPCOP_STR(C_DBGNET);
#endif /* DEBUG */
nsutimeout_secs = DHCPCOP_PRES(C_NSUPDATE) ?
DHCPCOP_NUM(C_NSUPDATE) : DHCP_NO_NSU;
if ((rescan_interval = DHCPCOP_NUM(C_RESCAN)) != 0) {
rescan_interval *= rescan_scale;
}
/* Load current datastore, if any. */
if (dsp == NULL)
return (1);
if ((i = confopt_to_datastore(dsp, &datastore)) !=
DSVC_SUCCESS) {
(void) fprintf(stderr, gettext(
"WARNING: Invalid datastore: %s\n"),
dhcpsvc_errmsg(i));
return (1);
}
free_dsvc_conf(dsp);
ns = status_dd(&datastore);
if (ns != DSVC_SUCCESS) {
(void) fprintf(stderr, gettext(
"Datastore status error: %s\n"),
dhcpsvc_errmsg(ns));
return (1);
}
} else {
if (!DHCPCOP_PRES(C_RELAY)) {
(void) fprintf(stderr, gettext("Missing BOOTP "
"relay destinations (%s)\n"),
DSVC_CK_RELAY_DESTINATIONS);
return (1);
}
if ((err = relay_agent_init(DHCPCOP_STR(C_RELAY))) != 0)
return (err);
}
if (!debug) {
/* Daemon (background, detach from controlling tty). */
switch (fork()) {
case -1:
(void) fprintf(stderr,
gettext("Daemon cannot fork(): %s\n"),
strerror(errno));
return (errno);
case 0:
/* child */
break;
default:
/* parent */
return (0);
}
closefrom(0); /* close all open files */
errno = 0; /* clean up benign bad file no error */
(void) open("/dev/null", O_RDONLY, 0);
(void) dup2(0, 1);
(void) dup2(0, 2);
/* set NOFILE to unlimited */
rl.rlim_cur = rl.rlim_max = RLIM_INFINITY;
if ((err = setrlimit(RLIMIT_NOFILE, &rl)) < 0) {
dhcpmsg(LOG_ERR, "Cannot set open file limit: %s\n",
strerror(errno));
return (err);
}
/* Detach console */
(void) setsid();
(void) openlog(DHCPD, LOG_PID, LOG_DAEMON);
if (verbose)
dhcpmsg(LOG_INFO, "Daemon started.\n");
}
/*
* Block all signals in main thread - threads created will also
* ignore signals.
*/
(void) sigfillset(&set);
(void) sigdelset(&set, SIGABRT); /* allow for user abort */
(void) thr_sigsetmask(SIG_SETMASK, &set, NULL);
/*
* Create signal handling thread.
* Due to threads library limitations, the main program
* thread currently cannot function as the signal thread, and
* must be a bound thread.
*/
if ((err = thr_create(NULL, 0, sig_handle, NULL, THR_NEW_LWP |
THR_DAEMON | THR_BOUND | THR_DETACHED, &sigthread)) != 0) {
(void) fprintf(stderr,
gettext("Cannot start signal handling thread, error: %d\n"),
err);
return (err);
}
#ifdef DEBUG
(void) fprintf(stderr,
gettext("Started signal handling thread: %d\n"), sigthread);
#endif /* DEBUG */
/* Save away the IP address associated with our HOSTNAME. */
#ifdef DEBUG
/* Debugging: allow shared use of difficult to create databases. */
if (getenv("DHCP_HOSTNAME") != NULL)
(void) strcpy(scratch, getenv("DHCP_HOSTNAME"));
else
#endif /* DEBUG */
(void) sysinfo(SI_HOSTNAME, scratch, MAXHOSTNAMELEN + 1);
if ((hp = gethostbyname(scratch)) != NULL &&
hp->h_addrtype == AF_INET &&
hp->h_length == sizeof (struct in_addr)) {
(void) memcpy((char *)&server_ip, hp->h_addr_list[0],
sizeof (server_ip));
/*
* server_ip is supplemented by owner_ip list
* the first in the list of owner_ips always = server_ip
*/
owner_ip = smalloc((sizeof (struct in_addr)) * (np + 1));
(void) memcpy(owner_ip, &server_ip, sizeof (server_ip));
if (DHCPCOP_PRES(C_OWNER)) {
ownerip_args = DHCPCOP_STR(C_OWNER);
sip = strtok_r(ownerip_args, ",", &lasts);
while (sip != NULL) {
owner_ip = srealloc(owner_ip,
(sizeof (struct in_addr)) * (np + 2));
oip = owner_ip + np;
if (inet_pton(AF_INET, sip, oip) == 0 ||
oip->s_addr == INADDR_ANY) {
dhcpmsg(LOG_ERR,
"Invalid OWNER IP address %s\n",
sip);
sip = strtok_r(NULL, ",", &lasts);
continue;
}
np++;
sip = strtok_r(NULL, ",", &lasts);
}
}
oip = owner_ip + np;
oip->s_addr = INADDR_ANY;
} else {
dhcpmsg(LOG_ERR,
"Cannot determine server hostname/IP address.\n");
local_closelog();
return (1);
}
if (res_ninit(&resolv_conf) == -1)
dhcpmsg(LOG_ERR, "Cannot acquire resolver configuration.\n");
i = 0;
if (server_mode) {
/*
* Calculate limits to maximum concurrency. Special values:
* If max_{threads,clients} == 0, calculate limits
* based on cpu and memory.
* Else if max_{threads,clients} is set to -1, run without
* concurrency limits.
* Else use supplied limits.
*/
if ((ncpus = sysconf(_SC_NPROCESSORS_CONF)) < 0)
ncpus = 1;
if (max_clients == 0)
max_clients = DHCP_DEFAULT_CLIENTS * ncpus;
/* Require a minimum number of client structs. */
if (max_clients != -1 && max_clients < DHCP_MIN_CLIENTS) {
max_clients = DHCP_MIN_CLIENTS;
dhcpmsg(LOG_ERR, "Warning: adjusting MAX_CLIENTS"
" to minimum value %d\n", max_clients);
}
if (max_threads == 0)
max_threads = max_clients/4;
/*
* 4321342: Alloc additional lwps for unbound library threads.
* Remove this performance workaround when bug fixed.
*/
if (max_clients != 0)
nss_lwp = max_clients/8;
if (nss_lwp <= 0 || nss_lwp > DHCP_NSS_LWP)
nss_lwp = DHCP_NSS_LWP;
i = thr_setconcurrency(nss_lwp);
}
if (verbose) {
if (i != 0)
dhcpmsg(LOG_ERR, "Error setting concurrency %d: %s\n",
max_threads, strerror(i));
dhcpmsg(LOG_INFO, "Daemon Version: %s\n", DAEMON_VERS);
dhcpmsg(LOG_INFO, "Maximum relay hops: %d\n", max_hops);
if (log_local > -1) {
dhcpmsg(LOG_INFO,
"Transaction logging to %s enabled.\n",
debug ? "console" : "syslog");
}
if (server_mode) {
dhcpmsg(LOG_INFO, "Run mode is: DHCP Server Mode.\n");
dhcpmsg(LOG_INFO, "Datastore resource: %s\n",
datastore.d_resource ?
datastore.d_resource : "");
dhcpmsg(LOG_INFO, "Location: %s\n",
datastore.d_location ?
datastore.d_location : "");
dhcpmsg(LOG_INFO, "DHCP offer TTL: %d\n", off_secs);
if (bootp_compat)
dhcpmsg(LOG_INFO,
"BOOTP compatibility enabled.\n");
if (rescan_interval != 0) {
dhcpmsg(LOG_INFO,
"Dhcptab rescan interval: %d minutes.\n",
rescan_interval / rescan_scale);
}
dhcpmsg(LOG_INFO, "ICMP validation timeout: %d "
"milliseconds, Attempts: %d.\n", icmp_timeout,
icmp_tries);
if (nsutimeout_secs != DHCP_NO_NSU) {
dhcpmsg(LOG_INFO, "Name service update "
"enabled, timeout: %d seconds\n",
nsutimeout_secs);
}
for (oip = owner_ip; oip->s_addr != INADDR_ANY; oip++)
dhcpmsg(LOG_INFO, "Owner IP address: %s\n",
inet_ntop(AF_INET, oip, ntoab,
sizeof (ntoab)));
dhcpmsg(LOG_INFO, "Maximum concurrent clients: %d\n",
max_clients);
dhcpmsg(LOG_INFO, "Maximum threads: %d\n", max_threads);
} else
dhcpmsg(LOG_INFO, "Run mode is: Relay Agent Mode.\n");
}
(void) mutex_init(&ttg_mtx, USYNC_THREAD, 0);
(void) cond_init(&ttg_cv, USYNC_THREAD, 0);
if (server_mode) {
if (initntab() != 0) {
dhcpmsg(LOG_ERR, "Cannot allocate per network hash "
"table.\n");
local_closelog();
(void) mutex_destroy(&ttg_mtx);
(void) cond_destroy(&ttg_cv);
return (1);
}
if (initmtab() != 0) {
dhcpmsg(LOG_ERR, "Cannot allocate macro hash table.\n");
local_closelog();
(void) mutex_destroy(&ttg_mtx);
(void) cond_destroy(&ttg_cv);
return (1);
}
if ((err = checktab()) != 0 ||
(err = readtab(NEW_DHCPTAB)) != 0) {
if (err == ENOENT) {
no_dhcptab = B_TRUE;
} else {
dhcpmsg(LOG_ERR,
"Error reading macro table.\n");
local_closelog();
(void) mutex_destroy(&ttg_mtx);
(void) cond_destroy(&ttg_cv);
return (err);
}
} else
no_dhcptab = B_FALSE;
}
if ((err = open_interfaces()) != 0) {
local_closelog();
return (err);
}
/*
* While forever, handle signals and dispatch them.
*/
while (!time_to_go) {
(void) mutex_lock(&ttg_mtx);
while (!time_to_go)
(void) cond_wait(&ttg_cv, &ttg_mtx);
(void) mutex_unlock(&ttg_mtx);
}
/* Daemon terminated. */
if (server_mode) {
resettab(B_TRUE);
close_clnts(); /* reaps client threads */
}
close_interfaces(); /* reaps monitor threads */
local_closelog();
(void) fflush(NULL);
(void) mutex_destroy(&ttg_mtx);
(void) cond_destroy(&ttg_cv);
return (err);
}
/*
* Signal handler routine. All signals handled by calling thread.
*/
/* ARGSUSED */
static void *
sig_handle(void *arg)
{
int err;
int sig;
sigset_t set;
char buf[SIG2STR_MAX];
timespec_t ts;
siginfo_t si;
(void) sigfillset(&set); /* catch all signals */
ts.tv_sec = rescan_interval == 0 ? DEFAULT_LEASE : rescan_interval;
ts.tv_nsec = 0L;
/* wait for a signal */
while (!time_to_go) {
switch (sig = sigtimedwait(&set, &si, &ts)) {
case -1:
if (rescan_interval == 0 || errno != EAGAIN)
break;
/*FALLTHRU*/
case SIGHUP:
/*
* Create reinitialization thread.
*/
if (init_thread != NULL)
break;
if ((err = thr_create(NULL, 0, reinitialize,
&init_thread, THR_BOUND | THR_DETACHED,
&init_thread)) != 0) {
(void) fprintf(stderr, gettext(
"Cannot start reinit thread, error: %d\n"),
err);
}
break;
case SIGTERM:
/* FALLTHRU */
case SIGINT:
(void) sig2str(sig, buf);
dhcpmsg(LOG_NOTICE, "Signal: %s received...Exiting\n",
buf);
time_to_go = B_TRUE;
break;
default:
if (verbose) {
(void) sig2str(sig, buf);
dhcpmsg(LOG_INFO,
"Signal: %s received...Ignored\n",
buf);
}
break;
}
if (time_to_go) {
(void) mutex_lock(&ttg_mtx);
(void) cond_signal(&ttg_cv);
(void) mutex_unlock(&ttg_mtx);
break;
}
}
return ((void *)sig); /* NOTREACHED */
}
static void
usage(void)
{
(void) fprintf(stderr, gettext(
"%s:\n\n\tCommon: [-d] [-v] [-i interface, ...] "
"[-h hops] [-l local_facility]\n\n\t"
"Server: [-n] [-t rescan_interval] [-o DHCP_offer_TTL]\n\t\t"
"[ -b automatic | manual]\n\n\t"
"Relay Agent: -r IP | hostname, ...\n"), DHCPD);
}
static void
local_closelog(void)
{
dhcpmsg(LOG_INFO, "Daemon terminated.\n");
if (!debug)
closelog();
}
/*
* Given a received BOOTP packet, generate an appropriately sized,
* and generically initialized BOOTP packet.
*/
PKT *
gen_bootp_pkt(int size, PKT *srcpktp)
{
PKT *pkt = (PKT *)smalloc(size);
pkt->htype = srcpktp->htype;
pkt->hlen = srcpktp->hlen;
pkt->xid = srcpktp->xid;
pkt->secs = srcpktp->secs;
pkt->flags = srcpktp->flags;
pkt->giaddr.s_addr = srcpktp->giaddr.s_addr;
(void) memcpy(pkt->cookie, srcpktp->cookie, 4);
(void) memcpy(pkt->chaddr, srcpktp->chaddr, srcpktp->hlen);
return (pkt);
}
/*
* Points field serves to identify those packets whose allocated size
* and address is not represented by the address in pkt.
*/
void
free_plp(PKT_LIST *plp)
{
char *tmpp;
#ifdef DEBUG
dhcpmsg(LOG_DEBUG,
"%04d: free_plp(0x%x)pkt(0x%x)len(%d)next(0x%x)prev(0x%x)\n",
thr_self(), plp, plp->pkt, plp->len,
plp->next, plp->prev);
#endif /* DEBUG */
if (plp->pkt) {
if (plp->offset != 0)
tmpp = (char *)((uint_t)plp->pkt - plp->offset);
else
tmpp = (char *)plp->pkt;
free(tmpp);
}
free(plp);
plp = NULL;
}
/*
* Validate boolean is "B_TRUE" or "B_FALSE".
* Returns B_TRUE if successful, B_FALSE otherwise.
*/
static boolean_t
bool_v(DHCP_COP *dp, const char *option)
{
boolean_t i;
assert(dp != NULL && option != NULL);
if (strcasecmp(option, DSVC_CV_TRUE) == 0) {
i = B_TRUE;
} else if (strcasecmp(option, DSVC_CV_FALSE) == 0) {
i = B_FALSE;
} else {
return (B_FALSE); /* huh? */
}
dp->cop_bool = i;
return (B_TRUE);
}
/*
* Validate uchar data.
* Returns B_TRUE if successful, B_FALSE otherwise.
*/
static boolean_t
uchar_v(DHCP_COP *dp, const char *option)
{
if (dp == NULL || option == NULL || !isdigit(*option))
return (B_FALSE);
dp->cop_num = strtoul(option, 0L, 0L);
if (dp->cop_num < 0 || dp->cop_num > 0xFF)
return (B_FALSE);
return (B_TRUE);
}
/*
* Validate integer data.
* Returns B_TRUE if successful, B_FALSE otherwise.
*/
static boolean_t
int_v(DHCP_COP *dp, const char *option)
{
if (dp != NULL && option != NULL) {
dp->cop_num = strtol(option, NULL, 0L);
if (errno == 0)
return (B_TRUE);
}
return (B_FALSE);
}
/*
* Validate unsigned integer data.
* Returns B_TRUE if successful, B_FALSE otherwise.
*/
static boolean_t
uint_v(DHCP_COP *dp, const char *option)
{
if (dp != NULL && option != NULL) {
dp->cop_num = strtoul(option, NULL, 0L);
if (errno == 0)
return (B_TRUE);
}
return (B_FALSE);
}
/*
* Check if value is a string.
* Returns B_TRUE if successful, B_FALSE otherwise
*/
static boolean_t
str_v(DHCP_COP *dp, const char *option)
{
if (dp == NULL || option == NULL ||
(dp->cop_str = strdup(option)) == NULL) {
return (B_FALSE);
}
return (B_TRUE);
}
/*
* Validate bootp compatibility options. Must be "automatic" or
* "manual".
* Returns B_TRUE if successful, B_FALSE otherwise.
*/
static boolean_t
bootp_v(DHCP_COP *dp, const char *option)
{
if (dp == NULL || option == NULL)
return (B_FALSE);
if ((strcasecmp(option, DSVC_CV_AUTOMATIC) == 0 ||
strcasecmp(option, DSVC_CV_MANUAL) == 0) &&
(dp->cop_str = strdup(option)) != NULL) {
return (B_TRUE);
}
return (B_FALSE);
}
/*
* Validate logging facility. Must be a number between 0 and 7 inclusive.
* Returns B_TRUE if successful, B_FALSE otherwise.
*/
static boolean_t
logging_v(DHCP_COP *dp, const char *option)
{
if (uint_v(dp, option) && dp->cop_num <= 7)
return (B_TRUE);
(void) fprintf(stderr, gettext("Syslog local facility must be in the "
"range of 0 through 7.\n"));
return (B_FALSE);
}
/*
* Validate run mode. Must be "server" or "relay".
* Returns B_TRUE if successful, B_FALSE otherwise
*/
static boolean_t
runmode_v(DHCP_COP *dp, const char *option)
{
if (dp == NULL || option == NULL)
return (B_FALSE);
if ((strcasecmp(option, DSVC_CV_SERVER) == 0 ||
strcasecmp(option, DSVC_CV_RELAY) == 0) &&
(dp->cop_str = strdup(option)) != NULL) {
return (B_TRUE);
}
return (B_FALSE);
}
/*
* Initialize options table based upon config file settings or command
* line flags. Handle all option inter-dependency checking here. No value
* checking is done here.
*
* Returns 0 if successful, nonzero otherwise.
*/
static int
collect_options(int count, char **args)
{
int c, i, j;
char *mode;
/* First, load the configuration options from the file, if present. */
for (errno = 0, i = 0; i < DHCP_RDCOP_RETRIES &&
read_dsvc_conf(&dsp) < 0; i++) {
(void) fprintf(stderr, gettext(
"WARNING: DHCP daemon config file: %s\n"),
strerror(errno));
if (errno == EAGAIN) {
/* file's busy, wait one second and try again */
(void) sleep(1);
} else
break;
}
if (errno == EAGAIN)
return (EAGAIN);
/* set default RUN_MODE to server if it wasn't found in the file */
if (query_dsvc_conf(dsp, DSVC_CK_RUN_MODE, &mode) < 0) {
if (errno == ENOENT) {
if (add_dsvc_conf(&dsp, DSVC_CK_RUN_MODE,
DSVC_CV_SERVER) != 0)
return (errno);
}
} else
free(mode);
/*
* Second, pick up the user's preferences from the command line,
* which modify the config file settings.
*/
while ((c = getopt(count, args, "dnvh:o:r:b:i:t:l:")) != -1) {
boolean_t relay_mode = B_FALSE;
char *key = NULL, *value = NULL;
switch (c) {
case 'd':
key = "DEBUG";
value = DSVC_CV_TRUE;
break;
case 'n':
key = DSVC_CK_ICMP_VERIFY;
value = DSVC_CV_FALSE;
break;
case 'v':
key = DSVC_CK_VERBOSE;
value = DSVC_CV_TRUE;
break;
case 'r':
key = DSVC_CK_RELAY_DESTINATIONS;
value = optarg;
relay_mode = B_TRUE;
break;
case 'b':
key = DSVC_CK_BOOTP_COMPAT;
value = optarg;
break;
case 'h':
key = DSVC_CK_RELAY_HOPS;
value = optarg;
break;
case 'i':
key = DSVC_CK_INTERFACES;
value = optarg;
break;
case 'o':
key = DSVC_CK_OFFER_CACHE_TIMEOUT;
value = optarg;
break;
case 't':
key = DSVC_CK_RESCAN_INTERVAL;
value = optarg;
break;
case 'l':
key = DSVC_CK_LOGGING_FACILITY;
value = optarg;
break;
default:
(void) fprintf(stderr, gettext("Unknown option: %c\n"),
c);
return (EINVAL);
}
/*
* Create parameters if they don't exist, or replace
* their value if they exist.
*/
if (replace_dsvc_conf(&dsp, key, value) < 0)
return (errno);
if (relay_mode) {
if (replace_dsvc_conf(&dsp, DSVC_CK_RUN_MODE,
DSVC_CV_RELAY) < 0)
return (errno);
}
}
if (optind < count) {
/* get all unused arguments */
(void) fprintf(stderr, "%s: unexpected argument(s) \"",
args[0]);
for (; optind < count; optind++) {
if (args[optind][0] != '-')
(void) fprintf(stderr, " %s", args[optind]);
else
break;
}
(void) fprintf(stderr, "\"; Aborting\n");
return (EINVAL);
}
/* load options table, validating value portions of present as we go */
for (i = 0; dsp != NULL && dsp[i].co_key != NULL; i++) {
if (dsp[i].co_type != DHCP_KEY)
continue; /* comment */
for (j = 0; j <= C_LAST; j++) {
if (strcasecmp(DHCPCOP_NAME(j),
dsp[i].co_key) == 0) {
DHCPCOP_PRES(j) = B_TRUE;
if (DHCPCOP_VINIT(j, dsp[i].co_value))
break;
else {
(void) fprintf(stderr, gettext(
"Invalid value for option: %s\n"),
DHCPCOP_NAME(j));
return (EINVAL);
}
}
}
}
return (0);
}
/*
* monitor_client: worker thread from pool created for each network.
* We loop through and process one packet. Relay agent tasks are handled by
* the per-interface threads, thus we should only be dealing with bootp/dhcp
* server bound packets here.
*
* The worker thread treats the client packet lists as
* "stacks", or FIFO objects. We do this so that we get
* the latest, equivalent request from the client before
* responding, thus keeping the chance of responding to
* moldy requests to an absolute minimum.
*
* Performance: a pool of threads are used, to avoid thread startup/teardown.
* Per-interface threads keep track of clients who cannot be serviced
* due to a lack of threads. After completing the current request, threads
* look for other work to do, before suspending and waiting to be
* continued when work is available.
*/
void *
monitor_client(void *arg)
{
dsvc_thr_t *thrp = (dsvc_thr_t *)arg;
dsvc_clnt_t *pcd;
dsvc_dnet_t *pnd;
dsvc_pendclnt_t *workp;
IF *ifp;
PKT_LIST *plp = NULL;
int nclients;
boolean_t delete;
uint_t flags = 0;
/*
* Initialize variables.
*
* Due to a possible race between suspend and continue, we must
* provide a positive indication that the thread has continued to
* the per-interface thread.
*/
(void) mutex_lock(&thrp->thr_mtx);
pcd = thrp->thr_pcd;
thrp->thr_pcd = NULL;
(void) mutex_unlock(&thrp->thr_mtx);
ifp = pcd->ifp;
pnd = pcd->pnd;
/*
* The per-interface thread leaves the client struct open,
* so it cannot be garbage-collected in the interim.
* Keep track of when we must release client structs.
*/
(void) mutex_lock(&pnd->thr_mtx);
nclients = pnd->nclients;
(void) mutex_unlock(&pnd->thr_mtx);
for (; (flags & DHCP_THR_EXITING) == 0; ) {
if (pcd == NULL) {
/*
* No work. Place thread struct on free list
* if it isn't already, and suspend
* until new work is available.
*/
(void) mutex_lock(&thrp->thr_mtx);
if ((thrp->thr_flags & DHCP_THR_LIST) == 0) {
thrp->thr_flags |= DHCP_THR_LIST;
thrp->thr_pcd = NULL;
thrp->thr_next = NULL;
(void) mutex_lock(&pnd->thr_mtx);
if (pnd->thrhead != NULL) {
pnd->thrtail->thr_next = thrp;
} else {
pnd->thrhead = thrp;
}
pnd->thrtail = thrp;
(void) mutex_unlock(&pnd->thr_mtx);
}
/* Wait for new work. */
(void) cond_wait(&thrp->thr_cv, &thrp->thr_mtx);
/*
* Resume with new client if any.
*/
pcd = thrp->thr_pcd;
thrp->thr_pcd = NULL;
flags = thrp->thr_flags;
(void) mutex_unlock(&thrp->thr_mtx);
continue;
}
(void) mutex_lock(&pcd->pkt_mtx);
/*
* Remove the first packet from the list
*/
plp = pcd->pkthead;
if (plp != NULL) {
detach_plp(pcd, plp);
pcd->pending--;
/*
* See if there's a later one
* exchanging this plp for that one.
*/
plp = refresh_pktlist(pcd, plp);
}
(void) mutex_unlock(&pcd->pkt_mtx);
(void) mutex_lock(&pcd->pcd_mtx);
if (plp == NULL || (pcd->flags & DHCP_PCD_CLOSING) != 0) {
if (plp) {
free_plp(plp); /* Free the packet. */
plp = NULL;
}
/*
* No work remaining for this client. Release,
* and check for other deferred clients on the
* per net work list.
*/
pcd->flags &= ~DHCP_PCD_WORK;
/*
* Housekeeping: delete pcd immediately if above
* threshold and no offer has been made, or offer
* has been completed. Only perform deletion if no
* other thread has.
*/
delete = B_FALSE;
if (max_clients != -1 &&
(pcd->flags & DHCP_PCD_CLOSING) == 0) {
if (nclients >=
max_clients - DHCP_MINFREE_CLIENTS &&
pcd->off_ip.s_addr == htonl(INADDR_ANY)) {
/* Remove clients without offers. */
pcd->flags |= DHCP_PCD_CLOSING;
delete = B_TRUE;
} else if (nclients > max_clients/2 &&
(pcd->state == ACK ||
(pcd->state == REQUEST &&
pcd->off_ip.s_addr == htonl(INADDR_ANY)))) {
/* Remove completed clients. */
pcd->flags |= DHCP_PCD_CLOSING;
delete = B_TRUE;
} else if (pcd->state == RELEASE ||
pcd->state == DECLINE) {
/* Remove freed clients. */
pcd->flags |= DHCP_PCD_CLOSING;
delete = B_TRUE;
}
}
pcd->clnt_thread = NULL;
(void) mutex_unlock(&pcd->pcd_mtx);
/* Close the client. */
close_clnt(pcd, delete);
pcd = NULL;
/*
* Remove next deferred work from list.
*/
workp = NULL;
(void) mutex_lock(&pnd->thr_mtx);
nclients = pnd->nclients;
workp = pnd->workhead;
if (workp &&
(pnd->workhead = pnd->workhead->pnd_next) == NULL)
pnd->worktail = NULL;
(void) mutex_unlock(&pnd->thr_mtx);
if (workp != NULL) {
/* See if the deferred client still exists. */
if (open_clnt(pnd, &pcd, workp->pnd_cid,
workp->pnd_cid_len, B_TRUE) != DSVC_SUCCESS)
pcd = NULL;
if (pcd == NULL) {
free(workp);
continue;
}
(void) mutex_lock(&pcd->pcd_mtx);
/* Check if it needs a worker thread. */
if (pcd->clnt_thread == NULL &&
(pcd->flags & DHCP_PCD_WORK) != 0 &&
(pcd->flags & DHCP_PCD_CLOSING) == 0) {
/* Found a valid client. Restart. */
pcd->clnt_thread = thrp;
(void) mutex_unlock(&pcd->pcd_mtx);
ifp = pcd->ifp;
free(workp);
continue;
}
(void) mutex_unlock(&pcd->pcd_mtx);
close_clnt(pcd, B_FALSE);
pcd = NULL;
free(workp);
}
continue;
}
(void) mutex_unlock(&pcd->pcd_mtx);
/*
* Based on the packet type, process accordingly.
*/
if (plp->pkt->op == BOOTREQUEST) {
if (plp->opts[CD_DHCP_TYPE]) {
/* DHCP packet */
dhcp(pcd, plp);
} else {
/* BOOTP packet */
if (!bootp_compat) {
dhcpmsg(LOG_INFO, "BOOTP request "
"received on interface: %s "
"ignored.\n", ifp->nm);
} else {
bootp(pcd, plp);
}
}
}
if (plp != NULL) {
free_plp(plp); /* Free the packet. */
plp = NULL;
}
(void) mutex_lock(&ifp->ifp_mtx);
ifp->processed++;
(void) mutex_unlock(&ifp->ifp_mtx);
}
/* Free the packet. */
if (plp != NULL)
free_plp(plp);
/* Release the client structure. */
if (pcd != NULL) {
(void) mutex_lock(&pcd->pcd_mtx);
pcd->flags &= ~DHCP_PCD_WORK;
pcd->clnt_thread = NULL;
(void) mutex_unlock(&pcd->pcd_mtx);
close_clnt(pcd, B_FALSE);
}
/* Release the thread reference in pernet structure. */
if (pnd != NULL) {
(void) mutex_lock(&pnd->thr_mtx);
pnd->nthreads--;
(void) cond_signal(&pnd->thr_cv);
(void) mutex_unlock(&pnd->thr_mtx);
}
return (NULL);
}