agent.c revision e704a8f24a369484ba8f4a1cf49d4db00dd91166
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdlib.h>
#include <errno.h>
#include <locale.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <dhcp_hostconf.h>
#include <dhcpagent_ipc.h>
#include <dhcpagent_util.h>
#include <dhcpmsg.h>
#include "async.h"
#include "agent.h"
#include "script_handler.h"
#include "util.h"
#include "class_id.h"
#include "states.h"
#include "packet.h"
#include "interface.h"
#include "defaults.h"
#ifndef TEXT_DOMAIN
#define TEXT_DOMAIN "SYS_TEST"
#endif
int class_id_len = 0;
char *class_id;
int rtsock_fd;
static unsigned int debug_level = 0;
/*
* The ipc_cmd_allowed[] table indicates which IPC commands are allowed in
* which states; a non-zero value indicates the command is permitted.
*
* START is permitted if the state machine is fresh, or if we are in the
* process of trying to obtain a lease (as a convenience to save the
* administrator from having to do an explicit DROP). EXTEND, RELEASE, and
* GET_TAG require a lease to be obtained in order to make sense. INFORM is
* permitted if the interface is fresh or has an INFORM in progress or
* previously done on it -- otherwise a DROP or RELEASE is first required.
* PING and STATUS always make sense and thus are always permitted, as is DROP
* in order to permit the administrator to always bail out.
*/
/* D E P R S S I G */
/* R X I E T T N E */
/* O T N L A A F T */
/* P E G E R T O _ */
/* . N . A T U R T */
/* . D . S . S M A */
/* . . . E . . . G */
/* INIT */ { 1, 0, 1, 0, 1, 1, 1, 0 },
/* SELECTING */ { 1, 0, 1, 0, 1, 1, 0, 0 },
/* REQUESTING */ { 1, 0, 1, 0, 1, 1, 0, 0 },
/* PRE_BOUND */ { 1, 1, 1, 1, 0, 1, 0, 1 },
/* BOUND */ { 1, 1, 1, 1, 0, 1, 0, 1 },
/* RENEWING */ { 1, 1, 1, 1, 0, 1, 0, 1 },
/* REBINDING */ { 1, 1, 1, 1, 0, 1, 0, 1 },
/* INFORMATION */ { 1, 0, 1, 0, 1, 1, 1, 1 },
/* INIT_REBOOT */ { 1, 0, 1, 1, 1, 1, 0, 0 },
/* ADOPTING */ { 1, 0, 1, 1, 0, 1, 0, 0 },
/* INFORM_SENT */ { 1, 0, 1, 0, 1, 1, 1, 0 },
/* DECLINING */ { 1, 1, 1, 1, 0, 1, 0, 1 },
/* RELEASING */ { 1, 0, 1, 0, 0, 1, 0, 1 },
};
/* DHCP_EXTEND */ CMD_ISPRIV,
/* DHCP_RELEASE */ CMD_ISPRIV,
};
int
{
int ipc_fd;
int c;
/*
* -l is ignored for compatibility with old agent.
*/
switch (c) {
case 'a':
grandparent = getpid();
break;
case 'd':
break;
case 'f':
break;
case 'v':
is_verbose = B_TRUE;
break;
case '?':
"\n", argv[0]);
return (EXIT_FAILURE);
default:
break;
}
}
(void) textdomain(TEXT_DOMAIN);
if (geteuid() != 0) {
dhcpmsg_fini();
return (EXIT_FAILURE);
}
dhcpmsg_fini();
return (EXIT_FAILURE);
}
/*
* Seed the random number generator, since we're going to need it
* to set transaction id's and for exponential backoff.
*/
(void) atexit(dhcpmsg_fini);
tq = iu_tq_create();
eh = iu_eh_create();
return (EXIT_FAILURE);
}
/*
* ignore most signals that could be reasonably generated.
*/
/*
* upon SIGTHAW we need to refresh any non-infinite leases.
*/
class_id = get_class_id();
else
"with no vendor class id");
/*
* the inactivity timer is enabled any time there are no
* interfaces under DHCP control. if DHCP_INACTIVITY_WAIT
* seconds transpire without an interface under DHCP control,
* the agent shuts down.
*/
/*
* max out the number available descriptors, just in case..
*/
/*
* Create and bind default IP sockets used to control interfaces and to
* catch stray packets.
*/
if (!dhcp_ip_default())
return (EXIT_FAILURE);
/*
* create the ipc channel that the agent will listen for
* requests on, and register it with the event handler so that
* `accept_event' will be called back.
*/
switch (dhcp_ipc_init(&ipc_fd)) {
case 0:
break;
case DHCP_IPC_E_BIND:
"%i (agent already running?)", IPPORT_DHCPAGENT);
return (EXIT_FAILURE);
default:
return (EXIT_FAILURE);
}
return (EXIT_FAILURE);
}
/*
* Create the global routing socket. This is used for monitoring
* interface transitions, so that we learn about the kernel's Duplicate
* Address Detection status, and for inserting and removing default
* routes as learned from DHCP servers. Both v4 and v6 are handed
* with this one socket.
*/
if (rtsock_fd == -1) {
return (EXIT_FAILURE);
}
return (EXIT_FAILURE);
}
/*
* if the -a (adopt) option was specified, try to adopt the
* kernel-managed interface before we start.
*/
if (do_adopt && !dhcp_adopt())
return (EXIT_FAILURE);
/*
* For DHCPv6, we own all of the interfaces marked DHCPRUNNING. As
* we're starting operation here, if there are any of those interfaces
* lingering around, they're strays, and need to be removed.
*
* It might be nice to save these addresses off somewhere -- for both
* v4 and v6 -- and use them as hints for later negotiation.
*/
/*
* enter the main event loop; this is where all the real work
* takes place (through registering events and scheduling timers).
* this function only returns when the agent is shutting down.
*/
case -1:
break;
case DHCP_REASON_INACTIVITY:
break;
case DHCP_REASON_TERMINATE:
break;
case DHCP_REASON_SIGNAL:
"down...");
break;
}
return (EXIT_SUCCESS);
}
/*
* drain_script(): event loop callback during shutdown
*
* input: eh_t *: unused
* void *: unused
* output: boolean_t: B_TRUE if event loop should exit; B_FALSE otherwise
*/
/* ARGSUSED */
{
if (shutdown_started == B_FALSE) {
}
return (script_count == 0);
}
/*
* accept_event(): accepts a new connection on the ipc socket and registers
* to receive its messages with the event handler
*
* input: iu_eh_t *: unused
* int: the file descriptor in the iu_eh_t * the connection came in on
* (other arguments unused)
* output: void
*/
/* ARGSUSED */
static void
{
int client_fd;
int is_priv;
return;
}
(void *)is_priv) == -1) {
"for callback");
}
}
/*
* ipc_event(): processes incoming ipc requests
*
* input: iu_eh_t *: unused
* int: the file descriptor in the iu_eh_t * the request came in on
* short: unused
* iu_event_id_t: event ID
* void *: indicates whether the request is from a privileged client
* output: void
*/
/* ARGSUSED */
static void
{
const char *ifname;
if (error != DHCP_IPC_SUCCESS) {
if (error != DHCP_IPC_E_EOF) {
"ipc_event: dhcp_ipc_recv_request failed: %s",
} else {
}
} else {
(void) dhcp_ipc_close(fd);
}
return;
}
/* Fill in temporary ipc_action structure for utility functions */
"ipc_event: invalid command (%s) attempted on %s",
return;
}
/* return EPERM for any of the privileged actions */
"ipc_event: privileged ipc command (%s) attempted on %s",
return;
}
/*
* Try to locate the state machine associated with this command. If
* the command is DHCP_START or DHCP_INFORM and there isn't a state
* machine already, make one (there may already be one from a previous
* failed attempt to START or INFORM). Otherwise, verify the reference
* is still valid.
*
* The interface name may be blank. In that case, we look up the
* primary interface, and the requested type (v4 or v6) doesn't matter.
*/
if (*ifname == '\0')
else
/* Note that verify_smach drops a reference */
if (!verify_smach(dsmp))
}
/*
* If the user asked for the primary DHCP interface, but there
* is none, then report failure.
*/
if (ifname[0] == '\0') {
/*
* If there's no interface, and we're starting up, then create
* it now, along with a state machine for it. Note that if
* insert_smach fails, it discards the LIF reference.
*/
/*
* Get client ID and set "DHCPRUNNING" flag on
* logical interface. (V4 only, because V6
* plumbs its own interfaces.)
*/
if (error == DHCP_IPC_SUCCESS)
if (error != DHCP_IPC_SUCCESS) {
}
}
/*
* Otherwise, this is an operation on an unknown interface.
*/
} else {
}
return;
}
}
return;
}
/*
* verify that the state machine is in a state which will allow the
* command. we do this up front so that we can return an error
* *before* needlessly cancelling an in-progress transaction.
*/
"in state %s; not allowing %s command on %s",
return;
}
/*
* The current design dictates that there can be only one outstanding
* transaction per state machine -- this simplifies the code
* considerably and also fits well with RFCs 2131 and 3315. It is
* worth classifying the different DHCP commands into synchronous
* (those which we will handle now and reply to immediately) and
* asynchronous (those which require transactions and will be completed
* at an indeterminate time in the future):
*
* DROP: removes the agent's management of a state machine.
* asynchronous as the script program may be invoked.
*
* PING: checks to see if the agent has a named state machine.
* synchronous, since no packets need to be sent
* to the DHCP server.
*
* STATUS: returns information about a state machine.
* synchronous, since no packets need to be sent
* to the DHCP server.
*
* RELEASE: releases the agent's management of a state machine
* and brings the associated interfaces down. asynchronous
* as the script program may be invoked.
*
* EXTEND: renews a lease. asynchronous, since the agent
* needs to wait for an ACK, etc.
*
* START: starts DHCP on a named state machine. asynchronous since
* the agent needs to wait for OFFERs, ACKs, etc.
*
* INFORM: obtains configuration parameters for the system using
* externally configured interface. asynchronous, since the
* agent needs to wait for an ACK.
*
* Notice that EXTEND, INFORM, START, DROP and RELEASE are
* asynchronous. Notice also that asynchronous commands may occur from
* within the agent -- for instance, the agent will need to do implicit
* EXTENDs to extend the lease. In order to make the code simpler, the
* following rules apply for asynchronous commands:
*
* There can only be one asynchronous command at a time per state
* machine. The current asynchronous command is managed by the async_*
* api: async_start(), async_finish(), and async_cancel().
* async_start() starts management of a new asynchronous command on an
* state machine, which should only be done after async_cancel() to
* terminate a previous command. When the command is completed,
* async_finish() should be called.
*
* Asynchronous commands started by a user command have an associated
* ipc_action which provides the agent with information for how to get
* in touch with the user command when the action completes. These
* ipc_action records also have an associated timeout which may be
* infinite. ipc_action_start() should be called when starting an
* asynchronous command requested by a user, which sets up the timer
* and keeps track of the ipc information (file descriptor, request
* type). When the asynchronous command completes, ipc_action_finish()
* should be called to return a command status code to the user and
* close the ipc connection). If the command does not complete before
* the timer fires, ipc_action_timeout() is called which closes the ipc
* connection and returns DHCP_IPC_E_TIMEOUT to the user. Note that
* independent of ipc_action_timeout(), ipc_action_finish() should be
* called.
*
* on a case-by-case basis, here is what happens (per state machine):
*
* o When an asynchronous command is requested, then
* async_cancel() is called to terminate any non-user
* action in progress. If there's a user action running,
* the user command is sent DHCP_IPC_E_PEND.
*
* o otherwise, the the transaction is started with
* async_start(). if the transaction is on behalf
* of a user, ipc_action_start() is called to keep
* track of the ipc information and set up the
* ipc_action timer.
*
* o if the command completes normally and before a
* timeout fires, then async_finish() is called.
* if there was an associated ipc_action,
* ipc_action_finish() is called to complete it.
*
* o if the command fails before a timeout fires, then
* async_finish() is called, and the state machine is
* is returned to a known state based on the command.
* if there was an associated ipc_action,
* ipc_action_finish() is called to complete it.
*
* o if the ipc_action timer fires before command
* completion, then DHCP_IPC_E_TIMEOUT is returned to
* the user. however, the transaction continues to
* be carried out asynchronously.
*/
/*
* Only immediate commands (ping, status, get_tag) need to
* worry about freeing ia through one of the reply functions
* before returning.
*/
} else {
/*
* if shutdown request has been received, send back an error.
*/
if (shutdown_started) {
return;
}
return;
}
return;
}
/* Action structure consumed by above function */
}
case DHCP_DROP:
break; /* not an immediate function */
case DHCP_EXTEND:
(void) dhcp_extending(dsmp);
break;
case DHCP_GET_TAG: {
/*
* verify the request makes sense.
*/
break;
}
sizeof (dhcp_optnum_t));
case DSYM_SITE: /* FALLTHRU */
case DSYM_STANDARD:
if (isv6) {
NULL);
} else {
}
break;
case DSYM_VENDOR:
if (isv6) {
/*
* Look through vendor options to find our
* enterprise number.
*/
for (;;) {
break;
continue;
sizeof (ent));
continue;
break;
}
/*
* Now find the requested vendor option
* within the vendor options block.
*/
}
} else {
/*
* the test against VS_OPTION_START is broken
* up into two tests to avoid compiler warnings
* under intel.
*/
}
break;
case DSYM_FIELD:
if (isv6) {
/* Validate the packet field the user wants */
if (d6m->d6m_msg_type ==
d6m->d6m_msg_type ==
if (optlen > sizeof (dhcpv6_relay_t))
break;
} else {
break;
}
}
} else {
break;
/*
* + 2 to account for option code and length
* byte
*/
}
}
return;
}
break;
default:
return;
}
/*
* return the option payload, if there was one. the "+ 2"
* accounts for the option code number and length byte.
*/
if (isv6) {
} else {
}
if (did_alloc)
break;
/*
* There wasn't any definition for the option in the
* current ack, so now retry with the original ack if
* the original ack is not the current ack.
*/
goto load_option;
}
/*
* note that an "okay" response is returned either in
* the case of an unknown option or a known option
* with no payload. this is okay (for now) since
* dhcpinfo checks whether an option is valid before
* ever performing ipc with the agent.
*/
break;
}
case DHCP_INFORM:
/* next destination: dhcp_acknak() */
break; /* not an immediate function */
case DHCP_PING:
else
break;
case DHCP_RELEASE:
break; /* not an immediate function */
case DHCP_START: {
/*
* if we have a valid hostconf lying around, then jump
* into INIT_REBOOT. if it fails, we'll end up going
* through the whole selecting() procedure again.
*/
/*
* If the allocation of the old ack fails, that's fine;
* continue without it.
*/
/*
* As long as we've allocated something, start using it.
*/
/* next destination: dhcp_acknak() */
break;
}
/*
* if not debugging, wait for a few seconds before
* going into SELECTING.
*/
/* next destination: dhcp_start() */
break;
} else {
/* next destination: dhcp_requesting() */
break;
}
}
case DHCP_STATUS: {
/*
* We return information on just the first lease as being
* representative of the lot. A better status mechanism is
* needed.
*/
} else {
}
sizeof (dhcp_status_t));
break;
}
}
}
/*
* check_rtm_addr(): determine if routing socket message matches interface
* address
*
* input: const struct if_msghdr *: pointer to routing socket message
* int: routing socket message length
* boolean_t: set to B_TRUE if IPv6
* const in6_addr_t *: pointer to IP address
* output: boolean_t: B_TRUE if address is a match
*/
static boolean_t
const in6_addr_t *addr)
{
return (B_FALSE);
/* LINTED: alignment */
return (B_FALSE);
case AF_INET:
cp += sizeof (struct sockaddr_in);
break;
case AF_LINK:
cp += sizeof (struct sockaddr_dl);
break;
case AF_INET6:
cp += sizeof (struct sockaddr_in6);
break;
default:
break;
}
}
}
if (isv6) {
const struct sockaddr_in6 *sin6;
/* LINTED: alignment */
return (B_FALSE);
return (B_FALSE);
} else {
const struct sockaddr_in *sinp;
/* LINTED: alignment */
return (B_FALSE);
return (B_FALSE);
}
}
/*
* is_rtm_v6(): determine if routing socket message is IPv6
*
* input: struct ifa_msghdr *: pointer to routing socket message
* int: message length
* output: boolean_t
*/
static boolean_t
{
/* LINTED: alignment */
return (B_FALSE);
case AF_INET:
return (B_FALSE);
case AF_LINK:
cp += sizeof (struct sockaddr_dl);
break;
case AF_INET6:
return (B_TRUE);
default:
break;
}
}
return (B_FALSE);
}
/*
* check_lif(): check the state of a given logical interface and its DHCP
* lease. We've been told by the routing socket that the
* corresponding ifIndex has changed. This may mean that DAD has
* completed or failed.
*
* input: dhcp_lif_t *: pointer to the LIF
* const struct ifa_msghdr *: routing socket message
* int: size of routing socket message
* output: boolean_t: B_TRUE if DAD has completed on this interface
*/
static boolean_t
{
int fd;
/*
* Get the real (64 bit) logical interface flags. Note that the
* routing socket message has flags, but these are just the lower 32
* bits.
*/
/*
* Failing to retrieve flags means that the interface is gone.
* It hasn't failed to verify with DAD, but we still have to
* give up on it.
*/
lifr.lifr_flags = 0;
if (!isv6)
} else {
"unable to retrieve interface flags on %s",
}
/*
* If the message is not about this logical interface,
* then just ignore it.
*/
return (B_FALSE);
}
if (dad_wait) {
}
if (unplumb)
return (dad_wait);
}
/*
* check_main_lif(): check the state of a main logical interface for a state
* machine. This is used only for DHCPv6.
*
* input: dhcp_smach_t *: pointer to the state machine
* const struct ifa_msghdr *: routing socket message
* int: size of routing socket message
* output: boolean_t: B_TRUE if LIF is ok.
*/
static boolean_t
{
/*
* Get the real (64 bit) logical interface flags. Note that the
* routing socket message has flags, but these are just the lower 32
* bits.
*/
/*
* Failing to retrieve flags means that the interface is gone.
* Our state machine is now trash.
*/
} else {
"unable to retrieve interface flags on %s",
}
return (B_FALSE);
/*
* If the message is not about this logical interface,
* then just ignore it.
*/
return (B_TRUE);
return (B_FALSE);
} else {
return (B_TRUE);
}
}
/*
* transitions; must go through INIT_REBOOT state if
* the link flaps.
*
* input: dhcp_pif_t *: pointer to the physical interface to check
* const struct if_msghdr *: routing socket message
* output: none
*/
static void
{
int fd;
/*
* If the message implies no change of flags, then we're done; no need
* to check further. Note that if we have multiple state machines on a
* single physical interface, this test keeps us from issuing an ioctl
* for each one.
*/
return;
/*
* We don't know what the real interface flags are, because the
* if_index number is only 16 bits; we must go ask.
*/
/*
* If we've lost the interface or it has gone down, then
* nothing special to do; just turn off the running flag.
*/
} else {
/*
* Interface has come back up: go through verification process.
*/
}
}
/*
* rtsock_event(): fetches routing socket messages and updates internal
* interface state based on those messages.
*
* input: iu_eh_t *: unused
* int: the routing socket file descriptor
* (other arguments unused)
* output: void
*/
/* ARGSUSED */
static void
{
union {
struct ifa_msghdr ifam;
char buf[1024];
} msg;
int msglen;
return;
/* Note that the routing socket interface index is just 16 bits */
} else {
return;
}
/*
* Note that script_start can call dhcp_drop directly, and
* that will do release_smach.
*/
/*
* physical interface basis.
*/
continue;
}
/*
* Since we cannot trust the flags reported by the routing
* socket (they're just 32 bits -- and thus never include
* IFF_DUPLICATE), and we can't trust the ifindex (it's only 16
* bits and also doesn't reflect the alias in use), we get
* flags on all matching interfaces, and go by that.
*/
dsmp->dsm_lif_wait--;
}
}
}
}
NULL);
continue;
}
/*
* Ignore this state machine if nothing interesting has
* happened.
*/
continue;
/*
* If we're still waiting for DAD to complete on some of the
* configured LIFs, then don't send a response.
*/
if (dsmp->dsm_lif_wait != 0) {
dsmp->dsm_lif_wait);
continue;
}
/*
* If we have some failed LIFs, then handle them now. We'll
* remove them from the list. Any leases that become empty are
* also removed as part of the decline-generation process.
*/
if (dsmp->dsm_lif_down != 0)
dsmp->dsm_bad_offers++;
/*
* For DHCPv6, we'll process the restart once we're
* done sending Decline messages, because these are
* supposed to be acknowledged. With DHCPv4, there's
* no acknowledgment for a DECLINE, so after sending
* it, we just restart right away.
*/
}
} else {
/*
* If we're now up on at least some of the leases and
* we were waiting for that, then kick off the rest of
* configuration. Lease validation and DAD are done.
*/
}
}
}
/*
* check_cmd_allowed(): check whether the requested command is allowed in the
* state specified.
*
* input: DHCPSTATE: current state
* dhcp_ipc_type_t: requested command
* output: boolean_t: B_TRUE if command is allowed in this state
*/
{
}