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 * 24 * SELECTING state of the client state machine. 25 */ 26 27 #include <sys/types.h> 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <strings.h> 31 #include <time.h> 32 #include <limits.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 const char *reqhost; 110 char hostfile[PATH_MAX + 1]; 111 112 /* 113 * We first set up to collect OFFER/Advertise packets as they arrive. 114 * We then send out DISCOVER/Solicit probes. Then we wait a 115 * user-tunable number of seconds before seeing if OFFERs/ 116 * Advertisements have come in response to our DISCOVER/Solicit. If 117 * none have come in, we continue to wait, sending out our DISCOVER/ 118 * Solicit probes with exponential backoff. If no OFFER/Advertisement 119 * is ever received, we will wait forever (note that since we're 120 * event-driven though, we're still able to service other state 121 * machines). 122 * 123 * Note that we do an reset_smach() here because we may be landing in 124 * dhcp_selecting() as a result of restarting DHCP, so the state 125 * machine may not be fresh. 126 */ 127 128 reset_smach(dsmp); 129 if (!set_smach_state(dsmp, SELECTING)) { 130 dhcpmsg(MSG_ERROR, 131 "dhcp_selecting: cannot switch to SELECTING state; " 132 "reverting to INIT on %s", dsmp->dsm_name); 133 goto failed; 134 135 } 136 137 /* Remove the stale hostconf file, if there is any */ 138 (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); 139 140 dsmp->dsm_offer_timer = iu_schedule_timer(tq, 141 dsmp->dsm_offer_wait, dhcp_requesting, dsmp); 142 if (dsmp->dsm_offer_timer == -1) { 143 dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read " 144 "%s packets", dsmp->dsm_isv6 ? "Advertise" : "OFFER"); 145 goto failed; 146 } 147 148 hold_smach(dsmp); 149 150 /* 151 * Assemble and send the DHCPDISCOVER or Solicit message. 152 * 153 * If this fails, we'll wait for the select timer to go off 154 * before trying again. 155 */ 156 if (dsmp->dsm_isv6) { 157 dhcpv6_ia_na_t d6in; 158 159 if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_SOLICIT)) == NULL) { 160 dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up " 161 "Solicit packet"); 162 return; 163 } 164 165 /* Add an IA_NA option for our controlling LIF */ 166 d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); 167 d6in.d6in_t1 = htonl(0); 168 d6in.d6in_t2 = htonl(0); 169 (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, 170 (dhcpv6_option_t *)&d6in + 1, 171 sizeof (d6in) - sizeof (dhcpv6_option_t)); 172 173 /* Option Request option for desired information */ 174 (void) add_pkt_prl(dpkt, dsmp); 175 176 /* Enable Rapid-Commit */ 177 (void) add_pkt_opt(dpkt, DHCPV6_OPT_RAPID_COMMIT, NULL, 0); 178 179 /* xxx add Reconfigure Accept */ 180 181 (void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers, 182 stop_selecting, DHCPV6_SOL_TIMEOUT, DHCPV6_SOL_MAX_RT); 183 } else { 184 if ((dpkt = init_pkt(dsmp, DISCOVER)) == NULL) { 185 dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up " 186 "DISCOVER packet"); 187 return; 188 } 189 190 /* 191 * The max DHCP message size option is set to the interface 192 * MTU, minus the size of the UDP and IP headers. 193 */ 194 (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, 195 htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr))); 196 (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM)); 197 198 if (class_id_len != 0) { 199 (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, 200 class_id_len); 201 } 202 (void) add_pkt_prl(dpkt, dsmp); 203 204 if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, 205 DF_REQUEST_HOSTNAME)) { 206 dhcpmsg(MSG_DEBUG, 207 "dhcp_selecting: DF_REQUEST_HOSTNAME"); 208 (void) snprintf(hostfile, sizeof (hostfile), 209 "/etc/hostname.%s", dsmp->dsm_name); 210 211 if ((reqhost = iffile_to_hostname(hostfile)) != NULL) { 212 dhcpmsg(MSG_DEBUG, "dhcp_selecting: host %s", 213 reqhost); 214 dsmp->dsm_reqhost = strdup(reqhost); 215 if (dsmp->dsm_reqhost != NULL) 216 (void) add_pkt_opt(dpkt, CD_HOSTNAME, 217 dsmp->dsm_reqhost, 218 strlen(dsmp->dsm_reqhost)); 219 else 220 dhcpmsg(MSG_WARNING, 221 "dhcp_selecting: cannot allocate " 222 "memory for host name option"); 223 } 224 } 225 (void) add_pkt_opt(dpkt, CD_END, NULL, 0); 226 227 (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), 228 stop_selecting); 229 } 230 return; 231 232 failed: 233 (void) set_smach_state(dsmp, INIT); 234 dsmp->dsm_dflags |= DHCP_IF_FAILED; 235 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 236 } 237 238 /* 239 * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when 240 * abandoning the state machine. For DHCPv6, this timer may 241 * go off before the offer wait timer. If so, then this is a 242 * good time to check for valid Advertisements, so cancel the 243 * timer and go check. 244 * 245 * input: dhcp_smach_t *: the state machine DISCOVERs are being sent on 246 * unsigned int: the number of DISCOVERs sent so far 247 * output: boolean_t: B_TRUE if retransmissions should stop 248 */ 249 250 /* ARGSUSED1 */ 251 static boolean_t 252 stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers) 253 { 254 /* 255 * If we're using v4 and the underlying LIF we're trying to configure 256 * has been touched by the user, then bail out. 257 */ 258 if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) { 259 finished_smach(dsmp, DHCP_IPC_E_UNKIF); 260 return (B_TRUE); 261 } 262 263 if (dsmp->dsm_recv_pkt_list != NULL) { 264 dhcp_requesting(NULL, dsmp); 265 if (dsmp->dsm_state != SELECTING) 266 return (B_TRUE); 267 } 268 return (B_FALSE); 269 } 270