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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  *
26  * SELECTING state of the client state machine.
27  */
28 
29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
30 
31 #include <sys/types.h>
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <strings.h>
35 #include <time.h>
36 #include <limits.h>
37 #include <netinet/in.h>
38 #include <sys/socket.h>
39 #include <net/route.h>
40 #include <net/if.h>
41 #include <netinet/dhcp.h>
42 #include <netinet/udp.h>
43 #include <netinet/ip_var.h>
44 #include <netinet/udp_var.h>
45 #include <stropts.h>				/* FLUSHR/FLUSHW */
46 #include <dhcpmsg.h>
47 
48 #include "states.h"
49 #include "agent.h"
50 #include "util.h"
51 #include "interface.h"
52 #include "packet.h"
53 #include "defaults.h"
54 
55 static iu_eh_callback_t	dhcp_collect_offers;
56 static stop_func_t	stop_selecting;
57 
58 /*
59  * dhcp_start(): starts DHCP on an interface
60  *
61  *   input: iu_tq_t *: unused
62  *	    void *: the interface to start DHCP on
63  *  output: void
64  */
65 
66 /* ARGSUSED */
67 void
68 dhcp_start(iu_tq_t *tqp, void *arg)
69 {
70 	struct ifslist	*ifsp = (struct ifslist *)arg;
71 
72 	if (check_ifs(ifsp) == 0) {
73 		(void) release_ifs(ifsp);
74 		return;
75 	}
76 
77 	dhcpmsg(MSG_VERBOSE, "starting DHCP on %s", ifsp->if_name);
78 	dhcp_selecting(ifsp);
79 }
80 
81 /*
82  * dhcp_selecting(): sends a DISCOVER and sets up reception for an OFFER
83  *
84  *   input: struct ifslist *: the interface to send the DISCOVER on, ...
85  *  output: void
86  */
87 
88 void
89 dhcp_selecting(struct ifslist *ifsp)
90 {
91 	dhcp_pkt_t		*dpkt;
92 	const char		*reqhost;
93 	char			hostfile[PATH_MAX + 1];
94 
95 	/*
96 	 * we first set up to collect OFFER packets as they arrive.
97 	 * we then send out DISCOVER probes.  then we wait at a
98 	 * user-tunable number of seconds before seeing if OFFERs have
99 	 * come in response to our DISCOVER.  if none have come in, we
100 	 * continue to wait, sending out our DISCOVER probes with
101 	 * exponential backoff.  if an OFFER is never received, we
102 	 * will wait forever (note that since we're event-driven
103 	 * though, we're still able to service other interfaces.)
104 	 *
105 	 * note that we do an reset_ifs() here because we may be
106 	 * landing in dhcp_selecting() as a result of restarting DHCP,
107 	 * so the ifs may not be fresh.
108 	 */
109 
110 	reset_ifs(ifsp);
111 	ifsp->if_state = SELECTING;
112 
113 	if ((ifsp->if_offer_id = iu_register_event(eh, ifsp->if_dlpi_fd, POLLIN,
114 	    dhcp_collect_offers, ifsp)) == -1) {
115 
116 		dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot register to collect "
117 		    "OFFER packets, reverting to INIT on %s",
118 		    ifsp->if_name);
119 
120 		ifsp->if_state   = INIT;
121 		ifsp->if_dflags |= DHCP_IF_FAILED;
122 		ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
123 		async_finish(ifsp);
124 		return;
125 	} else
126 		hold_ifs(ifsp);
127 
128 
129 	if (iu_schedule_timer(tq, ifsp->if_offer_wait, dhcp_requesting,
130 		ifsp) == -1) {
131 
132 		dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read "
133 		    "OFFER packets");
134 
135 		if (iu_unregister_event(eh, ifsp->if_offer_id, NULL) != 0) {
136 			ifsp->if_offer_id = -1;
137 			(void) release_ifs(ifsp);
138 		}
139 
140 		ifsp->if_state   = INIT;
141 		ifsp->if_dflags |= DHCP_IF_FAILED;
142 		ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
143 		async_finish(ifsp);
144 		return;
145 	} else
146 		hold_ifs(ifsp);
147 
148 	/*
149 	 * Assemble DHCPDISCOVER message.  The max dhcp message size
150 	 * option is set to the interface max, minus the size of the udp and
151 	 * ip headers.
152 	 */
153 
154 	dpkt = init_pkt(ifsp, DISCOVER);
155 
156 	add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(ifsp->if_max -
157 			sizeof (struct udpiphdr)));
158 	add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
159 
160 	add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len);
161 	add_pkt_opt(dpkt, CD_REQUEST_LIST, ifsp->if_prl, ifsp->if_prllen);
162 
163 	if (df_get_bool(ifsp->if_name, DF_REQUEST_HOSTNAME)) {
164 		dhcpmsg(MSG_DEBUG, "dhcp_selecting: DF_REQUEST_HOSTNAME");
165 		(void) snprintf(hostfile, sizeof (hostfile), "/etc/hostname.%s",
166 		    ifsp->if_name);
167 
168 		if ((reqhost = iffile_to_hostname(hostfile)) != NULL) {
169 			dhcpmsg(MSG_DEBUG, "dhcp_selecting: host %s", reqhost);
170 			if ((ifsp->if_reqhost = strdup(reqhost)) != NULL)
171 				add_pkt_opt(dpkt, CD_HOSTNAME, ifsp->if_reqhost,
172 				    strlen(ifsp->if_reqhost));
173 			else
174 				dhcpmsg(MSG_WARNING, "dhcp_selecting: cannot"
175 				    " allocate memory for host name option");
176 		}
177 	}
178 	add_pkt_opt(dpkt, CD_END, NULL, 0);
179 
180 	(void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), stop_selecting);
181 }
182 
183 /*
184  * dhcp_collect_offers(): collects incoming OFFERs to a DISCOVER
185  *
186  *   input: iu_eh_t *: unused
187  *	    int: the file descriptor the OFFER arrived on
188  *	    short: unused
189  *	    iu_event_id_t: the id of this event callback with the handler
190  *	    void *: the interface that received the OFFER
191  *  output: void
192  */
193 
194 /* ARGSUSED */
195 static void
196 dhcp_collect_offers(iu_eh_t *eh, int fd, short events, iu_event_id_t id,
197     void *arg)
198 {
199 	struct ifslist	*ifsp = (struct ifslist *)arg;
200 
201 	if (verify_ifs(ifsp) == 0) {
202 		(void) ioctl(fd, I_FLUSH, FLUSHR|FLUSHW);
203 		return;
204 	}
205 
206 	/*
207 	 * DHCP_PUNTYPED messages are BOOTP server responses.
208 	 */
209 
210 	(void) recv_pkt(ifsp, fd, DHCP_POFFER|DHCP_PUNTYPED, B_TRUE);
211 }
212 
213 /*
214  * stop_selecting(): decides when to stop retransmitting DISCOVERs (never)
215  *
216  *   input: struct ifslist *: the interface DISCOVERs are being sent on
217  *	    unsigned int: the number of DISCOVERs sent so far
218  *  output: boolean_t: B_TRUE if retransmissions should stop
219  */
220 
221 /* ARGSUSED */
222 static boolean_t
223 stop_selecting(struct ifslist *ifsp, unsigned int n_discovers)
224 {
225 	return (B_FALSE);
226 }
227