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