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 <strings.h> 33 #include <time.h> 34 #include <limits.h> 35 #include <netinet/in.h> 36 #include <net/route.h> 37 #include <net/if.h> 38 #include <netinet/dhcp.h> 39 #include <netinet/udp.h> 40 #include <netinet/ip_var.h> 41 #include <netinet/udp_var.h> 42 #include <dhcpmsg.h> 43 44 #include "states.h" 45 #include "agent.h" 46 #include "util.h" 47 #include "interface.h" 48 #include "packet.h" 49 #include "defaults.h" 50 51 static stop_func_t stop_selecting; 52 53 /* 54 * dhcp_start(): starts DHCP on a state machine 55 * 56 * input: iu_tq_t *: unused 57 * void *: the state machine on which to start DHCP 58 * output: void 59 */ 60 61 /* ARGSUSED */ 62 void 63 dhcp_start(iu_tq_t *tqp, void *arg) 64 { 65 dhcp_smach_t *dsmp = arg; 66 67 release_smach(dsmp); 68 69 dhcpmsg(MSG_VERBOSE, "starting DHCP on %s", dsmp->dsm_name); 70 dhcp_selecting(dsmp); 71 } 72 73 /* 74 * dhcp_selecting(): sends a DISCOVER and sets up reception of OFFERs for 75 * IPv4, or sends a Solicit and sets up reception of 76 * Advertisements for DHCPv6. 77 * 78 * input: dhcp_smach_t *: the state machine on which to send the DISCOVER 79 * output: void 80 */ 81 82 void 83 dhcp_selecting(dhcp_smach_t *dsmp) 84 { 85 dhcp_pkt_t *dpkt; 86 const char *reqhost; 87 char hostfile[PATH_MAX + 1]; 88 89 /* 90 * We first set up to collect OFFER/Advertise packets as they arrive. 91 * We then send out DISCOVER/Solicit probes. Then we wait a 92 * user-tunable number of seconds before seeing if OFFERs/ 93 * Advertisements have come in response to our DISCOVER/Solicit. If 94 * none have come in, we continue to wait, sending out our DISCOVER/ 95 * Solicit probes with exponential backoff. If no OFFER/Advertisement 96 * is ever received, we will wait forever (note that since we're 97 * event-driven though, we're still able to service other state 98 * machines). 99 * 100 * Note that we do an reset_smach() here because we may be landing in 101 * dhcp_selecting() as a result of restarting DHCP, so the state 102 * machine may not be fresh. 103 */ 104 105 reset_smach(dsmp); 106 if (!set_smach_state(dsmp, SELECTING)) { 107 dhcpmsg(MSG_ERROR, 108 "dhcp_selecting: cannot switch to SELECTING state; " 109 "reverting to INIT on %s", dsmp->dsm_name); 110 goto failed; 111 112 } 113 114 dsmp->dsm_offer_timer = iu_schedule_timer(tq, 115 dsmp->dsm_offer_wait, dhcp_requesting, dsmp); 116 if (dsmp->dsm_offer_timer == -1) { 117 dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read " 118 "%s packets", dsmp->dsm_isv6 ? "Advertise" : "OFFER"); 119 goto failed; 120 } 121 122 hold_smach(dsmp); 123 124 /* 125 * Assemble and send the DHCPDISCOVER or Solicit message. 126 * 127 * If this fails, we'll wait for the select timer to go off 128 * before trying again. 129 */ 130 if (dsmp->dsm_isv6) { 131 dhcpv6_ia_na_t d6in; 132 133 if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_SOLICIT)) == NULL) { 134 dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up " 135 "Solicit packet"); 136 return; 137 } 138 139 /* Add an IA_NA option for our controlling LIF */ 140 d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); 141 d6in.d6in_t1 = htonl(0); 142 d6in.d6in_t2 = htonl(0); 143 (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, 144 (dhcpv6_option_t *)&d6in + 1, 145 sizeof (d6in) - sizeof (dhcpv6_option_t)); 146 147 /* Option Request option for desired information */ 148 (void) add_pkt_prl(dpkt, dsmp); 149 150 /* Enable Rapid-Commit */ 151 (void) add_pkt_opt(dpkt, DHCPV6_OPT_RAPID_COMMIT, NULL, 0); 152 153 /* xxx add Reconfigure Accept */ 154 155 (void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers, 156 stop_selecting, DHCPV6_SOL_TIMEOUT, DHCPV6_SOL_MAX_RT); 157 } else { 158 if ((dpkt = init_pkt(dsmp, DISCOVER)) == NULL) { 159 dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up " 160 "DISCOVER packet"); 161 return; 162 } 163 164 /* 165 * The max DHCP message size option is set to the interface 166 * MTU, minus the size of the UDP and IP headers. 167 */ 168 (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, 169 htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr))); 170 (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM)); 171 172 if (class_id_len != 0) { 173 (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, 174 class_id_len); 175 } 176 (void) add_pkt_prl(dpkt, dsmp); 177 178 if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, 179 DF_REQUEST_HOSTNAME)) { 180 dhcpmsg(MSG_DEBUG, 181 "dhcp_selecting: DF_REQUEST_HOSTNAME"); 182 (void) snprintf(hostfile, sizeof (hostfile), 183 "/etc/hostname.%s", dsmp->dsm_name); 184 185 if ((reqhost = iffile_to_hostname(hostfile)) != NULL) { 186 dhcpmsg(MSG_DEBUG, "dhcp_selecting: host %s", 187 reqhost); 188 dsmp->dsm_reqhost = strdup(reqhost); 189 if (dsmp->dsm_reqhost != NULL) 190 (void) add_pkt_opt(dpkt, CD_HOSTNAME, 191 dsmp->dsm_reqhost, 192 strlen(dsmp->dsm_reqhost)); 193 else 194 dhcpmsg(MSG_WARNING, 195 "dhcp_selecting: cannot allocate " 196 "memory for host name option"); 197 } 198 } 199 (void) add_pkt_opt(dpkt, CD_END, NULL, 0); 200 201 (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), 202 stop_selecting); 203 } 204 return; 205 206 failed: 207 (void) set_smach_state(dsmp, INIT); 208 dsmp->dsm_dflags |= DHCP_IF_FAILED; 209 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 210 } 211 212 /* 213 * dhcp_collect_dlpi(): collects incoming OFFERs, ACKs, and NAKs via DLPI. 214 * 215 * input: iu_eh_t *: unused 216 * int: the file descriptor the mesage arrived on 217 * short: unused 218 * iu_event_id_t: the id of this event callback with the handler 219 * void *: the physical interface that received the message 220 * output: void 221 */ 222 223 /* ARGSUSED */ 224 void 225 dhcp_collect_dlpi(iu_eh_t *eh, int fd, short events, iu_event_id_t id, 226 void *arg) 227 { 228 dhcp_pif_t *pif = arg; 229 PKT_LIST *plp; 230 uchar_t recv_type; 231 const char *pname; 232 dhcp_smach_t *dsmp; 233 uint_t xid; 234 235 if ((plp = recv_pkt(fd, pif->pif_max, B_FALSE, B_TRUE)) == NULL) 236 return; 237 238 recv_type = pkt_recv_type(plp); 239 pname = pkt_type_to_string(recv_type, B_FALSE); 240 241 /* 242 * DHCP_PUNTYPED messages are BOOTP server responses. 243 */ 244 if (!pkt_v4_match(recv_type, 245 DHCP_PACK | DHCP_PNAK | DHCP_POFFER | DHCP_PUNTYPED)) { 246 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignored %s packet " 247 "received via DLPI on %s", pname, pif->pif_name); 248 free_pkt_entry(plp); 249 return; 250 } 251 252 /* 253 * Loop through the state machines that match on XID to find one that's 254 * interested in this offer. If there are none, then discard. 255 */ 256 xid = pkt_get_xid(plp->pkt, B_FALSE); 257 for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; 258 dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { 259 260 /* 261 * Find state machine on correct interface. 262 */ 263 if (dsmp->dsm_lif->lif_pif == pif) 264 break; 265 } 266 267 if (dsmp == NULL) { 268 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: no matching state " 269 "machine for %s packet XID %#x received via DLPI on %s", 270 pname, xid, pif->pif_name); 271 free_pkt_entry(plp); 272 return; 273 } 274 275 /* 276 * Ignore state machines that aren't looking for DLPI messages. 277 */ 278 if (!dsmp->dsm_using_dlpi) { 279 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignore state " 280 "machine for %s packet XID %#x received via DLPI on %s", 281 pname, xid, pif->pif_name); 282 free_pkt_entry(plp); 283 return; 284 } 285 286 if (pkt_v4_match(recv_type, DHCP_PACK)) { 287 if (!dhcp_bound(dsmp, plp)) { 288 dhcpmsg(MSG_WARNING, "dhcp_collect_dlpi: dhcp_bound " 289 "failed for %s", dsmp->dsm_name); 290 dhcp_restart(dsmp); 291 return; 292 } 293 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: %s on %s", 294 pname, dsmp->dsm_name); 295 } else if (pkt_v4_match(recv_type, DHCP_PNAK)) { 296 free_pkt_entry(plp); 297 dhcp_restart(dsmp); 298 } else { 299 pkt_smach_enqueue(dsmp, plp); 300 } 301 } 302 303 /* 304 * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when 305 * abandoning the state machine. For DHCPv6, this timer may 306 * go off before the offer wait timer. If so, then this is a 307 * good time to check for valid Advertisements, so cancel the 308 * timer and go check. 309 * 310 * input: dhcp_smach_t *: the state machine DISCOVERs are being sent on 311 * unsigned int: the number of DISCOVERs sent so far 312 * output: boolean_t: B_TRUE if retransmissions should stop 313 */ 314 315 /* ARGSUSED1 */ 316 static boolean_t 317 stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers) 318 { 319 /* 320 * If we're using v4 and the underlying LIF we're trying to configure 321 * has been touched by the user, then bail out. 322 */ 323 if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) { 324 finished_smach(dsmp, DHCP_IPC_E_UNKIF); 325 return (B_TRUE); 326 } 327 328 if (dsmp->dsm_recv_pkt_list != NULL) { 329 dhcp_requesting(NULL, dsmp); 330 if (dsmp->dsm_state != SELECTING) 331 return (B_TRUE); 332 } 333 return (B_FALSE); 334 } 335