xref: /illumos-gate/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c (revision 9525b14bcdeb5b5f6f95ab27c2f48f18bd2ec829)
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  * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when
239  *		     abandoning the state machine.  For DHCPv6, this timer may
240  *		     go off before the offer wait timer.  If so, then this is a
241  *		     good time to check for valid Advertisements, so cancel the
242  *		     timer and go check.
243  *
244  *   input: dhcp_smach_t *: the state machine DISCOVERs are being sent on
245  *	    unsigned int: the number of DISCOVERs sent so far
246  *  output: boolean_t: B_TRUE if retransmissions should stop
247  */
248 
249 /* ARGSUSED1 */
250 static boolean_t
251 stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers)
252 {
253 	/*
254 	 * If we're using v4 and the underlying LIF we're trying to configure
255 	 * has been touched by the user, then bail out.
256 	 */
257 	if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) {
258 		finished_smach(dsmp, DHCP_IPC_E_UNKIF);
259 		return (B_TRUE);
260 	}
261 
262 	if (dsmp->dsm_recv_pkt_list != NULL) {
263 		dhcp_requesting(NULL, dsmp);
264 		if (dsmp->dsm_state != SELECTING)
265 			return (B_TRUE);
266 	}
267 	return (B_FALSE);
268 }
269