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 (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); 173 (void) add_pkt_prl(dpkt, dsmp); 174 175 if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, 176 DF_REQUEST_HOSTNAME)) { 177 dhcpmsg(MSG_DEBUG, 178 "dhcp_selecting: DF_REQUEST_HOSTNAME"); 179 (void) snprintf(hostfile, sizeof (hostfile), 180 "/etc/hostname.%s", dsmp->dsm_name); 181 182 if ((reqhost = iffile_to_hostname(hostfile)) != NULL) { 183 dhcpmsg(MSG_DEBUG, "dhcp_selecting: host %s", 184 reqhost); 185 dsmp->dsm_reqhost = strdup(reqhost); 186 if (dsmp->dsm_reqhost != NULL) 187 (void) add_pkt_opt(dpkt, CD_HOSTNAME, 188 dsmp->dsm_reqhost, 189 strlen(dsmp->dsm_reqhost)); 190 else 191 dhcpmsg(MSG_WARNING, 192 "dhcp_selecting: cannot allocate " 193 "memory for host name option"); 194 } 195 } 196 (void) add_pkt_opt(dpkt, CD_END, NULL, 0); 197 198 (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), 199 stop_selecting); 200 } 201 return; 202 203 failed: 204 (void) set_smach_state(dsmp, INIT); 205 dsmp->dsm_dflags |= DHCP_IF_FAILED; 206 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 207 } 208 209 /* 210 * dhcp_collect_dlpi(): collects incoming OFFERs, ACKs, and NAKs via DLPI. 211 * 212 * input: iu_eh_t *: unused 213 * int: the file descriptor the mesage arrived on 214 * short: unused 215 * iu_event_id_t: the id of this event callback with the handler 216 * void *: the physical interface that received the message 217 * output: void 218 */ 219 220 /* ARGSUSED */ 221 void 222 dhcp_collect_dlpi(iu_eh_t *eh, int fd, short events, iu_event_id_t id, 223 void *arg) 224 { 225 dhcp_pif_t *pif = arg; 226 PKT_LIST *plp; 227 uchar_t recv_type; 228 const char *pname; 229 dhcp_smach_t *dsmp; 230 uint_t xid; 231 232 if ((plp = recv_pkt(fd, pif->pif_max, B_FALSE, B_TRUE)) == NULL) 233 return; 234 235 recv_type = pkt_recv_type(plp); 236 pname = pkt_type_to_string(recv_type, B_FALSE); 237 238 /* 239 * DHCP_PUNTYPED messages are BOOTP server responses. 240 */ 241 if (!pkt_v4_match(recv_type, 242 DHCP_PACK | DHCP_PNAK | DHCP_POFFER | DHCP_PUNTYPED)) { 243 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignored %s packet " 244 "received via DLPI on %s", pname, pif->pif_name); 245 free_pkt_entry(plp); 246 return; 247 } 248 249 /* 250 * Loop through the state machines that match on XID to find one that's 251 * interested in this offer. If there are none, then discard. 252 */ 253 xid = pkt_get_xid(plp->pkt, B_FALSE); 254 for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; 255 dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { 256 257 /* 258 * Find state machine on correct interface. 259 */ 260 if (dsmp->dsm_lif->lif_pif == pif) 261 break; 262 } 263 264 if (dsmp == NULL) { 265 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: no matching state " 266 "machine for %s packet XID %#x received via DLPI on %s", 267 pname, xid, pif->pif_name); 268 free_pkt_entry(plp); 269 return; 270 } 271 272 /* 273 * Ignore state machines that aren't looking for DLPI messages. 274 */ 275 if (!dsmp->dsm_using_dlpi) { 276 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignore state " 277 "machine for %s packet XID %#x received via DLPI on %s", 278 pname, xid, pif->pif_name); 279 free_pkt_entry(plp); 280 return; 281 } 282 283 if (pkt_v4_match(recv_type, DHCP_PACK | DHCP_PNAK)) { 284 if (!dhcp_bound(dsmp, plp)) { 285 dhcpmsg(MSG_WARNING, "dhcp_collect_dlpi: dhcp_bound " 286 "failed for %s", dsmp->dsm_name); 287 dhcp_restart(dsmp); 288 return; 289 } 290 dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: %s on %s", 291 pname, dsmp->dsm_name); 292 } else { 293 pkt_smach_enqueue(dsmp, plp); 294 } 295 } 296 297 /* 298 * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when 299 * abandoning the state machine. For DHCPv6, this timer may 300 * go off before the offer wait timer. If so, then this is a 301 * good time to check for valid Advertisements, so cancel the 302 * timer and go check. 303 * 304 * input: dhcp_smach_t *: the state machine DISCOVERs are being sent on 305 * unsigned int: the number of DISCOVERs sent so far 306 * output: boolean_t: B_TRUE if retransmissions should stop 307 */ 308 309 /* ARGSUSED1 */ 310 static boolean_t 311 stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers) 312 { 313 /* 314 * If we're using v4 and the underlying LIF we're trying to configure 315 * has been touched by the user, then bail out. 316 */ 317 if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) { 318 finished_smach(dsmp, DHCP_IPC_E_UNKIF); 319 return (B_TRUE); 320 } 321 322 if (dsmp->dsm_recv_pkt_list != NULL) { 323 dhcp_requesting(NULL, dsmp); 324 if (dsmp->dsm_state != SELECTING) 325 return (B_TRUE); 326 } 327 return (B_FALSE); 328 } 329