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
* 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 <stdarg.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <sys/systeminfo.h>
#include <errno.h>
#include <synch.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 char *optarg;
typedef struct dhcp_cops {
char *cop_name; /* opt name */
union {
char *ucop_str;
int ucop_num;
} dhcp_cops_un;
} DHCP_COP;
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
#else /* DEBUG */
#endif /* DEBUG */
/* name Present? Verify func Value */
/* ==== ======== =========== ===== */
/* Run mode / BOOTP relay agent selection option */
/* Generic daemon options */
/* DHCP server run mode options */
/* BOOTP relay agent options */
/* Name service update timeout */
#ifdef DEBUG
#endif /* DEBUG */
};
int debug;
int log_local; /* syslog local facility number */
int max_threads; /* maximum number of worker threads per net */
int max_clients; /* maximum number of active clients per net */
#ifdef DEBUG
#endif /* DEBUG */
/*
* 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.
*/
/* local syslog facilities */
static int log_facilities[] = {
};
int
{
int nss_lwp = 0;
char ntoab[INET_ADDRSTRLEN];
int np = 1;
#ifdef DEBUG
#endif /* DEBUG */
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEXT"
#endif /* ! TEXT_DOMAIN */
(void) textdomain(TEXT_DOMAIN);
DHCPD);
return (EPERM);
}
"file locked.\n"));
} else {
usage();
}
return (err);
}
/* Deal with run mode generic options first */
if (server_mode) {
if (bootp_compat) {
DSVC_CV_AUTOMATIC) == 0);
}
duplicate IP address detection!\n\n"));
} else {
}
#ifdef DEBUG
#endif /* DEBUG */
}
/* Load current datastore, if any. */
return (1);
DSVC_SUCCESS) {
"WARNING: Invalid datastore: %s\n"),
dhcpsvc_errmsg(i));
return (1);
}
if (ns != DSVC_SUCCESS) {
"Datastore status error: %s\n"),
dhcpsvc_errmsg(ns));
return (1);
}
} else {
if (!DHCPCOP_PRES(C_RELAY)) {
"relay destinations (%s)\n"),
return (1);
}
return (err);
}
if (!debug) {
/* Daemon (background, detach from controlling tty). */
switch (fork()) {
case -1:
gettext("Daemon cannot fork(): %s\n"),
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) dup2(0, 1);
(void) dup2(0, 2);
/* set NOFILE to unlimited */
return (err);
}
/* Detach console */
(void) setsid();
if (verbose)
}
/*
* Block all signals in main thread - threads created will also
* ignore signals.
*/
(void) sigfillset(&set);
/*
* 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.
*/
gettext("Cannot start signal handling thread, error: %d\n"),
err);
return (err);
}
#ifdef DEBUG
#endif /* DEBUG */
/* Save away the IP address associated with our HOSTNAME. */
#ifdef DEBUG
/* Debugging: allow shared use of difficult to create databases. */
else
#endif /* DEBUG */
sizeof (server_ip));
/*
* server_ip is supplemented by owner_ip list
* the first in the list of owner_ips always = server_ip
*/
if (DHCPCOP_PRES(C_OWNER)) {
"Invalid OWNER IP address %s\n",
sip);
continue;
}
np++;
}
}
} else {
return (1);
}
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.
*/
ncpus = 1;
if (max_clients == 0)
/* Require a minimum number of client structs. */
" to minimum value %d\n", max_clients);
}
if (max_threads == 0)
/*
* 4321342: Alloc additional lwps for unbound library threads.
* Remove this performance workaround when bug fixed.
*/
if (max_clients != 0)
i = thr_setconcurrency(nss_lwp);
}
if (verbose) {
if (i != 0)
max_threads, strerror(i));
if (log_local > -1) {
"Transaction logging to %s enabled.\n",
}
if (server_mode) {
if (bootp_compat)
"BOOTP compatibility enabled.\n");
if (rescan_interval != 0) {
"Dhcptab rescan interval: %d minutes.\n",
}
"milliseconds, Attempts: %d.\n", icmp_timeout,
if (nsutimeout_secs != DHCP_NO_NSU) {
"enabled, timeout: %d seconds\n",
}
sizeof (ntoab)));
} else
}
if (server_mode) {
if (initntab() != 0) {
"table.\n");
(void) mutex_destroy(&ttg_mtx);
(void) cond_destroy(&ttg_cv);
return (1);
}
if (initmtab() != 0) {
(void) mutex_destroy(&ttg_mtx);
(void) cond_destroy(&ttg_cv);
return (1);
}
no_dhcptab = B_TRUE;
} else {
"Error reading macro table.\n");
(void) mutex_destroy(&ttg_mtx);
(void) cond_destroy(&ttg_cv);
return (err);
}
} else
}
if ((err = open_interfaces()) != 0) {
return (err);
}
/*
* While forever, handle signals and dispatch them.
*/
while (!time_to_go) {
(void) mutex_lock(&ttg_mtx);
while (!time_to_go)
(void) mutex_unlock(&ttg_mtx);
}
/* Daemon terminated. */
if (server_mode) {
close_clnts(); /* reaps client threads */
}
close_interfaces(); /* reaps monitor threads */
(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;
char buf[SIG2STR_MAX];
/* wait for a signal */
while (!time_to_go) {
case -1:
break;
/*FALLTHRU*/
case SIGHUP:
/*
* Create reinitialization thread.
*/
if (init_thread != NULL)
break;
&init_thread)) != 0) {
"Cannot start reinit thread, error: %d\n"),
err);
}
break;
case SIGTERM:
/* FALLTHRU */
case SIGINT:
buf);
time_to_go = B_TRUE;
break;
default:
if (verbose) {
"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)
{
"%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)
{
if (!debug)
closelog();
}
/*
* Given a received BOOTP packet, generate an appropriately sized,
* and generically initialized BOOTP packet.
*/
PKT *
{
return (pkt);
}
/*
* Points field serves to identify those packets whose allocated size
* and address is not represented by the address in pkt.
*/
void
{
char *tmpp;
#ifdef DEBUG
"%04d: free_plp(0x%x)pkt(0x%x)len(%d)next(0x%x)prev(0x%x)\n",
#endif /* DEBUG */
else
}
}
/*
* Validate boolean is "B_TRUE" or "B_FALSE".
* Returns B_TRUE if successful, B_FALSE otherwise.
*/
static boolean_t
{
boolean_t i;
i = B_TRUE;
i = B_FALSE;
} else {
return (B_FALSE); /* huh? */
}
return (B_TRUE);
}
/*
* Validate uchar data.
* Returns B_TRUE if successful, B_FALSE otherwise.
*/
static boolean_t
{
return (B_FALSE);
return (B_FALSE);
return (B_TRUE);
}
/*
* Validate integer data.
* Returns B_TRUE if successful, B_FALSE otherwise.
*/
static boolean_t
{
if (errno == 0)
return (B_TRUE);
}
return (B_FALSE);
}
/*
* Validate unsigned integer data.
* Returns B_TRUE if successful, B_FALSE otherwise.
*/
static boolean_t
{
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
{
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
{
return (B_FALSE);
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
{
return (B_TRUE);
"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
{
return (B_FALSE);
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
{
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++) {
"WARNING: DHCP daemon config file: %s\n"),
/* file's busy, wait one second and try again */
(void) sleep(1);
} else
break;
}
return (EAGAIN);
/* set default RUN_MODE to server if it wasn't found in the file */
DSVC_CV_SERVER) != 0)
return (errno);
}
} else
/*
* Second, pick up the user's preferences from the command line,
* which modify the config file settings.
*/
switch (c) {
case 'd':
key = "DEBUG";
break;
case 'n':
break;
case 'v':
break;
case 'r':
relay_mode = B_TRUE;
break;
case 'b':
break;
case 'h':
break;
case 'i':
break;
case 'o':
break;
case 't':
break;
case 'l':
break;
default:
c);
return (EINVAL);
}
/*
* Create parameters if they don't exist, or replace
* their value if they exist.
*/
return (errno);
if (relay_mode) {
DSVC_CV_RELAY) < 0)
return (errno);
}
}
/* get all unused arguments */
args[0]);
else
break;
}
return (EINVAL);
}
/* load options table, validating value portions of present as we go */
continue; /* comment */
for (j = 0; j <= C_LAST; j++) {
if (strcasecmp(DHCPCOP_NAME(j),
DHCPCOP_PRES(j) = B_TRUE;
break;
else {
"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
* 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.
*
* 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)
{
int nclients;
/*
* 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.
*/
/*
* 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.
*/
for (; (flags & DHCP_THR_EXITING) == 0; ) {
/*
* No work. Place thread struct on free list
* if it isn't already, and suspend
* until new work is available.
*/
} else {
}
}
/* Wait for new work. */
/*
* Resume with new client if any.
*/
continue;
}
/*
* Remove the first packet from the list
*/
/*
* See if there's a later one
* exchanging this plp for that one.
*/
}
if (plp) {
}
/*
* No work remaining for this client. Release,
* and check for other deferred clients on the
* per net work list.
*/
/*
* 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.
*/
if (max_clients != -1 &&
if (nclients >=
/* Remove clients without offers. */
/* Remove completed clients. */
/* Remove freed clients. */
}
}
/* Close the client. */
/*
* Remove next deferred work from list.
*/
if (workp &&
/* See if the deferred client still exists. */
continue;
}
/* Check if it needs a worker thread. */
/* Found a valid client. Restart. */
continue;
}
}
continue;
}
/*
* Based on the packet type, process accordingly.
*/
/* DHCP packet */
} else {
/* BOOTP packet */
if (!bootp_compat) {
"received on interface: %s "
} else {
}
}
}
}
}
/* Free the packet. */
/* Release the client structure. */
}
/* Release the thread reference in pernet structure. */
}
return (NULL);
}