7388 Support -h <hostname> for ipadm DHCP
[unleashed.git] / usr / src / cmd / cmd-inet / sbin / dhcpagent / select.c
blobe2570a4f0fed1f440adf71b4268efe011f749829
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright (c) 2016-2017, Chris Fraire <cfraire@me.com>.
25 * SELECTING state of the client state machine.
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>
43 #include "states.h"
44 #include "agent.h"
45 #include "util.h"
46 #include "interface.h"
47 #include "packet.h"
48 #include "defaults.h"
50 static stop_func_t stop_selecting;
53 * dhcp_start(): starts DHCP on a state machine
55 * input: iu_tq_t *: unused
56 * void *: the state machine on which to start DHCP
57 * output: void
60 /* ARGSUSED */
61 static void
62 dhcp_start(iu_tq_t *tqp, void *arg)
64 dhcp_smach_t *dsmp = arg;
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);
75 * set_start_timer(): sets a random timer to start a DHCP state machine
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
81 boolean_t
82 set_start_timer(dhcp_smach_t *dsmp)
84 if (dsmp->dsm_start_timer != -1)
85 return (B_TRUE);
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);
92 hold_smach(dsmp);
93 return (B_TRUE);
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.
101 * input: dhcp_smach_t *: the state machine on which to send the DISCOVER
102 * output: void
105 void
106 dhcp_selecting(dhcp_smach_t *dsmp)
108 dhcp_pkt_t *dpkt;
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).
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.
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;
135 /* Remove the stale hostconf file, if there is any */
136 (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
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;
146 hold_smach(dsmp);
149 * Assemble and send the DHCPDISCOVER or Solicit message.
151 * If this fails, we'll wait for the select timer to go off
152 * before trying again.
154 if (dsmp->dsm_isv6) {
155 dhcpv6_ia_na_t d6in;
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;
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));
171 /* Option Request option for desired information */
172 (void) add_pkt_prl(dpkt, dsmp);
174 /* Enable Rapid-Commit */
175 (void) add_pkt_opt(dpkt, DHCPV6_OPT_RAPID_COMMIT, NULL, 0);
177 /* xxx add Reconfigure Accept */
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;
189 * The max DHCP message size option is set to the interface
190 * MTU, minus the size of the UDP and IP headers.
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));
196 if (class_id_len != 0) {
197 (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id,
198 class_id_len);
200 (void) add_pkt_prl(dpkt, dsmp);
202 if (!dhcp_add_fqdn_opt(dpkt, dsmp))
203 (void) dhcp_add_hostname_opt(dpkt, dsmp);
205 (void) add_pkt_opt(dpkt, CD_END, NULL, 0);
207 (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST),
208 stop_selecting);
210 return;
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);
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.
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
230 /* ARGSUSED1 */
231 static boolean_t
232 stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers)
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.
238 if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) {
239 finished_smach(dsmp, DHCP_IPC_E_UNKIF);
240 return (B_TRUE);
243 if (dsmp->dsm_recv_pkt_list != NULL) {
244 dhcp_requesting(NULL, dsmp);
245 if (dsmp->dsm_state != SELECTING)
246 return (B_TRUE);
248 return (B_FALSE);