xref: /onnv-gate/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c (revision 3448:aaf16568054b)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
52157Sdh155122  * Common Development and Distribution License (the "License").
62157Sdh155122  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate /*
223431Scarlsonj  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
230Sstevel@tonic-gate  * Use is subject to license terms.
240Sstevel@tonic-gate  *
250Sstevel@tonic-gate  * SELECTING state of the client state machine.
260Sstevel@tonic-gate  */
270Sstevel@tonic-gate 
280Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
290Sstevel@tonic-gate 
300Sstevel@tonic-gate #include <sys/types.h>
310Sstevel@tonic-gate #include <stdio.h>
320Sstevel@tonic-gate #include <strings.h>
330Sstevel@tonic-gate #include <time.h>
340Sstevel@tonic-gate #include <limits.h>
350Sstevel@tonic-gate #include <netinet/in.h>
360Sstevel@tonic-gate #include <net/route.h>
370Sstevel@tonic-gate #include <net/if.h>
380Sstevel@tonic-gate #include <netinet/dhcp.h>
390Sstevel@tonic-gate #include <netinet/udp.h>
400Sstevel@tonic-gate #include <netinet/ip_var.h>
410Sstevel@tonic-gate #include <netinet/udp_var.h>
420Sstevel@tonic-gate #include <dhcpmsg.h>
430Sstevel@tonic-gate 
440Sstevel@tonic-gate #include "states.h"
450Sstevel@tonic-gate #include "agent.h"
460Sstevel@tonic-gate #include "util.h"
470Sstevel@tonic-gate #include "interface.h"
480Sstevel@tonic-gate #include "packet.h"
490Sstevel@tonic-gate #include "defaults.h"
500Sstevel@tonic-gate 
510Sstevel@tonic-gate static stop_func_t	stop_selecting;
520Sstevel@tonic-gate 
530Sstevel@tonic-gate /*
543431Scarlsonj  * dhcp_start(): starts DHCP on a state machine
550Sstevel@tonic-gate  *
560Sstevel@tonic-gate  *   input: iu_tq_t *: unused
573431Scarlsonj  *	    void *: the state machine on which to start DHCP
580Sstevel@tonic-gate  *  output: void
590Sstevel@tonic-gate  */
600Sstevel@tonic-gate 
610Sstevel@tonic-gate /* ARGSUSED */
620Sstevel@tonic-gate void
630Sstevel@tonic-gate dhcp_start(iu_tq_t *tqp, void *arg)
640Sstevel@tonic-gate {
653431Scarlsonj 	dhcp_smach_t	*dsmp = arg;
660Sstevel@tonic-gate 
673431Scarlsonj 	release_smach(dsmp);
680Sstevel@tonic-gate 
693431Scarlsonj 	dhcpmsg(MSG_VERBOSE, "starting DHCP on %s", dsmp->dsm_name);
703431Scarlsonj 	dhcp_selecting(dsmp);
710Sstevel@tonic-gate }
720Sstevel@tonic-gate 
730Sstevel@tonic-gate /*
743431Scarlsonj  * dhcp_selecting(): sends a DISCOVER and sets up reception of OFFERs for
753431Scarlsonj  *		     IPv4, or sends a Solicit and sets up reception of
763431Scarlsonj  *		     Advertisements for DHCPv6.
770Sstevel@tonic-gate  *
783431Scarlsonj  *   input: dhcp_smach_t *: the state machine on which to send the DISCOVER
790Sstevel@tonic-gate  *  output: void
800Sstevel@tonic-gate  */
810Sstevel@tonic-gate 
820Sstevel@tonic-gate void
833431Scarlsonj dhcp_selecting(dhcp_smach_t *dsmp)
840Sstevel@tonic-gate {
850Sstevel@tonic-gate 	dhcp_pkt_t		*dpkt;
860Sstevel@tonic-gate 	const char		*reqhost;
870Sstevel@tonic-gate 	char			hostfile[PATH_MAX + 1];
880Sstevel@tonic-gate 
890Sstevel@tonic-gate 	/*
903431Scarlsonj 	 * We first set up to collect OFFER/Advertise packets as they arrive.
913431Scarlsonj 	 * We then send out DISCOVER/Solicit probes.  Then we wait a
923431Scarlsonj 	 * user-tunable number of seconds before seeing if OFFERs/
933431Scarlsonj 	 * Advertisements have come in response to our DISCOVER/Solicit.  If
943431Scarlsonj 	 * none have come in, we continue to wait, sending out our DISCOVER/
953431Scarlsonj 	 * Solicit probes with exponential backoff.  If no OFFER/Advertisement
963431Scarlsonj 	 * is ever received, we will wait forever (note that since we're
973431Scarlsonj 	 * event-driven though, we're still able to service other state
983431Scarlsonj 	 * machines).
990Sstevel@tonic-gate 	 *
1003431Scarlsonj 	 * Note that we do an reset_smach() here because we may be landing in
1013431Scarlsonj 	 * dhcp_selecting() as a result of restarting DHCP, so the state
1023431Scarlsonj 	 * machine may not be fresh.
1030Sstevel@tonic-gate 	 */
1040Sstevel@tonic-gate 
1053431Scarlsonj 	reset_smach(dsmp);
1063431Scarlsonj 	if (!set_smach_state(dsmp, SELECTING)) {
1073431Scarlsonj 		dhcpmsg(MSG_ERROR,
1083431Scarlsonj 		    "dhcp_selecting: cannot switch to SELECTING state; "
1093431Scarlsonj 		    "reverting to INIT on %s", dsmp->dsm_name);
1103431Scarlsonj 		goto failed;
1110Sstevel@tonic-gate 
1123431Scarlsonj 	}
1130Sstevel@tonic-gate 
1143431Scarlsonj 	dsmp->dsm_offer_timer = iu_schedule_timer(tq,
1153431Scarlsonj 	    dsmp->dsm_offer_wait, dhcp_requesting, dsmp);
1163431Scarlsonj 	if (dsmp->dsm_offer_timer == -1) {
1173431Scarlsonj 		dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read "
1183431Scarlsonj 		    "%s packets", dsmp->dsm_isv6 ? "Advertise" : "OFFER");
1193431Scarlsonj 		goto failed;
1203431Scarlsonj 	}
1210Sstevel@tonic-gate 
1223431Scarlsonj 	hold_smach(dsmp);
1230Sstevel@tonic-gate 
1243431Scarlsonj 	/*
1253431Scarlsonj 	 * Assemble and send the DHCPDISCOVER or Solicit message.
1263431Scarlsonj 	 *
1273431Scarlsonj 	 * If this fails, we'll wait for the select timer to go off
1283431Scarlsonj 	 * before trying again.
1293431Scarlsonj 	 */
1303431Scarlsonj 	if (dsmp->dsm_isv6) {
1313431Scarlsonj 		dhcpv6_ia_na_t d6in;
1320Sstevel@tonic-gate 
1333431Scarlsonj 		if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_SOLICIT)) == NULL) {
1343431Scarlsonj 			dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up "
1353431Scarlsonj 			    "Solicit packet");
1363431Scarlsonj 			return;
1370Sstevel@tonic-gate 		}
1380Sstevel@tonic-gate 
1393431Scarlsonj 		/* Add an IA_NA option for our controlling LIF */
1403431Scarlsonj 		d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid);
1413431Scarlsonj 		d6in.d6in_t1 = htonl(0);
1423431Scarlsonj 		d6in.d6in_t2 = htonl(0);
1433431Scarlsonj 		(void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA,
1443431Scarlsonj 		    (dhcpv6_option_t *)&d6in + 1,
1453431Scarlsonj 		    sizeof (d6in) - sizeof (dhcpv6_option_t));
1463431Scarlsonj 
1473431Scarlsonj 		/* Option Request option for desired information */
1483431Scarlsonj 		(void) add_pkt_prl(dpkt, dsmp);
1493431Scarlsonj 
1503431Scarlsonj 		/* Enable Rapid-Commit */
1513431Scarlsonj 		(void) add_pkt_opt(dpkt, DHCPV6_OPT_RAPID_COMMIT, NULL, 0);
1523431Scarlsonj 
1533431Scarlsonj 		/* xxx add Reconfigure Accept */
1540Sstevel@tonic-gate 
1553431Scarlsonj 		(void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers,
1563431Scarlsonj 		    stop_selecting, DHCPV6_SOL_TIMEOUT, DHCPV6_SOL_MAX_RT);
1573431Scarlsonj 	} else {
1583431Scarlsonj 		if ((dpkt = init_pkt(dsmp, DISCOVER)) == NULL) {
1593431Scarlsonj 			dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up "
1603431Scarlsonj 			    "DISCOVER packet");
1613431Scarlsonj 			return;
1623431Scarlsonj 		}
1630Sstevel@tonic-gate 
1643431Scarlsonj 		/*
1653431Scarlsonj 		 * The max DHCP message size option is set to the interface
1663431Scarlsonj 		 * MTU, minus the size of the UDP and IP headers.
1673431Scarlsonj 		 */
1683431Scarlsonj 		(void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE,
1693431Scarlsonj 		    htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr)));
1703431Scarlsonj 		(void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
1710Sstevel@tonic-gate 
172*3448Sdh155122 		if (class_id_len != 0) {
173*3448Sdh155122 			(void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id,
174*3448Sdh155122 			    class_id_len);
175*3448Sdh155122 		}
1763431Scarlsonj 		(void) add_pkt_prl(dpkt, dsmp);
1770Sstevel@tonic-gate 
1783431Scarlsonj 		if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6,
1793431Scarlsonj 		    DF_REQUEST_HOSTNAME)) {
1803431Scarlsonj 			dhcpmsg(MSG_DEBUG,
1813431Scarlsonj 			    "dhcp_selecting: DF_REQUEST_HOSTNAME");
1823431Scarlsonj 			(void) snprintf(hostfile, sizeof (hostfile),
1833431Scarlsonj 			    "/etc/hostname.%s", dsmp->dsm_name);
1840Sstevel@tonic-gate 
1853431Scarlsonj 			if ((reqhost = iffile_to_hostname(hostfile)) != NULL) {
1863431Scarlsonj 				dhcpmsg(MSG_DEBUG, "dhcp_selecting: host %s",
1873431Scarlsonj 				    reqhost);
1883431Scarlsonj 				dsmp->dsm_reqhost = strdup(reqhost);
1893431Scarlsonj 				if (dsmp->dsm_reqhost != NULL)
1903431Scarlsonj 					(void) add_pkt_opt(dpkt, CD_HOSTNAME,
1913431Scarlsonj 					    dsmp->dsm_reqhost,
1923431Scarlsonj 					    strlen(dsmp->dsm_reqhost));
1933431Scarlsonj 				else
1943431Scarlsonj 					dhcpmsg(MSG_WARNING,
1953431Scarlsonj 					    "dhcp_selecting: cannot allocate "
1963431Scarlsonj 					    "memory for host name option");
1973431Scarlsonj 			}
1980Sstevel@tonic-gate 		}
1993431Scarlsonj 		(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
2003431Scarlsonj 
2013431Scarlsonj 		(void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST),
2023431Scarlsonj 		    stop_selecting);
2030Sstevel@tonic-gate 	}
2043431Scarlsonj 	return;
2050Sstevel@tonic-gate 
2063431Scarlsonj failed:
2073431Scarlsonj 	(void) set_smach_state(dsmp, INIT);
2083431Scarlsonj 	dsmp->dsm_dflags |= DHCP_IF_FAILED;
2093431Scarlsonj 	ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY);
2100Sstevel@tonic-gate }
2110Sstevel@tonic-gate 
2120Sstevel@tonic-gate /*
2133431Scarlsonj  * dhcp_collect_dlpi(): collects incoming OFFERs, ACKs, and NAKs via DLPI.
2140Sstevel@tonic-gate  *
2150Sstevel@tonic-gate  *   input: iu_eh_t *: unused
2163431Scarlsonj  *	    int: the file descriptor the mesage arrived on
2170Sstevel@tonic-gate  *	    short: unused
2180Sstevel@tonic-gate  *	    iu_event_id_t: the id of this event callback with the handler
2193431Scarlsonj  *	    void *: the physical interface that received the message
2200Sstevel@tonic-gate  *  output: void
2210Sstevel@tonic-gate  */
2220Sstevel@tonic-gate 
2230Sstevel@tonic-gate /* ARGSUSED */
2243431Scarlsonj void
2253431Scarlsonj dhcp_collect_dlpi(iu_eh_t *eh, int fd, short events, iu_event_id_t id,
2260Sstevel@tonic-gate     void *arg)
2270Sstevel@tonic-gate {
2283431Scarlsonj 	dhcp_pif_t	*pif = arg;
2293431Scarlsonj 	PKT_LIST	*plp;
2303431Scarlsonj 	uchar_t		recv_type;
2313431Scarlsonj 	const char	*pname;
2323431Scarlsonj 	dhcp_smach_t	*dsmp;
2333431Scarlsonj 	uint_t		xid;
2340Sstevel@tonic-gate 
2353431Scarlsonj 	if ((plp = recv_pkt(fd, pif->pif_max, B_FALSE, B_TRUE)) == NULL)
2360Sstevel@tonic-gate 		return;
2373431Scarlsonj 
2383431Scarlsonj 	recv_type = pkt_recv_type(plp);
2393431Scarlsonj 	pname = pkt_type_to_string(recv_type, B_FALSE);
2400Sstevel@tonic-gate 
2410Sstevel@tonic-gate 	/*
2420Sstevel@tonic-gate 	 * DHCP_PUNTYPED messages are BOOTP server responses.
2430Sstevel@tonic-gate 	 */
2443431Scarlsonj 	if (!pkt_v4_match(recv_type,
2453431Scarlsonj 	    DHCP_PACK | DHCP_PNAK | DHCP_POFFER | DHCP_PUNTYPED)) {
2463431Scarlsonj 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignored %s packet "
2473431Scarlsonj 		    "received via DLPI on %s", pname, pif->pif_name);
2483431Scarlsonj 		free_pkt_entry(plp);
2493431Scarlsonj 		return;
2503431Scarlsonj 	}
2510Sstevel@tonic-gate 
2523431Scarlsonj 	/*
2533431Scarlsonj 	 * Loop through the state machines that match on XID to find one that's
2543431Scarlsonj 	 * interested in this offer.  If there are none, then discard.
2553431Scarlsonj 	 */
2563431Scarlsonj 	xid = pkt_get_xid(plp->pkt, B_FALSE);
2573431Scarlsonj 	for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL;
2583431Scarlsonj 	    dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) {
2593431Scarlsonj 
2603431Scarlsonj 		/*
2613431Scarlsonj 		 * Find state machine on correct interface.
2623431Scarlsonj 		 */
2633431Scarlsonj 		if (dsmp->dsm_lif->lif_pif == pif)
2643431Scarlsonj 			break;
2653431Scarlsonj 	}
2663431Scarlsonj 
2673431Scarlsonj 	if (dsmp == NULL) {
2683431Scarlsonj 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: no matching state "
2693431Scarlsonj 		    "machine for %s packet XID %#x received via DLPI on %s",
2703431Scarlsonj 		    pname, xid, pif->pif_name);
2713431Scarlsonj 		free_pkt_entry(plp);
2723431Scarlsonj 		return;
2733431Scarlsonj 	}
2743431Scarlsonj 
2753431Scarlsonj 	/*
2763431Scarlsonj 	 * Ignore state machines that aren't looking for DLPI messages.
2773431Scarlsonj 	 */
2783431Scarlsonj 	if (!dsmp->dsm_using_dlpi) {
2793431Scarlsonj 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignore state "
2803431Scarlsonj 		    "machine for %s packet XID %#x received via DLPI on %s",
2813431Scarlsonj 		    pname, xid, pif->pif_name);
2823431Scarlsonj 		free_pkt_entry(plp);
2833431Scarlsonj 		return;
2843431Scarlsonj 	}
2853431Scarlsonj 
286*3448Sdh155122 	if (pkt_v4_match(recv_type, DHCP_PACK)) {
2873431Scarlsonj 		if (!dhcp_bound(dsmp, plp)) {
2883431Scarlsonj 			dhcpmsg(MSG_WARNING, "dhcp_collect_dlpi: dhcp_bound "
2893431Scarlsonj 			    "failed for %s", dsmp->dsm_name);
2903431Scarlsonj 			dhcp_restart(dsmp);
2913431Scarlsonj 			return;
2923431Scarlsonj 		}
2933431Scarlsonj 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: %s on %s",
2943431Scarlsonj 		    pname, dsmp->dsm_name);
295*3448Sdh155122 	} else if (pkt_v4_match(recv_type, DHCP_PNAK)) {
296*3448Sdh155122 		free_pkt_entry(plp);
297*3448Sdh155122 		dhcp_restart(dsmp);
2983431Scarlsonj 	} else {
2993431Scarlsonj 		pkt_smach_enqueue(dsmp, plp);
3003431Scarlsonj 	}
3010Sstevel@tonic-gate }
3020Sstevel@tonic-gate 
3030Sstevel@tonic-gate /*
3043431Scarlsonj  * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when
3053431Scarlsonj  *		     abandoning the state machine.  For DHCPv6, this timer may
3063431Scarlsonj  *		     go off before the offer wait timer.  If so, then this is a
3073431Scarlsonj  *		     good time to check for valid Advertisements, so cancel the
3083431Scarlsonj  *		     timer and go check.
3090Sstevel@tonic-gate  *
3103431Scarlsonj  *   input: dhcp_smach_t *: the state machine DISCOVERs are being sent on
3110Sstevel@tonic-gate  *	    unsigned int: the number of DISCOVERs sent so far
3120Sstevel@tonic-gate  *  output: boolean_t: B_TRUE if retransmissions should stop
3130Sstevel@tonic-gate  */
3140Sstevel@tonic-gate 
3153431Scarlsonj /* ARGSUSED1 */
3160Sstevel@tonic-gate static boolean_t
3173431Scarlsonj stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers)
3180Sstevel@tonic-gate {
3193431Scarlsonj 	/*
3203431Scarlsonj 	 * If we're using v4 and the underlying LIF we're trying to configure
3213431Scarlsonj 	 * has been touched by the user, then bail out.
3223431Scarlsonj 	 */
3233431Scarlsonj 	if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) {
3243431Scarlsonj 		finished_smach(dsmp, DHCP_IPC_E_UNKIF);
3253431Scarlsonj 		return (B_TRUE);
3263431Scarlsonj 	}
3273431Scarlsonj 
3283431Scarlsonj 	if (dsmp->dsm_recv_pkt_list != NULL) {
3293431Scarlsonj 		dhcp_requesting(NULL, dsmp);
3303431Scarlsonj 		if (dsmp->dsm_state != SELECTING)
3313431Scarlsonj 			return (B_TRUE);
3323431Scarlsonj 	}
3330Sstevel@tonic-gate 	return (B_FALSE);
3340Sstevel@tonic-gate }
335