xref: /illumos-gate/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c (revision 66582b606a8194f7f3ba5b3a3a6dca5b0d346361)
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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright (c) 2016-2017, Chris Fraire <cfraire@me.com>.
24  *
25  * SELECTING state of the client state machine.
26  */
27 
28 #include <sys/types.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <strings.h>
32 #include <time.h>
33 #include <netinet/in.h>
34 #include <net/route.h>
35 #include <net/if.h>
36 #include <netinet/dhcp.h>
37 #include <netinet/udp.h>
38 #include <netinet/ip_var.h>
39 #include <netinet/udp_var.h>
40 #include <dhcpmsg.h>
41 #include <dhcp_hostconf.h>
42 
43 #include "states.h"
44 #include "agent.h"
45 #include "util.h"
46 #include "interface.h"
47 #include "packet.h"
48 #include "defaults.h"
49 
50 static stop_func_t	stop_selecting;
51 
52 /*
53  * dhcp_start(): starts DHCP on a state machine
54  *
55  *   input: iu_tq_t *: unused
56  *	    void *: the state machine on which to start DHCP
57  *  output: void
58  */
59 
60 /* ARGSUSED */
61 static void
62 dhcp_start(iu_tq_t *tqp, void *arg)
63 {
64 	dhcp_smach_t	*dsmp = arg;
65 
66 	dsmp->dsm_start_timer = -1;
67 	(void) set_smach_state(dsmp, INIT);
68 	if (verify_smach(dsmp)) {
69 		dhcpmsg(MSG_VERBOSE, "starting DHCP on %s", dsmp->dsm_name);
70 		dhcp_selecting(dsmp);
71 	}
72 }
73 
74 /*
75  * set_start_timer(): sets a random timer to start a DHCP state machine
76  *
77  *   input: dhcp_smach_t *: the state machine on which to start DHCP
78  *  output: boolean_t: B_TRUE if a timer is now running
79  */
80 
81 boolean_t
82 set_start_timer(dhcp_smach_t *dsmp)
83 {
84 	if (dsmp->dsm_start_timer != -1)
85 		return (B_TRUE);
86 
87 	dsmp->dsm_start_timer = iu_schedule_timer_ms(tq,
88 	    lrand48() % DHCP_SELECT_WAIT, dhcp_start, dsmp);
89 	if (dsmp->dsm_start_timer == -1)
90 		return (B_FALSE);
91 
92 	hold_smach(dsmp);
93 	return (B_TRUE);
94 }
95 
96 /*
97  * dhcp_selecting(): sends a DISCOVER and sets up reception of OFFERs for
98  *		     IPv4, or sends a Solicit and sets up reception of
99  *		     Advertisements for DHCPv6.
100  *
101  *   input: dhcp_smach_t *: the state machine on which to send the DISCOVER
102  *  output: void
103  */
104 
105 void
106 dhcp_selecting(dhcp_smach_t *dsmp)
107 {
108 	dhcp_pkt_t		*dpkt;
109 
110 	/*
111 	 * We first set up to collect OFFER/Advertise packets as they arrive.
112 	 * We then send out DISCOVER/Solicit probes.  Then we wait a
113 	 * user-tunable number of seconds before seeing if OFFERs/
114 	 * Advertisements have come in response to our DISCOVER/Solicit.  If
115 	 * none have come in, we continue to wait, sending out our DISCOVER/
116 	 * Solicit probes with exponential backoff.  If no OFFER/Advertisement
117 	 * is ever received, we will wait forever (note that since we're
118 	 * event-driven though, we're still able to service other state
119 	 * machines).
120 	 *
121 	 * Note that we do an reset_smach() here because we may be landing in
122 	 * dhcp_selecting() as a result of restarting DHCP, so the state
123 	 * machine may not be fresh.
124 	 */
125 
126 	reset_smach(dsmp);
127 	if (!set_smach_state(dsmp, SELECTING)) {
128 		dhcpmsg(MSG_ERROR,
129 		    "dhcp_selecting: cannot switch to SELECTING state; "
130 		    "reverting to INIT on %s", dsmp->dsm_name);
131 		goto failed;
132 
133 	}
134 
135 	/* Remove the stale hostconf file, if there is any */
136 	(void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
137 
138 	dsmp->dsm_offer_timer = iu_schedule_timer(tq,
139 	    dsmp->dsm_offer_wait, dhcp_requesting, dsmp);
140 	if (dsmp->dsm_offer_timer == -1) {
141 		dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read "
142 		    "%s packets", dsmp->dsm_isv6 ? "Advertise" : "OFFER");
143 		goto failed;
144 	}
145 
146 	hold_smach(dsmp);
147 
148 	/*
149 	 * Assemble and send the DHCPDISCOVER or Solicit message.
150 	 *
151 	 * If this fails, we'll wait for the select timer to go off
152 	 * before trying again.
153 	 */
154 	if (dsmp->dsm_isv6) {
155 		dhcpv6_ia_na_t d6in;
156 
157 		if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_SOLICIT)) == NULL) {
158 			dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up "
159 			    "Solicit packet");
160 			return;
161 		}
162 
163 		/* Add an IA_NA option for our controlling LIF */
164 		d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid);
165 		d6in.d6in_t1 = htonl(0);
166 		d6in.d6in_t2 = htonl(0);
167 		(void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA,
168 		    (dhcpv6_option_t *)&d6in + 1,
169 		    sizeof (d6in) - sizeof (dhcpv6_option_t));
170 
171 		/* Option Request option for desired information */
172 		(void) add_pkt_prl(dpkt, dsmp);
173 
174 		/* Enable Rapid-Commit */
175 		(void) add_pkt_opt(dpkt, DHCPV6_OPT_RAPID_COMMIT, NULL, 0);
176 
177 		/* xxx add Reconfigure Accept */
178 
179 		(void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers,
180 		    stop_selecting, DHCPV6_SOL_TIMEOUT, DHCPV6_SOL_MAX_RT);
181 	} else {
182 		if ((dpkt = init_pkt(dsmp, DISCOVER)) == NULL) {
183 			dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up "
184 			    "DISCOVER packet");
185 			return;
186 		}
187 
188 		/*
189 		 * The max DHCP message size option is set to the interface
190 		 * MTU, minus the size of the UDP and IP headers.
191 		 */
192 		(void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE,
193 		    htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr)));
194 		(void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
195 
196 		if (class_id_len != 0) {
197 			(void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id,
198 			    class_id_len);
199 		}
200 		(void) add_pkt_prl(dpkt, dsmp);
201 
202 		if (!dhcp_add_fqdn_opt(dpkt, dsmp))
203 			(void) dhcp_add_hostname_opt(dpkt, dsmp);
204 
205 		(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
206 
207 		(void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST),
208 		    stop_selecting);
209 	}
210 	return;
211 
212 failed:
213 	(void) set_smach_state(dsmp, INIT);
214 	dsmp->dsm_dflags |= DHCP_IF_FAILED;
215 	ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY);
216 }
217 
218 /*
219  * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when
220  *		     abandoning the state machine.  For DHCPv6, this timer may
221  *		     go off before the offer wait timer.  If so, then this is a
222  *		     good time to check for valid Advertisements, so cancel the
223  *		     timer and go check.
224  *
225  *   input: dhcp_smach_t *: the state machine DISCOVERs are being sent on
226  *	    unsigned int: the number of DISCOVERs sent so far
227  *  output: boolean_t: B_TRUE if retransmissions should stop
228  */
229 
230 /* ARGSUSED1 */
231 static boolean_t
232 stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers)
233 {
234 	/*
235 	 * If we're using v4 and the underlying LIF we're trying to configure
236 	 * has been touched by the user, then bail out.
237 	 */
238 	if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) {
239 		finished_smach(dsmp, DHCP_IPC_E_UNKIF);
240 		return (B_TRUE);
241 	}
242 
243 	if (dsmp->dsm_recv_pkt_list != NULL) {
244 		dhcp_requesting(NULL, dsmp);
245 		if (dsmp->dsm_state != SELECTING)
246 			return (B_TRUE);
247 	}
248 	return (B_FALSE);
249 }
250