xref: /titanic_50/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c (revision 20c794b39650d115e17a15983b6b82e46238cf45)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * SELECTING state of the client state machine.
26  */
27 
28 #pragma ident	"%Z%%M%	%I%	%E% SMI"
29 
30 #include <sys/types.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <strings.h>
34 #include <time.h>
35 #include <limits.h>
36 #include <netinet/in.h>
37 #include <net/route.h>
38 #include <net/if.h>
39 #include <netinet/dhcp.h>
40 #include <netinet/udp.h>
41 #include <netinet/ip_var.h>
42 #include <netinet/udp_var.h>
43 #include <dhcpmsg.h>
44 
45 #include "states.h"
46 #include "agent.h"
47 #include "util.h"
48 #include "interface.h"
49 #include "packet.h"
50 #include "defaults.h"
51 
52 static stop_func_t	stop_selecting;
53 
54 /*
55  * dhcp_start(): starts DHCP on a state machine
56  *
57  *   input: iu_tq_t *: unused
58  *	    void *: the state machine on which to start DHCP
59  *  output: void
60  */
61 
62 /* ARGSUSED */
63 static void
64 dhcp_start(iu_tq_t *tqp, void *arg)
65 {
66 	dhcp_smach_t	*dsmp = arg;
67 
68 	dsmp->dsm_start_timer = -1;
69 	(void) set_smach_state(dsmp, INIT);
70 	if (verify_smach(dsmp)) {
71 		dhcpmsg(MSG_VERBOSE, "starting DHCP on %s", dsmp->dsm_name);
72 		dhcp_selecting(dsmp);
73 	}
74 }
75 
76 /*
77  * set_start_timer(): sets a random timer to start a DHCP state machine
78  *
79  *   input: dhcp_smach_t *: the state machine on which to start DHCP
80  *  output: boolean_t: B_TRUE if a timer is now running
81  */
82 
83 boolean_t
84 set_start_timer(dhcp_smach_t *dsmp)
85 {
86 	if (dsmp->dsm_start_timer != -1)
87 		return (B_TRUE);
88 
89 	dsmp->dsm_start_timer = iu_schedule_timer_ms(tq,
90 	    lrand48() % DHCP_SELECT_WAIT, dhcp_start, dsmp);
91 	if (dsmp->dsm_start_timer == -1)
92 		return (B_FALSE);
93 
94 	hold_smach(dsmp);
95 	return (B_TRUE);
96 }
97 
98 /*
99  * dhcp_selecting(): sends a DISCOVER and sets up reception of OFFERs for
100  *		     IPv4, or sends a Solicit and sets up reception of
101  *		     Advertisements for DHCPv6.
102  *
103  *   input: dhcp_smach_t *: the state machine on which to send the DISCOVER
104  *  output: void
105  */
106 
107 void
108 dhcp_selecting(dhcp_smach_t *dsmp)
109 {
110 	dhcp_pkt_t		*dpkt;
111 	const char		*reqhost;
112 	char			hostfile[PATH_MAX + 1];
113 
114 	/*
115 	 * We first set up to collect OFFER/Advertise packets as they arrive.
116 	 * We then send out DISCOVER/Solicit probes.  Then we wait a
117 	 * user-tunable number of seconds before seeing if OFFERs/
118 	 * Advertisements have come in response to our DISCOVER/Solicit.  If
119 	 * none have come in, we continue to wait, sending out our DISCOVER/
120 	 * Solicit probes with exponential backoff.  If no OFFER/Advertisement
121 	 * is ever received, we will wait forever (note that since we're
122 	 * event-driven though, we're still able to service other state
123 	 * machines).
124 	 *
125 	 * Note that we do an reset_smach() here because we may be landing in
126 	 * dhcp_selecting() as a result of restarting DHCP, so the state
127 	 * machine may not be fresh.
128 	 */
129 
130 	reset_smach(dsmp);
131 	if (!set_smach_state(dsmp, SELECTING)) {
132 		dhcpmsg(MSG_ERROR,
133 		    "dhcp_selecting: cannot switch to SELECTING state; "
134 		    "reverting to INIT on %s", dsmp->dsm_name);
135 		goto failed;
136 
137 	}
138 
139 	dsmp->dsm_offer_timer = iu_schedule_timer(tq,
140 	    dsmp->dsm_offer_wait, dhcp_requesting, dsmp);
141 	if (dsmp->dsm_offer_timer == -1) {
142 		dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read "
143 		    "%s packets", dsmp->dsm_isv6 ? "Advertise" : "OFFER");
144 		goto failed;
145 	}
146 
147 	hold_smach(dsmp);
148 
149 	/*
150 	 * Assemble and send the DHCPDISCOVER or Solicit message.
151 	 *
152 	 * If this fails, we'll wait for the select timer to go off
153 	 * before trying again.
154 	 */
155 	if (dsmp->dsm_isv6) {
156 		dhcpv6_ia_na_t d6in;
157 
158 		if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_SOLICIT)) == NULL) {
159 			dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up "
160 			    "Solicit packet");
161 			return;
162 		}
163 
164 		/* Add an IA_NA option for our controlling LIF */
165 		d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid);
166 		d6in.d6in_t1 = htonl(0);
167 		d6in.d6in_t2 = htonl(0);
168 		(void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA,
169 		    (dhcpv6_option_t *)&d6in + 1,
170 		    sizeof (d6in) - sizeof (dhcpv6_option_t));
171 
172 		/* Option Request option for desired information */
173 		(void) add_pkt_prl(dpkt, dsmp);
174 
175 		/* Enable Rapid-Commit */
176 		(void) add_pkt_opt(dpkt, DHCPV6_OPT_RAPID_COMMIT, NULL, 0);
177 
178 		/* xxx add Reconfigure Accept */
179 
180 		(void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers,
181 		    stop_selecting, DHCPV6_SOL_TIMEOUT, DHCPV6_SOL_MAX_RT);
182 	} else {
183 		if ((dpkt = init_pkt(dsmp, DISCOVER)) == NULL) {
184 			dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up "
185 			    "DISCOVER packet");
186 			return;
187 		}
188 
189 		/*
190 		 * The max DHCP message size option is set to the interface
191 		 * MTU, minus the size of the UDP and IP headers.
192 		 */
193 		(void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE,
194 		    htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr)));
195 		(void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
196 
197 		if (class_id_len != 0) {
198 			(void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id,
199 			    class_id_len);
200 		}
201 		(void) add_pkt_prl(dpkt, dsmp);
202 
203 		if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6,
204 		    DF_REQUEST_HOSTNAME)) {
205 			dhcpmsg(MSG_DEBUG,
206 			    "dhcp_selecting: DF_REQUEST_HOSTNAME");
207 			(void) snprintf(hostfile, sizeof (hostfile),
208 			    "/etc/hostname.%s", dsmp->dsm_name);
209 
210 			if ((reqhost = iffile_to_hostname(hostfile)) != NULL) {
211 				dhcpmsg(MSG_DEBUG, "dhcp_selecting: host %s",
212 				    reqhost);
213 				dsmp->dsm_reqhost = strdup(reqhost);
214 				if (dsmp->dsm_reqhost != NULL)
215 					(void) add_pkt_opt(dpkt, CD_HOSTNAME,
216 					    dsmp->dsm_reqhost,
217 					    strlen(dsmp->dsm_reqhost));
218 				else
219 					dhcpmsg(MSG_WARNING,
220 					    "dhcp_selecting: cannot allocate "
221 					    "memory for host name option");
222 			}
223 		}
224 		(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
225 
226 		(void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST),
227 		    stop_selecting);
228 	}
229 	return;
230 
231 failed:
232 	(void) set_smach_state(dsmp, INIT);
233 	dsmp->dsm_dflags |= DHCP_IF_FAILED;
234 	ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY);
235 }
236 
237 /*
238  * dhcp_collect_dlpi(): collects incoming OFFERs, ACKs, and NAKs via DLPI.
239  *
240  *   input: iu_eh_t *: unused
241  *	    int: unused
242  *	    short: unused
243  *	    iu_event_id_t: the id of this event callback with the handler
244  *	    void *: the physical interface that received the message
245  *  output: void
246  */
247 
248 /* ARGSUSED */
249 void
250 dhcp_collect_dlpi(iu_eh_t *eh, int fd, short events, iu_event_id_t id,
251     void *arg)
252 {
253 	dhcp_pif_t	*pif = arg;
254 	PKT_LIST	*plp;
255 	uchar_t		recv_type;
256 	const char	*pname;
257 	dhcp_smach_t	*dsmp;
258 	uint_t		xid;
259 
260 	if ((plp = recv_pkt(fd, pif->pif_max, B_FALSE, B_TRUE, pif)) == NULL)
261 		return;
262 
263 	recv_type = pkt_recv_type(plp);
264 	pname = pkt_type_to_string(recv_type, B_FALSE);
265 
266 	/*
267 	 * DHCP_PUNTYPED messages are BOOTP server responses.
268 	 */
269 	if (!pkt_v4_match(recv_type,
270 	    DHCP_PACK | DHCP_PNAK | DHCP_POFFER | DHCP_PUNTYPED)) {
271 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignored %s packet "
272 		    "received via DLPI on %s", pname, pif->pif_name);
273 		free_pkt_entry(plp);
274 		return;
275 	}
276 
277 	/*
278 	 * Loop through the state machines that match on XID to find one that's
279 	 * interested in this offer.  If there are none, then discard.
280 	 */
281 	xid = pkt_get_xid(plp->pkt, B_FALSE);
282 	for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL;
283 	    dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) {
284 
285 		/*
286 		 * Find state machine on correct interface.
287 		 */
288 		if (dsmp->dsm_lif->lif_pif == pif)
289 			break;
290 	}
291 
292 	if (dsmp == NULL) {
293 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: no matching state "
294 		    "machine for %s packet XID %#x received via DLPI on %s",
295 		    pname, xid, pif->pif_name);
296 		free_pkt_entry(plp);
297 		return;
298 	}
299 
300 	/*
301 	 * Ignore state machines that aren't looking for DLPI messages.
302 	 */
303 	if (!dsmp->dsm_using_dlpi) {
304 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignore state "
305 		    "machine for %s packet XID %#x received via DLPI on %s",
306 		    pname, xid, pif->pif_name);
307 		free_pkt_entry(plp);
308 		return;
309 	}
310 
311 	/* See also accept_v[46]_message; account for processed packets. */
312 	dsmp->dsm_received++;
313 
314 	if (pkt_v4_match(recv_type, DHCP_PACK)) {
315 		if (!dhcp_bound(dsmp, plp)) {
316 			dhcpmsg(MSG_WARNING, "dhcp_collect_dlpi: dhcp_bound "
317 			    "failed for %s", dsmp->dsm_name);
318 			dhcp_restart(dsmp);
319 			return;
320 		}
321 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: %s on %s",
322 		    pname, dsmp->dsm_name);
323 	} else if (pkt_v4_match(recv_type, DHCP_PNAK)) {
324 		dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: %s on %s",
325 		    pname, dsmp->dsm_name);
326 		free_pkt_entry(plp);
327 		dhcp_restart(dsmp);
328 	} else {
329 		pkt_smach_enqueue(dsmp, plp);
330 	}
331 }
332 
333 /*
334  * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when
335  *		     abandoning the state machine.  For DHCPv6, this timer may
336  *		     go off before the offer wait timer.  If so, then this is a
337  *		     good time to check for valid Advertisements, so cancel the
338  *		     timer and go check.
339  *
340  *   input: dhcp_smach_t *: the state machine DISCOVERs are being sent on
341  *	    unsigned int: the number of DISCOVERs sent so far
342  *  output: boolean_t: B_TRUE if retransmissions should stop
343  */
344 
345 /* ARGSUSED1 */
346 static boolean_t
347 stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers)
348 {
349 	/*
350 	 * If we're using v4 and the underlying LIF we're trying to configure
351 	 * has been touched by the user, then bail out.
352 	 */
353 	if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) {
354 		finished_smach(dsmp, DHCP_IPC_E_UNKIF);
355 		return (B_TRUE);
356 	}
357 
358 	if (dsmp->dsm_recv_pkt_list != NULL) {
359 		dhcp_requesting(NULL, dsmp);
360 		if (dsmp->dsm_state != SELECTING)
361 			return (B_TRUE);
362 	}
363 	return (B_FALSE);
364 }
365