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 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * ADOPTING state of the client state machine.  This is used only during
26  * diskless boot with IPv4.
27  */
28 
29 #include <sys/types.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <stdlib.h>
33 #include <signal.h>
34 #include <sys/socket.h>
35 #include <net/if_arp.h>
36 #include <netinet/in.h>
37 #include <sys/systeminfo.h>
38 #include <netinet/inetutil.h>
39 #include <netinet/dhcp.h>
40 #include <dhcpmsg.h>
41 #include <libdevinfo.h>
42 
43 #include "agent.h"
44 #include "async.h"
45 #include "util.h"
46 #include "packet.h"
47 #include "interface.h"
48 #include "states.h"
49 
50 
51 typedef struct {
52 	char		dk_if_name[IFNAMSIZ];
53 	char		dk_ack[1];
54 } dhcp_kcache_t;
55 
56 static int	get_dhcp_kcache(dhcp_kcache_t **, size_t *);
57 
58 static boolean_t	get_prom_prop(const char *, const char *, uchar_t **,
59 			    uint_t *);
60 
61 /*
62  * dhcp_adopt(): adopts the interface managed by the kernel for diskless boot
63  *
64  *   input: void
65  *  output: boolean_t: B_TRUE success, B_FALSE on failure
66  */
67 
68 boolean_t
dhcp_adopt(void)69 dhcp_adopt(void)
70 {
71 	int		retval;
72 	dhcp_kcache_t	*kcache = NULL;
73 	size_t		kcache_size;
74 	PKT_LIST	*plp = NULL;
75 	dhcp_lif_t	*lif;
76 	dhcp_smach_t	*dsmp = NULL;
77 	uint_t		client_id_len;
78 
79 	retval = get_dhcp_kcache(&kcache, &kcache_size);
80 	if (retval == 0 || kcache_size < sizeof (dhcp_kcache_t)) {
81 		dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot fetch kernel cache");
82 		goto failure;
83 	}
84 
85 	dhcpmsg(MSG_DEBUG, "dhcp_adopt: fetched %s kcache", kcache->dk_if_name);
86 
87 	/*
88 	 * convert the kernel's ACK into binary
89 	 */
90 
91 	plp = alloc_pkt_entry(strlen(kcache->dk_ack) / 2, B_FALSE);
92 	if (plp == NULL)
93 		goto failure;
94 
95 	dhcpmsg(MSG_DEBUG, "dhcp_adopt: allocated ACK of %d bytes", plp->len);
96 
97 	if (hexascii_to_octet(kcache->dk_ack, plp->len * 2, plp->pkt,
98 	    &plp->len) != 0) {
99 		dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot convert kernel ACK");
100 		goto failure;
101 	}
102 
103 	if (dhcp_options_scan(plp, B_TRUE) != 0) {
104 		dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot parse kernel ACK");
105 		goto failure;
106 	}
107 
108 	/*
109 	 * make an interface to represent the "cached interface" in
110 	 * the kernel, hook up the ACK packet we made, and send out
111 	 * the extend request (to attempt to renew the lease).
112 	 *
113 	 * we do a send_extend() instead of doing a dhcp_init_reboot()
114 	 * because although dhcp_init_reboot() is more correct from a
115 	 * protocol perspective, it introduces a window where a
116 	 * diskless client has no IP address but may need to page in
117 	 * more of this program.  we could mlockall(), but that's
118 	 * going to be a mess, especially with handling malloc() and
119 	 * stack growth, so it's easier to just renew().  the only
120 	 * catch here is that if we are not granted a renewal, we're
121 	 * totally hosed and can only bail out.
122 	 */
123 
124 	if ((lif = attach_lif(kcache->dk_if_name, B_FALSE, &retval)) == NULL) {
125 		dhcpmsg(MSG_ERROR, "dhcp_adopt: unable to attach %s: %d",
126 		    kcache->dk_if_name, retval);
127 		goto failure;
128 	}
129 
130 	if ((dsmp = insert_smach(lif, &retval)) == NULL) {
131 		dhcpmsg(MSG_ERROR, "dhcp_adopt: unable to create state "
132 		    "machine for %s: %d", kcache->dk_if_name, retval);
133 		goto failure;
134 	}
135 
136 	/*
137 	 * If the agent is adopting a lease, then OBP is initially
138 	 * searched for a client-id.
139 	 */
140 
141 	dhcpmsg(MSG_DEBUG, "dhcp_adopt: getting /chosen:clientid property");
142 
143 	client_id_len = 0;
144 	if (!get_prom_prop("chosen", "client-id", &dsmp->dsm_cid,
145 	    &client_id_len)) {
146 		/*
147 		 * a failure occurred trying to acquire the client-id
148 		 */
149 
150 		dhcpmsg(MSG_DEBUG,
151 		    "dhcp_adopt: cannot allocate client id for %s",
152 		    dsmp->dsm_name);
153 		goto failure;
154 	} else if (dsmp->dsm_hwtype == ARPHRD_IB && dsmp->dsm_cid == NULL) {
155 		/*
156 		 * when the interface is infiniband and the agent
157 		 * is adopting the lease there must be an OBP
158 		 * client-id.
159 		 */
160 
161 		dhcpmsg(MSG_DEBUG, "dhcp_adopt: no /chosen:clientid id for %s",
162 		    dsmp->dsm_name);
163 		goto failure;
164 	}
165 
166 	dsmp->dsm_cidlen = client_id_len;
167 
168 	if (set_lif_dhcp(lif) != DHCP_IPC_SUCCESS)
169 		goto failure;
170 
171 	if (!set_smach_state(dsmp, ADOPTING))
172 		goto failure;
173 	dsmp->dsm_dflags = DHCP_IF_PRIMARY;
174 
175 	/*
176 	 * move to BOUND and use the information in our ACK packet.
177 	 * adoption will continue after DAD via dhcp_adopt_complete.
178 	 */
179 
180 	if (!dhcp_bound(dsmp, plp)) {
181 		dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot use cached packet");
182 		goto failure;
183 	}
184 
185 	free(kcache);
186 	return (B_TRUE);
187 
188 failure:
189 	/* Note: no need to free lif; dsmp holds reference */
190 	if (dsmp != NULL)
191 		remove_smach(dsmp);
192 	free(kcache);
193 	free_pkt_entry(plp);
194 	return (B_FALSE);
195 }
196 
197 /*
198  * dhcp_adopt_complete(): completes interface adoption process after kernel
199  *			  duplicate address detection (DAD) is done.
200  *
201  *   input: dhcp_smach_t *: the state machine on which a lease is being adopted
202  *  output: none
203  */
204 
205 void
dhcp_adopt_complete(dhcp_smach_t * dsmp)206 dhcp_adopt_complete(dhcp_smach_t *dsmp)
207 {
208 	dhcpmsg(MSG_DEBUG, "dhcp_adopt_complete: completing adoption");
209 
210 	if (async_start(dsmp, DHCP_EXTEND, B_FALSE) == 0) {
211 		dhcpmsg(MSG_CRIT, "dhcp_adopt_complete: async_start failed");
212 		return;
213 	}
214 
215 	if (dhcp_extending(dsmp) == 0) {
216 		dhcpmsg(MSG_CRIT,
217 		    "dhcp_adopt_complete: cannot send renew request");
218 		return;
219 	}
220 
221 	if (grandparent != (pid_t)0) {
222 		dhcpmsg(MSG_DEBUG, "adoption complete, signalling parent (%ld)"
223 		    " to exit.", grandparent);
224 		(void) kill(grandparent, SIGALRM);
225 	}
226 }
227 
228 /*
229  * get_dhcp_kcache(): fetches the DHCP ACK and interface name from the kernel
230  *
231  *   input: dhcp_kcache_t **: a dynamically-allocated cache packet
232  *	    size_t *: the length of that packet (on return)
233  *  output: int: nonzero on success, zero on failure
234  */
235 
236 static int
get_dhcp_kcache(dhcp_kcache_t ** kernel_cachep,size_t * kcache_size)237 get_dhcp_kcache(dhcp_kcache_t **kernel_cachep, size_t *kcache_size)
238 {
239 	char	dummy;
240 	long	size;
241 
242 	size = sysinfo(SI_DHCP_CACHE, &dummy, sizeof (dummy));
243 	if (size == -1)
244 		return (0);
245 
246 	*kcache_size   = size;
247 	*kernel_cachep = malloc(*kcache_size);
248 	if (*kernel_cachep == NULL)
249 		return (0);
250 
251 	(void) sysinfo(SI_DHCP_CACHE, (caddr_t)*kernel_cachep, size);
252 	return (1);
253 }
254 
255 /*
256  * get_prom_prop(): get the value of the named property on the named node in
257  *		    devinfo root.
258  *
259  *   input: const char *: The name of the node containing the property.
260  *	    const char *: The name of the property.
261  *	    uchar_t **: The property value, modified iff B_TRUE is returned.
262  *                      If no value is found the value is set to NULL.
263  *	    uint_t *: The length of the property value
264  *  output: boolean_t: Returns B_TRUE if successful (no problems),
265  *                     otherwise B_FALSE.
266  *    note: The memory allocated by this function must be freed by
267  *          the caller.
268  */
269 
270 static boolean_t
get_prom_prop(const char * nodename,const char * propname,uchar_t ** propvaluep,uint_t * lenp)271 get_prom_prop(const char *nodename, const char *propname, uchar_t **propvaluep,
272     uint_t *lenp)
273 {
274 	di_node_t		root_node;
275 	di_node_t		node;
276 	di_prom_handle_t	phdl = DI_PROM_HANDLE_NIL;
277 	di_prom_prop_t		pp;
278 	uchar_t			*value = NULL;
279 	unsigned int		len = 0;
280 	boolean_t		success = B_TRUE;
281 
282 	/*
283 	 * locate root node
284 	 */
285 
286 	if ((root_node = di_init("/", DINFOCPYALL)) == DI_NODE_NIL ||
287 	    (phdl = di_prom_init()) == DI_PROM_HANDLE_NIL) {
288 		dhcpmsg(MSG_DEBUG, "get_prom_prop: property root node "
289 		    "not found");
290 		goto get_prom_prop_cleanup;
291 	}
292 
293 	/*
294 	 * locate nodename within '/'
295 	 */
296 
297 	for (node = di_child_node(root_node);
298 	    node != DI_NODE_NIL;
299 	    node = di_sibling_node(node)) {
300 		if (strcmp(di_node_name(node), nodename) == 0) {
301 			break;
302 		}
303 	}
304 
305 	if (node == DI_NODE_NIL) {
306 		dhcpmsg(MSG_DEBUG, "get_prom_prop: node not found");
307 		goto get_prom_prop_cleanup;
308 	}
309 
310 	/*
311 	 * scan all properties of /nodename for the 'propname' property
312 	 */
313 
314 	for (pp = di_prom_prop_next(phdl, node, DI_PROM_PROP_NIL);
315 	    pp != DI_PROM_PROP_NIL;
316 	    pp = di_prom_prop_next(phdl, node, pp)) {
317 
318 		dhcpmsg(MSG_DEBUG, "get_prom_prop: property = %s",
319 		    di_prom_prop_name(pp));
320 
321 		if (strcmp(propname, di_prom_prop_name(pp)) == 0) {
322 			break;
323 		}
324 	}
325 
326 	if (pp == DI_PROM_PROP_NIL) {
327 		dhcpmsg(MSG_DEBUG, "get_prom_prop: property not found");
328 		goto get_prom_prop_cleanup;
329 	}
330 
331 	/*
332 	 * get the property; allocate some memory copy it out
333 	 */
334 
335 	len = di_prom_prop_data(pp, (uchar_t **)&value);
336 
337 	if (value == NULL) {
338 		/*
339 		 * property data read problems
340 		 */
341 
342 		success = B_FALSE;
343 		dhcpmsg(MSG_ERR, "get_prom_prop: cannot read property data");
344 		goto get_prom_prop_cleanup;
345 	}
346 
347 	if (propvaluep != NULL) {
348 		/*
349 		 * allocate somewhere to copy the property value to
350 		 */
351 
352 		*propvaluep = calloc(len, sizeof (uchar_t));
353 
354 		if (*propvaluep == NULL) {
355 			/*
356 			 * allocation problems
357 			 */
358 
359 			success = B_FALSE;
360 			dhcpmsg(MSG_ERR, "get_prom_prop: cannot allocate "
361 			    "memory for property value");
362 			goto get_prom_prop_cleanup;
363 		}
364 
365 		/*
366 		 * copy data out
367 		 */
368 
369 		(void) memcpy(*propvaluep, value, len);
370 
371 		/*
372 		 * copy out the length if a suitable pointer has
373 		 * been supplied
374 		 */
375 
376 		if (lenp != NULL) {
377 			*lenp = len;
378 		}
379 
380 		dhcpmsg(MSG_DEBUG, "get_prom_prop: property value "
381 		    "length = %d", len);
382 	}
383 
384 get_prom_prop_cleanup:
385 
386 	if (phdl != DI_PROM_HANDLE_NIL) {
387 		di_prom_fini(phdl);
388 	}
389 
390 	if (root_node != DI_NODE_NIL) {
391 		di_fini(root_node);
392 	}
393 
394 	return (success);
395 }
396