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