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