/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * ADOPTING state of the client state machine. This is used only during * diskless boot with IPv4. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "agent.h" #include "async.h" #include "util.h" #include "packet.h" #include "interface.h" #include "states.h" typedef struct { char dk_if_name[IFNAMSIZ]; char dk_ack[1]; } dhcp_kcache_t; static int get_dhcp_kcache(dhcp_kcache_t **, size_t *); static boolean_t get_prom_prop(const char *, const char *, uchar_t **, uint_t *); /* * dhcp_adopt(): adopts the interface managed by the kernel for diskless boot * * input: void * output: boolean_t: B_TRUE success, B_FALSE on failure */ boolean_t dhcp_adopt(void) { int retval; dhcp_kcache_t *kcache = NULL; size_t kcache_size; PKT_LIST *plp = NULL; dhcp_lif_t *lif; dhcp_smach_t *dsmp = NULL; uint_t client_id_len; retval = get_dhcp_kcache(&kcache, &kcache_size); if (retval == 0 || kcache_size < sizeof (dhcp_kcache_t)) { dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot fetch kernel cache"); goto failure; } dhcpmsg(MSG_DEBUG, "dhcp_adopt: fetched %s kcache", kcache->dk_if_name); /* * convert the kernel's ACK into binary */ plp = alloc_pkt_entry(strlen(kcache->dk_ack) / 2, B_FALSE); if (plp == NULL) goto failure; dhcpmsg(MSG_DEBUG, "dhcp_adopt: allocated ACK of %d bytes", plp->len); if (hexascii_to_octet(kcache->dk_ack, plp->len * 2, plp->pkt, &plp->len) != 0) { dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot convert kernel ACK"); goto failure; } if (dhcp_options_scan(plp, B_TRUE) != 0) { dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot parse kernel ACK"); goto failure; } /* * make an interface to represent the "cached interface" in * the kernel, hook up the ACK packet we made, and send out * the extend request (to attempt to renew the lease). * * we do a send_extend() instead of doing a dhcp_init_reboot() * because although dhcp_init_reboot() is more correct from a * protocol perspective, it introduces a window where a * diskless client has no IP address but may need to page in * more of this program. we could mlockall(), but that's * going to be a mess, especially with handling malloc() and * stack growth, so it's easier to just renew(). the only * catch here is that if we are not granted a renewal, we're * totally hosed and can only bail out. */ if ((lif = attach_lif(kcache->dk_if_name, B_FALSE, &retval)) == NULL) { dhcpmsg(MSG_ERROR, "dhcp_adopt: unable to attach %s: %d", kcache->dk_if_name, retval); goto failure; } if ((dsmp = insert_smach(lif, &retval)) == NULL) { dhcpmsg(MSG_ERROR, "dhcp_adopt: unable to create state " "machine for %s: %d", kcache->dk_if_name, retval); goto failure; } /* * If the agent is adopting a lease, then OBP is initially * searched for a client-id. */ dhcpmsg(MSG_DEBUG, "dhcp_adopt: getting /chosen:clientid property"); client_id_len = 0; if (!get_prom_prop("chosen", "client-id", &dsmp->dsm_cid, &client_id_len)) { /* * a failure occurred trying to acquire the client-id */ dhcpmsg(MSG_DEBUG, "dhcp_adopt: cannot allocate client id for %s", dsmp->dsm_name); goto failure; } else if (dsmp->dsm_hwtype == ARPHRD_IB && dsmp->dsm_cid == NULL) { /* * when the interface is infiniband and the agent * is adopting the lease there must be an OBP * client-id. */ dhcpmsg(MSG_DEBUG, "dhcp_adopt: no /chosen:clientid id for %s", dsmp->dsm_name); goto failure; } dsmp->dsm_cidlen = client_id_len; if (set_lif_dhcp(lif, B_TRUE) != DHCP_IPC_SUCCESS) goto failure; if (!set_smach_state(dsmp, ADOPTING)) goto failure; dsmp->dsm_dflags = DHCP_IF_PRIMARY; /* * move to BOUND and use the information in our ACK packet. * adoption will continue after DAD via dhcp_adopt_complete. */ if (!dhcp_bound(dsmp, plp)) { dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot use cached packet"); goto failure; } free(kcache); return (B_TRUE); failure: /* Note: no need to free lif; dsmp holds reference */ if (dsmp != NULL) release_smach(dsmp); free(kcache); free_pkt_entry(plp); return (B_FALSE); } /* * dhcp_adopt_complete(): completes interface adoption process after kernel * duplicate address detection (DAD) is done. * * input: dhcp_smach_t *: the state machine on which a lease is being adopted * output: none */ void dhcp_adopt_complete(dhcp_smach_t *dsmp) { dhcpmsg(MSG_DEBUG, "dhcp_adopt_complete: completing adoption"); if (async_start(dsmp, DHCP_EXTEND, B_FALSE) == 0) { dhcpmsg(MSG_CRIT, "dhcp_adopt_complete: async_start failed"); return; } if (dhcp_extending(dsmp) == 0) { dhcpmsg(MSG_CRIT, "dhcp_adopt_complete: cannot send renew request"); return; } if (grandparent != (pid_t)0) { dhcpmsg(MSG_DEBUG, "adoption complete, signalling parent (%ld)" " to exit.", grandparent); (void) kill(grandparent, SIGALRM); } } /* * get_dhcp_kcache(): fetches the DHCP ACK and interface name from the kernel * * input: dhcp_kcache_t **: a dynamically-allocated cache packet * size_t *: the length of that packet (on return) * output: int: nonzero on success, zero on failure */ static int get_dhcp_kcache(dhcp_kcache_t **kernel_cachep, size_t *kcache_size) { char dummy; long size; size = sysinfo(SI_DHCP_CACHE, &dummy, sizeof (dummy)); if (size == -1) return (0); *kcache_size = size; *kernel_cachep = malloc(*kcache_size); if (*kernel_cachep == NULL) return (0); (void) sysinfo(SI_DHCP_CACHE, (caddr_t)*kernel_cachep, size); return (1); } /* * get_prom_prop(): get the value of the named property on the named node in * devinfo root. * * input: const char *: The name of the node containing the property. * const char *: The name of the property. * uchar_t **: The property value, modified iff B_TRUE is returned. * If no value is found the value is set to NULL. * uint_t *: The length of the property value * output: boolean_t: Returns B_TRUE if successful (no problems), * otherwise B_FALSE. * note: The memory allocated by this function must be freed by * the caller. This code is derived from * usr/src/lib/libwanboot/common/bootinfo_aux.c. */ static boolean_t get_prom_prop(const char *nodename, const char *propname, uchar_t **propvaluep, uint_t *lenp) { di_node_t root_node; di_node_t node; di_prom_handle_t phdl = DI_PROM_HANDLE_NIL; di_prom_prop_t pp; uchar_t *value = NULL; unsigned int len = 0; boolean_t success = B_TRUE; /* * locate root node */ if ((root_node = di_init("/", DINFOCPYALL)) == DI_NODE_NIL || (phdl = di_prom_init()) == DI_PROM_HANDLE_NIL) { dhcpmsg(MSG_DEBUG, "get_prom_prop: property root node " "not found"); goto get_prom_prop_cleanup; } /* * locate nodename within '/' */ for (node = di_child_node(root_node); node != DI_NODE_NIL; node = di_sibling_node(node)) { if (strcmp(di_node_name(node), nodename) == 0) { break; } } if (node == DI_NODE_NIL) { dhcpmsg(MSG_DEBUG, "get_prom_prop: node not found"); goto get_prom_prop_cleanup; } /* * scan all properties of /nodename for the 'propname' property */ for (pp = di_prom_prop_next(phdl, node, DI_PROM_PROP_NIL); pp != DI_PROM_PROP_NIL; pp = di_prom_prop_next(phdl, node, pp)) { dhcpmsg(MSG_DEBUG, "get_prom_prop: property = %s", di_prom_prop_name(pp)); if (strcmp(propname, di_prom_prop_name(pp)) == 0) { break; } } if (pp == DI_PROM_PROP_NIL) { dhcpmsg(MSG_DEBUG, "get_prom_prop: property not found"); goto get_prom_prop_cleanup; } /* * get the property; allocate some memory copy it out */ len = di_prom_prop_data(pp, (uchar_t **)&value); if (value == NULL) { /* * property data read problems */ success = B_FALSE; dhcpmsg(MSG_ERR, "get_prom_prop: cannot read property data"); goto get_prom_prop_cleanup; } if (propvaluep != NULL) { /* * allocate somewhere to copy the property value to */ *propvaluep = calloc(len, sizeof (uchar_t)); if (*propvaluep == NULL) { /* * allocation problems */ success = B_FALSE; dhcpmsg(MSG_ERR, "get_prom_prop: cannot allocate " "memory for property value"); goto get_prom_prop_cleanup; } /* * copy data out */ (void) memcpy(*propvaluep, value, len); /* * copy out the length if a suitable pointer has * been supplied */ if (lenp != NULL) { *lenp = len; } dhcpmsg(MSG_DEBUG, "get_prom_prop: property value " "length = %d", len); } get_prom_prop_cleanup: if (phdl != DI_PROM_HANDLE_NIL) { di_prom_fini(phdl); } if (root_node != DI_NODE_NIL) { di_fini(root_node); } return (success); }