xref: /onnv-gate/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c (revision 3431:9f2d277dcffa)
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 /*
22*3431Scarlsonj  * 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 /*
54*3431Scarlsonj  * dhcp_start(): starts DHCP on a state machine
550Sstevel@tonic-gate  *
560Sstevel@tonic-gate  *   input: iu_tq_t *: unused
57*3431Scarlsonj  *	    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 {
65*3431Scarlsonj 	dhcp_smach_t	*dsmp = arg;
660Sstevel@tonic-gate 
67*3431Scarlsonj 	release_smach(dsmp);
680Sstevel@tonic-gate 
69*3431Scarlsonj 	dhcpmsg(MSG_VERBOSE, "starting DHCP on %s", dsmp->dsm_name);
70*3431Scarlsonj 	dhcp_selecting(dsmp);
710Sstevel@tonic-gate }
720Sstevel@tonic-gate 
730Sstevel@tonic-gate /*
74*3431Scarlsonj  * dhcp_selecting(): sends a DISCOVER and sets up reception of OFFERs for
75*3431Scarlsonj  *		     IPv4, or sends a Solicit and sets up reception of
76*3431Scarlsonj  *		     Advertisements for DHCPv6.
770Sstevel@tonic-gate  *
78*3431Scarlsonj  *   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
83*3431Scarlsonj 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 	/*
90*3431Scarlsonj 	 * We first set up to collect OFFER/Advertise packets as they arrive.
91*3431Scarlsonj 	 * We then send out DISCOVER/Solicit probes.  Then we wait a
92*3431Scarlsonj 	 * user-tunable number of seconds before seeing if OFFERs/
93*3431Scarlsonj 	 * Advertisements have come in response to our DISCOVER/Solicit.  If
94*3431Scarlsonj 	 * none have come in, we continue to wait, sending out our DISCOVER/
95*3431Scarlsonj 	 * Solicit probes with exponential backoff.  If no OFFER/Advertisement
96*3431Scarlsonj 	 * is ever received, we will wait forever (note that since we're
97*3431Scarlsonj 	 * event-driven though, we're still able to service other state
98*3431Scarlsonj 	 * machines).
990Sstevel@tonic-gate 	 *
100*3431Scarlsonj 	 * Note that we do an reset_smach() here because we may be landing in
101*3431Scarlsonj 	 * dhcp_selecting() as a result of restarting DHCP, so the state
102*3431Scarlsonj 	 * machine may not be fresh.
1030Sstevel@tonic-gate 	 */
1040Sstevel@tonic-gate 
105*3431Scarlsonj 	reset_smach(dsmp);
106*3431Scarlsonj 	if (!set_smach_state(dsmp, SELECTING)) {
107*3431Scarlsonj 		dhcpmsg(MSG_ERROR,
108*3431Scarlsonj 		    "dhcp_selecting: cannot switch to SELECTING state; "
109*3431Scarlsonj 		    "reverting to INIT on %s", dsmp->dsm_name);
110*3431Scarlsonj 		goto failed;
1110Sstevel@tonic-gate 
112*3431Scarlsonj 	}
1130Sstevel@tonic-gate 
114*3431Scarlsonj 	dsmp->dsm_offer_timer = iu_schedule_timer(tq,
115*3431Scarlsonj 	    dsmp->dsm_offer_wait, dhcp_requesting, dsmp);
116*3431Scarlsonj 	if (dsmp->dsm_offer_timer == -1) {
117*3431Scarlsonj 		dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read "
118*3431Scarlsonj 		    "%s packets", dsmp->dsm_isv6 ? "Advertise" : "OFFER");
119*3431Scarlsonj 		goto failed;
120*3431Scarlsonj 	}
1210Sstevel@tonic-gate 
122*3431Scarlsonj 	hold_smach(dsmp);
1230Sstevel@tonic-gate 
124*3431Scarlsonj 	/*
125*3431Scarlsonj 	 * Assemble and send the DHCPDISCOVER or Solicit message.
126*3431Scarlsonj 	 *
127*3431Scarlsonj 	 * If this fails, we'll wait for the select timer to go off
128*3431Scarlsonj 	 * before trying again.
129*3431Scarlsonj 	 */
130*3431Scarlsonj 	if (dsmp->dsm_isv6) {
131*3431Scarlsonj 		dhcpv6_ia_na_t d6in;
1320Sstevel@tonic-gate 
133*3431Scarlsonj 		if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_SOLICIT)) == NULL) {
134*3431Scarlsonj 			dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up "
135*3431Scarlsonj 			    "Solicit packet");
136*3431Scarlsonj 			return;
1370Sstevel@tonic-gate 		}
1380Sstevel@tonic-gate 
139*3431Scarlsonj 		/* Add an IA_NA option for our controlling LIF */
140*3431Scarlsonj 		d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid);
141*3431Scarlsonj 		d6in.d6in_t1 = htonl(0);
142*3431Scarlsonj 		d6in.d6in_t2 = htonl(0);
143*3431Scarlsonj 		(void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA,
144*3431Scarlsonj 		    (dhcpv6_option_t *)&d6in + 1,
145*3431Scarlsonj 		    sizeof (d6in) - sizeof (dhcpv6_option_t));
146*3431Scarlsonj 
147*3431Scarlsonj 		/* Option Request option for desired information */
148*3431Scarlsonj 		(void) add_pkt_prl(dpkt, dsmp);
149*3431Scarlsonj 
150*3431Scarlsonj 		/* Enable Rapid-Commit */
151*3431Scarlsonj 		(void) add_pkt_opt(dpkt, DHCPV6_OPT_RAPID_COMMIT, NULL, 0);
152*3431Scarlsonj 
153*3431Scarlsonj 		/* xxx add Reconfigure Accept */
1540Sstevel@tonic-gate 
155*3431Scarlsonj 		(void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers,
156*3431Scarlsonj 		    stop_selecting, DHCPV6_SOL_TIMEOUT, DHCPV6_SOL_MAX_RT);
157*3431Scarlsonj 	} else {
158*3431Scarlsonj 		if ((dpkt = init_pkt(dsmp, DISCOVER)) == NULL) {
159*3431Scarlsonj 			dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up "
160*3431Scarlsonj 			    "DISCOVER packet");
161*3431Scarlsonj 			return;
162*3431Scarlsonj 		}
1630Sstevel@tonic-gate 
164*3431Scarlsonj 		/*
165*3431Scarlsonj 		 * The max DHCP message size option is set to the interface
166*3431Scarlsonj 		 * MTU, minus the size of the UDP and IP headers.
167*3431Scarlsonj 		 */
168*3431Scarlsonj 		(void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE,
169*3431Scarlsonj 		    htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr)));
170*3431Scarlsonj 		(void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
1710Sstevel@tonic-gate 
172*3431Scarlsonj 		(void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len);
173*3431Scarlsonj 		(void) add_pkt_prl(dpkt, dsmp);
1740Sstevel@tonic-gate 
175*3431Scarlsonj 		if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6,
176*3431Scarlsonj 		    DF_REQUEST_HOSTNAME)) {
177*3431Scarlsonj 			dhcpmsg(MSG_DEBUG,
178*3431Scarlsonj 			    "dhcp_selecting: DF_REQUEST_HOSTNAME");
179*3431Scarlsonj 			(void) snprintf(hostfile, sizeof (hostfile),
180*3431Scarlsonj 			    "/etc/hostname.%s", dsmp->dsm_name);
1810Sstevel@tonic-gate 
182*3431Scarlsonj 			if ((reqhost = iffile_to_hostname(hostfile)) != NULL) {
183*3431Scarlsonj 				dhcpmsg(MSG_DEBUG, "dhcp_selecting: host %s",
184*3431Scarlsonj 				    reqhost);
185*3431Scarlsonj 				dsmp->dsm_reqhost = strdup(reqhost);
186*3431Scarlsonj 				if (dsmp->dsm_reqhost != NULL)
187*3431Scarlsonj 					(void) add_pkt_opt(dpkt, CD_HOSTNAME,
188*3431Scarlsonj 					    dsmp->dsm_reqhost,
189*3431Scarlsonj 					    strlen(dsmp->dsm_reqhost));
190*3431Scarlsonj 				else
191*3431Scarlsonj 					dhcpmsg(MSG_WARNING,
192*3431Scarlsonj 					    "dhcp_selecting: cannot allocate "
193*3431Scarlsonj 					    "memory for host name option");
194*3431Scarlsonj 			}
1950Sstevel@tonic-gate 		}
196*3431Scarlsonj 		(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
197*3431Scarlsonj 
198*3431Scarlsonj 		(void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST),
199*3431Scarlsonj 		    stop_selecting);
2000Sstevel@tonic-gate 	}
201*3431Scarlsonj 	return;
2020Sstevel@tonic-gate 
203*3431Scarlsonj failed:
204*3431Scarlsonj 	(void) set_smach_state(dsmp, INIT);
205*3431Scarlsonj 	dsmp->dsm_dflags |= DHCP_IF_FAILED;
206*3431Scarlsonj 	ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY);
2070Sstevel@tonic-gate }
2080Sstevel@tonic-gate 
2090Sstevel@tonic-gate /*
210*3431Scarlsonj  * dhcp_collect_dlpi(): collects incoming OFFERs, ACKs, and NAKs via DLPI.
2110Sstevel@tonic-gate  *
2120Sstevel@tonic-gate  *   input: iu_eh_t *: unused
213*3431Scarlsonj  *	    int: the file descriptor the mesage arrived on
2140Sstevel@tonic-gate  *	    short: unused
2150Sstevel@tonic-gate  *	    iu_event_id_t: the id of this event callback with the handler
216*3431Scarlsonj  *	    void *: the physical interface that received the message
2170Sstevel@tonic-gate  *  output: void
2180Sstevel@tonic-gate  */
2190Sstevel@tonic-gate 
2200Sstevel@tonic-gate /* ARGSUSED */
221*3431Scarlsonj void
222*3431Scarlsonj dhcp_collect_dlpi(iu_eh_t *eh, int fd, short events, iu_event_id_t id,
2230Sstevel@tonic-gate     void *arg)
2240Sstevel@tonic-gate {
225*3431Scarlsonj 	dhcp_pif_t	*pif = arg;
226*3431Scarlsonj 	PKT_LIST	*plp;
227*3431Scarlsonj 	uchar_t		recv_type;
228*3431Scarlsonj 	const char	*pname;
229*3431Scarlsonj 	dhcp_smach_t	*dsmp;
230*3431Scarlsonj 	uint_t		xid;
2310Sstevel@tonic-gate 
232*3431Scarlsonj 	if ((plp = recv_pkt(fd, pif->pif_max, B_FALSE, B_TRUE)) == NULL)
2330Sstevel@tonic-gate 		return;
234*3431Scarlsonj 
235*3431Scarlsonj 	recv_type = pkt_recv_type(plp);
236*3431Scarlsonj 	pname = pkt_type_to_string(recv_type, B_FALSE);
2370Sstevel@tonic-gate 
2380Sstevel@tonic-gate 	/*
2390Sstevel@tonic-gate 	 * DHCP_PUNTYPED messages are BOOTP server responses.
2400Sstevel@tonic-gate 	 */
241*3431Scarlsonj 	if (!pkt_v4_match(recv_type,
242*3431Scarlsonj 	    DHCP_PACK | DHCP_PNAK | DHCP_POFFER | DHCP_PUNTYPED)) {
243*3431Scarlsonj 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignored %s packet "
244*3431Scarlsonj 		    "received via DLPI on %s", pname, pif->pif_name);
245*3431Scarlsonj 		free_pkt_entry(plp);
246*3431Scarlsonj 		return;
247*3431Scarlsonj 	}
2480Sstevel@tonic-gate 
249*3431Scarlsonj 	/*
250*3431Scarlsonj 	 * Loop through the state machines that match on XID to find one that's
251*3431Scarlsonj 	 * interested in this offer.  If there are none, then discard.
252*3431Scarlsonj 	 */
253*3431Scarlsonj 	xid = pkt_get_xid(plp->pkt, B_FALSE);
254*3431Scarlsonj 	for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL;
255*3431Scarlsonj 	    dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) {
256*3431Scarlsonj 
257*3431Scarlsonj 		/*
258*3431Scarlsonj 		 * Find state machine on correct interface.
259*3431Scarlsonj 		 */
260*3431Scarlsonj 		if (dsmp->dsm_lif->lif_pif == pif)
261*3431Scarlsonj 			break;
262*3431Scarlsonj 	}
263*3431Scarlsonj 
264*3431Scarlsonj 	if (dsmp == NULL) {
265*3431Scarlsonj 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: no matching state "
266*3431Scarlsonj 		    "machine for %s packet XID %#x received via DLPI on %s",
267*3431Scarlsonj 		    pname, xid, pif->pif_name);
268*3431Scarlsonj 		free_pkt_entry(plp);
269*3431Scarlsonj 		return;
270*3431Scarlsonj 	}
271*3431Scarlsonj 
272*3431Scarlsonj 	/*
273*3431Scarlsonj 	 * Ignore state machines that aren't looking for DLPI messages.
274*3431Scarlsonj 	 */
275*3431Scarlsonj 	if (!dsmp->dsm_using_dlpi) {
276*3431Scarlsonj 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignore state "
277*3431Scarlsonj 		    "machine for %s packet XID %#x received via DLPI on %s",
278*3431Scarlsonj 		    pname, xid, pif->pif_name);
279*3431Scarlsonj 		free_pkt_entry(plp);
280*3431Scarlsonj 		return;
281*3431Scarlsonj 	}
282*3431Scarlsonj 
283*3431Scarlsonj 	if (pkt_v4_match(recv_type, DHCP_PACK | DHCP_PNAK)) {
284*3431Scarlsonj 		if (!dhcp_bound(dsmp, plp)) {
285*3431Scarlsonj 			dhcpmsg(MSG_WARNING, "dhcp_collect_dlpi: dhcp_bound "
286*3431Scarlsonj 			    "failed for %s", dsmp->dsm_name);
287*3431Scarlsonj 			dhcp_restart(dsmp);
288*3431Scarlsonj 			return;
289*3431Scarlsonj 		}
290*3431Scarlsonj 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: %s on %s",
291*3431Scarlsonj 		    pname, dsmp->dsm_name);
292*3431Scarlsonj 	} else {
293*3431Scarlsonj 		pkt_smach_enqueue(dsmp, plp);
294*3431Scarlsonj 	}
2950Sstevel@tonic-gate }
2960Sstevel@tonic-gate 
2970Sstevel@tonic-gate /*
298*3431Scarlsonj  * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when
299*3431Scarlsonj  *		     abandoning the state machine.  For DHCPv6, this timer may
300*3431Scarlsonj  *		     go off before the offer wait timer.  If so, then this is a
301*3431Scarlsonj  *		     good time to check for valid Advertisements, so cancel the
302*3431Scarlsonj  *		     timer and go check.
3030Sstevel@tonic-gate  *
304*3431Scarlsonj  *   input: dhcp_smach_t *: the state machine DISCOVERs are being sent on
3050Sstevel@tonic-gate  *	    unsigned int: the number of DISCOVERs sent so far
3060Sstevel@tonic-gate  *  output: boolean_t: B_TRUE if retransmissions should stop
3070Sstevel@tonic-gate  */
3080Sstevel@tonic-gate 
309*3431Scarlsonj /* ARGSUSED1 */
3100Sstevel@tonic-gate static boolean_t
311*3431Scarlsonj stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers)
3120Sstevel@tonic-gate {
313*3431Scarlsonj 	/*
314*3431Scarlsonj 	 * If we're using v4 and the underlying LIF we're trying to configure
315*3431Scarlsonj 	 * has been touched by the user, then bail out.
316*3431Scarlsonj 	 */
317*3431Scarlsonj 	if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) {
318*3431Scarlsonj 		finished_smach(dsmp, DHCP_IPC_E_UNKIF);
319*3431Scarlsonj 		return (B_TRUE);
320*3431Scarlsonj 	}
321*3431Scarlsonj 
322*3431Scarlsonj 	if (dsmp->dsm_recv_pkt_list != NULL) {
323*3431Scarlsonj 		dhcp_requesting(NULL, dsmp);
324*3431Scarlsonj 		if (dsmp->dsm_state != SELECTING)
325*3431Scarlsonj 			return (B_TRUE);
326*3431Scarlsonj 	}
3270Sstevel@tonic-gate 	return (B_FALSE);
3280Sstevel@tonic-gate }
329