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 * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when 239 * abandoning the state machine. For DHCPv6, this timer may 240 * go off before the offer wait timer. If so, then this is a 241 * good time to check for valid Advertisements, so cancel the 242 * timer and go check. 243 * 244 * input: dhcp_smach_t *: the state machine DISCOVERs are being sent on 245 * unsigned int: the number of DISCOVERs sent so far 246 * output: boolean_t: B_TRUE if retransmissions should stop 247 */ 248 249 /* ARGSUSED1 */ 250 static boolean_t 251 stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers) 252 { 253 /* 254 * If we're using v4 and the underlying LIF we're trying to configure 255 * has been touched by the user, then bail out. 256 */ 257 if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) { 258 finished_smach(dsmp, DHCP_IPC_E_UNKIF); 259 return (B_TRUE); 260 } 261 262 if (dsmp->dsm_recv_pkt_list != NULL) { 263 dhcp_requesting(NULL, dsmp); 264 if (dsmp->dsm_state != SELECTING) 265 return (B_TRUE); 266 } 267 return (B_FALSE); 268 } 269