Home | History | Annotate | Download | only in dhcpagent
      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