select.c revision f4b3ec61df05330d25f55a36b975b4d7519fdeb1
/*
* 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
* 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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* SELECTING state of the client state machine.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/types.h>
#include <stdio.h>
#include <strings.h>
#include <time.h>
#include <limits.h>
#include <netinet/in.h>
#include <net/route.h>
#include <net/if.h>
#include <netinet/dhcp.h>
#include <netinet/udp.h>
#include <netinet/ip_var.h>
#include <netinet/udp_var.h>
#include <dhcpmsg.h>
#include "states.h"
#include "agent.h"
#include "util.h"
#include "interface.h"
#include "packet.h"
#include "defaults.h"
static stop_func_t stop_selecting;
/*
* dhcp_start(): starts DHCP on a state machine
*
* input: iu_tq_t *: unused
* void *: the state machine on which to start DHCP
* output: void
*/
/* ARGSUSED */
void
dhcp_start(iu_tq_t *tqp, void *arg)
{
dhcp_smach_t *dsmp = arg;
release_smach(dsmp);
dhcpmsg(MSG_VERBOSE, "starting DHCP on %s", dsmp->dsm_name);
dhcp_selecting(dsmp);
}
/*
* dhcp_selecting(): sends a DISCOVER and sets up reception of OFFERs for
* IPv4, or sends a Solicit and sets up reception of
* Advertisements for DHCPv6.
*
* input: dhcp_smach_t *: the state machine on which to send the DISCOVER
* output: void
*/
void
dhcp_selecting(dhcp_smach_t *dsmp)
{
dhcp_pkt_t *dpkt;
const char *reqhost;
char hostfile[PATH_MAX + 1];
/*
* We first set up to collect OFFER/Advertise packets as they arrive.
* We then send out DISCOVER/Solicit probes. Then we wait a
* user-tunable number of seconds before seeing if OFFERs/
* Advertisements have come in response to our DISCOVER/Solicit. If
* none have come in, we continue to wait, sending out our DISCOVER/
* Solicit probes with exponential backoff. If no OFFER/Advertisement
* is ever received, we will wait forever (note that since we're
* event-driven though, we're still able to service other state
* machines).
*
* Note that we do an reset_smach() here because we may be landing in
* dhcp_selecting() as a result of restarting DHCP, so the state
* machine may not be fresh.
*/
reset_smach(dsmp);
if (!set_smach_state(dsmp, SELECTING)) {
dhcpmsg(MSG_ERROR,
"dhcp_selecting: cannot switch to SELECTING state; "
"reverting to INIT on %s", dsmp->dsm_name);
goto failed;
}
dsmp->dsm_offer_timer = iu_schedule_timer(tq,
dsmp->dsm_offer_wait, dhcp_requesting, dsmp);
if (dsmp->dsm_offer_timer == -1) {
dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read "
"%s packets", dsmp->dsm_isv6 ? "Advertise" : "OFFER");
goto failed;
}
hold_smach(dsmp);
/*
* Assemble and send the DHCPDISCOVER or Solicit message.
*
* If this fails, we'll wait for the select timer to go off
* before trying again.
*/
if (dsmp->dsm_isv6) {
dhcpv6_ia_na_t d6in;
if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_SOLICIT)) == NULL) {
dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up "
"Solicit packet");
return;
}
/* Add an IA_NA option for our controlling LIF */
d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid);
d6in.d6in_t1 = htonl(0);
d6in.d6in_t2 = htonl(0);
(void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA,
(dhcpv6_option_t *)&d6in + 1,
sizeof (d6in) - sizeof (dhcpv6_option_t));
/* Option Request option for desired information */
(void) add_pkt_prl(dpkt, dsmp);
/* Enable Rapid-Commit */
(void) add_pkt_opt(dpkt, DHCPV6_OPT_RAPID_COMMIT, NULL, 0);
/* xxx add Reconfigure Accept */
(void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers,
stop_selecting, DHCPV6_SOL_TIMEOUT, DHCPV6_SOL_MAX_RT);
} else {
if ((dpkt = init_pkt(dsmp, DISCOVER)) == NULL) {
dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up "
"DISCOVER packet");
return;
}
/*
* The max DHCP message size option is set to the interface
* MTU, minus the size of the UDP and IP headers.
*/
(void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE,
htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr)));
(void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
if (class_id_len != 0) {
(void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id,
class_id_len);
}
(void) add_pkt_prl(dpkt, dsmp);
if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6,
DF_REQUEST_HOSTNAME)) {
dhcpmsg(MSG_DEBUG,
"dhcp_selecting: DF_REQUEST_HOSTNAME");
(void) snprintf(hostfile, sizeof (hostfile),
"/etc/hostname.%s", dsmp->dsm_name);
if ((reqhost = iffile_to_hostname(hostfile)) != NULL) {
dhcpmsg(MSG_DEBUG, "dhcp_selecting: host %s",
reqhost);
dsmp->dsm_reqhost = strdup(reqhost);
if (dsmp->dsm_reqhost != NULL)
(void) add_pkt_opt(dpkt, CD_HOSTNAME,
dsmp->dsm_reqhost,
strlen(dsmp->dsm_reqhost));
else
dhcpmsg(MSG_WARNING,
"dhcp_selecting: cannot allocate "
"memory for host name option");
}
}
(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
(void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST),
stop_selecting);
}
return;
failed:
(void) set_smach_state(dsmp, INIT);
dsmp->dsm_dflags |= DHCP_IF_FAILED;
ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY);
}
/*
* dhcp_collect_dlpi(): collects incoming OFFERs, ACKs, and NAKs via DLPI.
*
* input: iu_eh_t *: unused
* int: the file descriptor the mesage arrived on
* short: unused
* iu_event_id_t: the id of this event callback with the handler
* void *: the physical interface that received the message
* output: void
*/
/* ARGSUSED */
void
dhcp_collect_dlpi(iu_eh_t *eh, int fd, short events, iu_event_id_t id,
void *arg)
{
dhcp_pif_t *pif = arg;
PKT_LIST *plp;
uchar_t recv_type;
const char *pname;
dhcp_smach_t *dsmp;
uint_t xid;
if ((plp = recv_pkt(fd, pif->pif_max, B_FALSE, B_TRUE)) == NULL)
return;
recv_type = pkt_recv_type(plp);
pname = pkt_type_to_string(recv_type, B_FALSE);
/*
* DHCP_PUNTYPED messages are BOOTP server responses.
*/
if (!pkt_v4_match(recv_type,
DHCP_PACK | DHCP_PNAK | DHCP_POFFER | DHCP_PUNTYPED)) {
dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignored %s packet "
"received via DLPI on %s", pname, pif->pif_name);
free_pkt_entry(plp);
return;
}
/*
* Loop through the state machines that match on XID to find one that's
* interested in this offer. If there are none, then discard.
*/
xid = pkt_get_xid(plp->pkt, B_FALSE);
for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL;
dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) {
/*
* Find state machine on correct interface.
*/
if (dsmp->dsm_lif->lif_pif == pif)
break;
}
if (dsmp == NULL) {
dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: no matching state "
"machine for %s packet XID %#x received via DLPI on %s",
pname, xid, pif->pif_name);
free_pkt_entry(plp);
return;
}
/*
* Ignore state machines that aren't looking for DLPI messages.
*/
if (!dsmp->dsm_using_dlpi) {
dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignore state "
"machine for %s packet XID %#x received via DLPI on %s",
pname, xid, pif->pif_name);
free_pkt_entry(plp);
return;
}
if (pkt_v4_match(recv_type, DHCP_PACK)) {
if (!dhcp_bound(dsmp, plp)) {
dhcpmsg(MSG_WARNING, "dhcp_collect_dlpi: dhcp_bound "
"failed for %s", dsmp->dsm_name);
dhcp_restart(dsmp);
return;
}
dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: %s on %s",
pname, dsmp->dsm_name);
} else if (pkt_v4_match(recv_type, DHCP_PNAK)) {
free_pkt_entry(plp);
dhcp_restart(dsmp);
} else {
pkt_smach_enqueue(dsmp, plp);
}
}
/*
* stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when
* abandoning the state machine. For DHCPv6, this timer may
* go off before the offer wait timer. If so, then this is a
* good time to check for valid Advertisements, so cancel the
* timer and go check.
*
* input: dhcp_smach_t *: the state machine DISCOVERs are being sent on
* unsigned int: the number of DISCOVERs sent so far
* output: boolean_t: B_TRUE if retransmissions should stop
*/
/* ARGSUSED1 */
static boolean_t
stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers)
{
/*
* If we're using v4 and the underlying LIF we're trying to configure
* has been touched by the user, then bail out.
*/
if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) {
finished_smach(dsmp, DHCP_IPC_E_UNKIF);
return (B_TRUE);
}
if (dsmp->dsm_recv_pkt_list != NULL) {
dhcp_requesting(NULL, dsmp);
if (dsmp->dsm_state != SELECTING)
return (B_TRUE);
}
return (B_FALSE);
}