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 * dhcp_collect_dlpi(): collects incoming OFFERs, ACKs, and NAKs via DLPI. 239 * 240 * input: iu_eh_t *: unused 241 * int: the file descriptor the mesage arrived on 242 * short: unused 243 * iu_event_id_t: the id of this event callback with the handler 244 * void *: the physical interface that received the message 245 * output: void 246 */ 247 248 /* ARGSUSED */ 249 void 250 dhcp_collect_dlpi(iu_eh_t *eh, int fd, short events, iu_event_id_t id, 251 void *arg) 252 { 253 dhcp_pif_t *pif = arg; 254 PKT_LIST *plp; 255 uchar_t recv_type; 256 const char *pname; 257 dhcp_smach_t *dsmp; 258 uint_t xid; 259 260 if ((plp = recv_pkt(fd, pif->pif_max, B_FALSE, B_TRUE)) == NULL) 261 return; 262 263 recv_type = pkt_recv_type(plp); 264 pname = pkt_type_to_string(recv_type, B_FALSE); 265 266 /* 267 * DHCP_PUNTYPED messages are BOOTP server responses. 268 */ 269 if (!pkt_v4_match(recv_type, 270 DHCP_PACK | DHCP_PNAK | DHCP_POFFER | DHCP_PUNTYPED)) { 271 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignored %s packet " 272 "received via DLPI on %s", pname, pif->pif_name); 273 free_pkt_entry(plp); 274 return; 275 } 276 277 /* 278 * Loop through the state machines that match on XID to find one that's 279 * interested in this offer. If there are none, then discard. 280 */ 281 xid = pkt_get_xid(plp->pkt, B_FALSE); 282 for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; 283 dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { 284 285 /* 286 * Find state machine on correct interface. 287 */ 288 if (dsmp->dsm_lif->lif_pif == pif) 289 break; 290 } 291 292 if (dsmp == NULL) { 293 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: no matching state " 294 "machine for %s packet XID %#x received via DLPI on %s", 295 pname, xid, pif->pif_name); 296 free_pkt_entry(plp); 297 return; 298 } 299 300 /* 301 * Ignore state machines that aren't looking for DLPI messages. 302 */ 303 if (!dsmp->dsm_using_dlpi) { 304 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignore state " 305 "machine for %s packet XID %#x received via DLPI on %s", 306 pname, xid, pif->pif_name); 307 free_pkt_entry(plp); 308 return; 309 } 310 311 if (pkt_v4_match(recv_type, DHCP_PACK)) { 312 if (!dhcp_bound(dsmp, plp)) { 313 dhcpmsg(MSG_WARNING, "dhcp_collect_dlpi: dhcp_bound " 314 "failed for %s", dsmp->dsm_name); 315 dhcp_restart(dsmp); 316 return; 317 } 318 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: %s on %s", 319 pname, dsmp->dsm_name); 320 } else if (pkt_v4_match(recv_type, DHCP_PNAK)) { 321 free_pkt_entry(plp); 322 dhcp_restart(dsmp); 323 } else { 324 pkt_smach_enqueue(dsmp, plp); 325 } 326 } 327 328 /* 329 * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when 330 * abandoning the state machine. For DHCPv6, this timer may 331 * go off before the offer wait timer. If so, then this is a 332 * good time to check for valid Advertisements, so cancel the 333 * timer and go check. 334 * 335 * input: dhcp_smach_t *: the state machine DISCOVERs are being sent on 336 * unsigned int: the number of DISCOVERs sent so far 337 * output: boolean_t: B_TRUE if retransmissions should stop 338 */ 339 340 /* ARGSUSED1 */ 341 static boolean_t 342 stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers) 343 { 344 /* 345 * If we're using v4 and the underlying LIF we're trying to configure 346 * has been touched by the user, then bail out. 347 */ 348 if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) { 349 finished_smach(dsmp, DHCP_IPC_E_UNKIF); 350 return (B_TRUE); 351 } 352 353 if (dsmp->dsm_recv_pkt_list != NULL) { 354 dhcp_requesting(NULL, dsmp); 355 if (dsmp->dsm_state != SELECTING) 356 return (B_TRUE); 357 } 358 return (B_FALSE); 359 } 360