agent.c revision dc041e83f8b943874ea814b873eaa8ee53498cf2
/*
* 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 2006 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 <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"
#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 interface 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, 0, 1, 1, 1 },
/* INIT_REBOOT */ { 1, 0, 1, 0, 1, 1, 0, 0 },
/* ADOPTING */ { 1, 0, 1, 0, 0, 1, 0, 0 },
/* INFORM_SENT */ { 1, 0, 1, 0, 0, 1, 1, 0 }
};
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);
}
(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 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.
*/
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() == 0)
return (EXIT_FAILURE);
/*
* 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: unused
* void *: indicates whether the request is from a privileged client
* output: void
*/
/* ARGSUSED */
static void
{
(void) dhcp_ipc_close(fd);
return;
}
return;
}
/* return EPERM for any of the privileged actions */
if (!is_priv) {
switch (cmd) {
case DHCP_STATUS:
case DHCP_PING:
case DHCP_GET_TAG:
break;
default:
"command (%i) attempted on %s", cmd,
return;
}
}
/*
* try to locate the ifs associated with this command. if the
* command is DHCP_START or DHCP_INFORM, then if there isn't
* an ifs already, make one (there may already be one from a
* previous failed attempt to START or INFORM). otherwise,
* verify the interface is still valid.
*/
switch (cmd) {
case DHCP_START: /* FALLTHRU */
case DHCP_INFORM:
/*
* it's possible that the interface already exists, but
* has been abandoned. usually in those cases we should
* return DHCP_IPC_E_UNKIF, but that makes little sense
* in the case of "start" or "inform", so just ignore
* the abandoned interface and start over anew.
*/
/*
* as part of initializing the ifs, insert_ifs()
* creates a DLPI stream at ifsp->if_dlpi_fd.
*/
return;
}
}
break;
default:
else
return;
}
break;
}
if (verify_ifs(ifsp) == 0) {
return;
}
switch (cmd) {
case DHCP_EXTEND:
case DHCP_RELEASE:
case DHCP_INFORM:
return;
default:
break;
}
}
/*
* verify that the interface 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.
*/
return;
}
}
/*
* current design dictates that there can be only one
* outstanding transaction per interface -- this simplifies
* the code considerably and also fits well with RFC2131.
* it is worth classifying the different DHCP commands into
* synchronous (those which we will handle now and be done
* with) and asynchronous (those which require transactions
* and will be completed at an indeterminate time in the
* future):
*
* DROP: removes the agent's management of an interface.
* asynchronous as the script program may be invoked.
*
* PING: checks to see if the agent controls an interface.
* synchronous, since no packets need to be sent
* to the DHCP server.
*
* STATUS: returns information about the an interface.
* synchronous, since no packets need to be sent
* to the DHCP server.
*
* RELEASE: releases the agent's management of an interface
* and brings the interface 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 an interface. asynchronous since
* the agent needs to wait for OFFERs, ACKs, etc.
*
* INFORM: obtains configuration parameters for an 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
* interface. the current asynchronous command is managed by
* the async_* api: async_start(), async_finish(),
* async_timeout(), async_cancel(), and async_pending().
* async_start() starts management of a new asynchronous
* command on an interface, which should only be done after
* async_pending() is called to check that there are no
* pending asynchronous commands on that interface. when the
* command is completed, async_finish() should be called. all
* asynchronous commands have an associated timer, which calls
* async_timeout() when it times out. if async_timeout()
* decides that the asynchronous command should be cancelled
* (see below), it calls async_cancel() to attempt
* cancellation.
*
* 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 interface):
*
* o when an asynchronous command is requested, then
* async_pending() is called to see if there is already
* an asynchronous event. if so, the command does not
* proceed, and if there is an associated ipc_action,
* 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 interface 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.
*
* o if async_timeout() fires before command completion,
* then if the command was internal to the agent, it
* is cancelled. otherwise, if it was a user command,
* then if the user is still waiting for the command
* to complete, the command continues and async_timeout()
* is rescheduled.
*/
switch (cmd) {
case DHCP_DROP: /* FALLTHRU */
case DHCP_RELEASE: /* FALLTHRU */
case DHCP_EXTEND: /* FALLTHRU */
case DHCP_INFORM: /* FALLTHRU */
case DHCP_START:
/*
* if shutdown request has been received, send back an error.
*/
if (shutdown_started) {
return;
}
if (async_pending(ifsp)) {
return;
}
return;
}
return;
}
break;
default:
break;
}
switch (cmd) {
case DHCP_DROP:
return;
case DHCP_EXTEND:
(void) dhcp_extending(ifsp);
break;
case DHCP_GET_TAG: {
/*
* verify the request makes sense.
*/
return;
}
case DSYM_SITE: /* FALLTHRU */
case DSYM_STANDARD:
break;
case DSYM_VENDOR:
/*
* the test against VS_OPTION_START is broken up into
* two tests to avoid compiler warnings under intel.
*/
break;
case DSYM_FIELD:
break;
/* + 2 to account for option code and length byte */
&fd);
return;
}
break;
default:
return;
}
/*
* return the option payload, if there was one. the "+ 2"
* accounts for the option code number and length byte.
*/
if (did_alloc)
return;
/*
* 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.
*/
return;
}
case DHCP_INFORM:
/* next destination: dhcp_acknak() */
return;
case DHCP_PING:
else
return;
case DHCP_RELEASE:
"Finished with lease.", NULL);
return;
case DHCP_START:
(void) canonize_ifs(ifsp);
/*
* 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 (error != -1) {
if (error > 1) {
/*
* Return indicated we had more than one packet
* second one is the original ack. Older
* versions of the agent wrote only one ack
* to the file, we now keep both the first
* ack as well as the last one.
*/
}
/* next destination: dhcp_acknak() */
return;
}
/*
* if not debugging, wait for a few seconds before
* going into SELECTING.
*/
if (debug_level == 0) {
if (iu_schedule_timer_ms(tq,
!= -1) {
/* next destination: dhcp_start() */
return;
}
}
/* next destination: dhcp_requesting() */
return;
case DHCP_STATUS: {
} else {
}
sizeof (dhcp_status_t));
return;
}
default:
return;
}
}
/*
* check_rtm_addr(): determine if routing socket message matches interface
* address
*
* input: struct if_msghdr *: pointer to routing socket message
* struct in_addr: IP address
* output: boolean_t
*/
static boolean_t
{
struct sockaddr_in *sinp;
return (B_FALSE);
/* LINTED: alignment */
return (B_FALSE);
case AF_UNIX:
cp += sizeof (struct sockaddr_un);
break;
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;
}
}
}
/* LINTED: alignment */
return (B_FALSE);
}
/*
* 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;
char *fail;
int msglen;
return;
/*
* These are the messages that can identify a particular logical
* interface by local IP address.
*/
return;
/* Note that ifam_index is just 16 bits */
/*
* The if_sock_ip_fd is set to a non-negative integer by
* configure_bound(). If it's negative, then DHCP doesn't
* think we're bound.
*
* For pre-bound interfaces, we want to check to see if the
* IFF_UP bit has been reported. This means that DAD is
* complete.
*/
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.
*/
fail = "unable to retrieve interface flags on %s";
lifr.lifr_flags = 0;
/*
* If the message is not about this logical interface,
* then just ignore it.
*/
continue;
fail = "interface %s has duplicate address";
} else {
/*
* If we're now up and we were waiting for that, then
* kick off this interface. DAD is done.
*/
}
continue;
}
}
/*
* The binding has evidently failed, so it's as though it never
* happened. We need to do switch back to PRE_BOUND state so
* that send_pkt_internal() uses DLPI instead of sockets. Our
* logical interface has already been torn down by the kernel,
* and thus we can't send DHCPDECLINE by way of regular IP.
* (Unless we're adopting -- allow the grandparent to be
* handled as expected.)
*/
ifs->if_bad_offers++;
}
}