xref: /onnv-gate/usr/src/cmd/cmd-inet/sbin/dhcpagent/states.c (revision 9633:309290318ad0)
13431Scarlsonj /*
23431Scarlsonj  * CDDL HEADER START
33431Scarlsonj  *
43431Scarlsonj  * The contents of this file are subject to the terms of the
53431Scarlsonj  * Common Development and Distribution License (the "License").
63431Scarlsonj  * You may not use this file except in compliance with the License.
73431Scarlsonj  *
83431Scarlsonj  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
93431Scarlsonj  * or http://www.opensolaris.org/os/licensing.
103431Scarlsonj  * See the License for the specific language governing permissions
113431Scarlsonj  * and limitations under the License.
123431Scarlsonj  *
133431Scarlsonj  * When distributing Covered Code, include this CDDL HEADER in each
143431Scarlsonj  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
153431Scarlsonj  * If applicable, add the following below this CDDL HEADER, with the
163431Scarlsonj  * fields enclosed by brackets "[]" replaced with your own identifying
173431Scarlsonj  * information: Portions Copyright [yyyy] [name of copyright owner]
183431Scarlsonj  *
193431Scarlsonj  * CDDL HEADER END
203431Scarlsonj  */
213431Scarlsonj /*
228485SPeter.Memishian@Sun.COM  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
233431Scarlsonj  * Use is subject to license terms.
243431Scarlsonj  *
253431Scarlsonj  * This module contains core functions for managing DHCP state machine
263431Scarlsonj  * instances.
273431Scarlsonj  */
283431Scarlsonj 
299508SPeter.Memishian@Sun.COM #include <assert.h>
303431Scarlsonj #include <stdlib.h>
313431Scarlsonj #include <search.h>
323431Scarlsonj #include <string.h>
333431Scarlsonj #include <ctype.h>
343431Scarlsonj #include <sys/types.h>
353431Scarlsonj #include <sys/socket.h>
363431Scarlsonj #include <netinet/in.h>
373431Scarlsonj #include <netinet/arp.h>
383431Scarlsonj #include <arpa/inet.h>
393431Scarlsonj #include <dhcpmsg.h>
403431Scarlsonj #include <dhcpagent_util.h>
413431Scarlsonj #include <dhcp_stable.h>
42*9633Sjames.d.carlson@sun.com #include <dhcp_inittab.h>
433431Scarlsonj 
443431Scarlsonj #include "agent.h"
453431Scarlsonj #include "states.h"
463431Scarlsonj #include "interface.h"
473431Scarlsonj #include "defaults.h"
483431Scarlsonj #include "script_handler.h"
493431Scarlsonj 
503431Scarlsonj static uint_t global_smach_count;
513431Scarlsonj 
523431Scarlsonj static uchar_t *global_duid;
533431Scarlsonj static size_t global_duidlen;
543431Scarlsonj 
553431Scarlsonj /*
563431Scarlsonj  * iaid_retry(): attempt to write LIF IAID again
573431Scarlsonj  *
583431Scarlsonj  *   input: iu_tq_t *: ignored
593431Scarlsonj  *	    void *: pointer to LIF
603431Scarlsonj  *  output: none
613431Scarlsonj  */
623431Scarlsonj 
633431Scarlsonj /* ARGSUSED */
643431Scarlsonj static void
653431Scarlsonj iaid_retry(iu_tq_t *tqp, void *arg)
663431Scarlsonj {
673431Scarlsonj 	dhcp_lif_t *lif = arg;
683431Scarlsonj 
693431Scarlsonj 	if (write_stable_iaid(lif->lif_name, lif->lif_iaid) == -1) {
703431Scarlsonj 		if (errno != EROFS) {
713431Scarlsonj 			dhcpmsg(MSG_ERR,
723431Scarlsonj 			    "iaid_retry: unable to write out IAID for %s",
733431Scarlsonj 			    lif->lif_name);
743431Scarlsonj 			release_lif(lif);
753431Scarlsonj 		} else {
763431Scarlsonj 			lif->lif_iaid_id = iu_schedule_timer(tq, 60,
773431Scarlsonj 			    iaid_retry, lif);
783431Scarlsonj 		}
793431Scarlsonj 	} else {
803431Scarlsonj 		release_lif(lif);
813431Scarlsonj 	}
823431Scarlsonj }
833431Scarlsonj 
843431Scarlsonj /*
85*9633Sjames.d.carlson@sun.com  * parse_param_list(): parse a parameter list.
86*9633Sjames.d.carlson@sun.com  *
87*9633Sjames.d.carlson@sun.com  *   input: const char *: parameter list string with comma-separated entries
88*9633Sjames.d.carlson@sun.com  *	    uint_t *: return parameter; number of entries decoded
89*9633Sjames.d.carlson@sun.com  *	    const char *: name of parameter list for logging purposes
90*9633Sjames.d.carlson@sun.com  *	    dhcp_smach_t *: smach pointer for logging
91*9633Sjames.d.carlson@sun.com  *  output: uint16_t *: allocated array of parameters, or NULL if none.
92*9633Sjames.d.carlson@sun.com  */
93*9633Sjames.d.carlson@sun.com 
94*9633Sjames.d.carlson@sun.com static uint16_t *
95*9633Sjames.d.carlson@sun.com parse_param_list(const char *param_list, uint_t *param_cnt,
96*9633Sjames.d.carlson@sun.com     const char *param_name, dhcp_smach_t *dsmp)
97*9633Sjames.d.carlson@sun.com {
98*9633Sjames.d.carlson@sun.com 	int i, maxparam;
99*9633Sjames.d.carlson@sun.com 	char tsym[DSYM_MAX_SYM_LEN + 1];
100*9633Sjames.d.carlson@sun.com 	uint16_t *params;
101*9633Sjames.d.carlson@sun.com 	const char *cp;
102*9633Sjames.d.carlson@sun.com 	dhcp_symbol_t *entry;
103*9633Sjames.d.carlson@sun.com 
104*9633Sjames.d.carlson@sun.com 	*param_cnt = 0;
105*9633Sjames.d.carlson@sun.com 
106*9633Sjames.d.carlson@sun.com 	if (param_list == NULL)
107*9633Sjames.d.carlson@sun.com 		return (NULL);
108*9633Sjames.d.carlson@sun.com 
109*9633Sjames.d.carlson@sun.com 	for (maxparam = 1, i = 0; param_list[i] != '\0'; i++) {
110*9633Sjames.d.carlson@sun.com 		if (param_list[i] == ',')
111*9633Sjames.d.carlson@sun.com 			maxparam++;
112*9633Sjames.d.carlson@sun.com 	}
113*9633Sjames.d.carlson@sun.com 
114*9633Sjames.d.carlson@sun.com 	params = malloc(maxparam * sizeof (*params));
115*9633Sjames.d.carlson@sun.com 	if (params == NULL) {
116*9633Sjames.d.carlson@sun.com 		dhcpmsg(MSG_WARNING,
117*9633Sjames.d.carlson@sun.com 		    "cannot allocate parameter %s list for %s (continuing)",
118*9633Sjames.d.carlson@sun.com 		    param_name, dsmp->dsm_name);
119*9633Sjames.d.carlson@sun.com 		return (NULL);
120*9633Sjames.d.carlson@sun.com 	}
121*9633Sjames.d.carlson@sun.com 
122*9633Sjames.d.carlson@sun.com 	for (i = 0; i < maxparam; ) {
123*9633Sjames.d.carlson@sun.com 
124*9633Sjames.d.carlson@sun.com 		if (isspace(*param_list))
125*9633Sjames.d.carlson@sun.com 			param_list++;
126*9633Sjames.d.carlson@sun.com 
127*9633Sjames.d.carlson@sun.com 		/* extract the next element on the list */
128*9633Sjames.d.carlson@sun.com 		cp = strchr(param_list, ',');
129*9633Sjames.d.carlson@sun.com 		if (cp == NULL || cp - param_list >= sizeof (tsym))
130*9633Sjames.d.carlson@sun.com 			(void) strlcpy(tsym, param_list, sizeof (tsym));
131*9633Sjames.d.carlson@sun.com 		else
132*9633Sjames.d.carlson@sun.com 			(void) strlcpy(tsym, param_list, cp - param_list + 1);
133*9633Sjames.d.carlson@sun.com 
134*9633Sjames.d.carlson@sun.com 		/* LINTED -- do nothing with blanks on purpose */
135*9633Sjames.d.carlson@sun.com 		if (tsym[0] == '\0') {
136*9633Sjames.d.carlson@sun.com 			;
137*9633Sjames.d.carlson@sun.com 		} else if (isalpha(tsym[0])) {
138*9633Sjames.d.carlson@sun.com 			entry = inittab_getbyname(ITAB_CAT_SITE |
139*9633Sjames.d.carlson@sun.com 			    ITAB_CAT_STANDARD |
140*9633Sjames.d.carlson@sun.com 			    (dsmp->dsm_isv6 ? ITAB_CAT_V6 : 0),
141*9633Sjames.d.carlson@sun.com 			    ITAB_CONS_INFO, tsym);
142*9633Sjames.d.carlson@sun.com 			if (entry == NULL) {
143*9633Sjames.d.carlson@sun.com 				dhcpmsg(MSG_INFO, "ignored unknown %s list "
144*9633Sjames.d.carlson@sun.com 				    "entry '%s' for %s", param_name, tsym,
145*9633Sjames.d.carlson@sun.com 				    dsmp->dsm_name);
146*9633Sjames.d.carlson@sun.com 			} else {
147*9633Sjames.d.carlson@sun.com 				params[i++] = entry->ds_code;
148*9633Sjames.d.carlson@sun.com 				free(entry);
149*9633Sjames.d.carlson@sun.com 			}
150*9633Sjames.d.carlson@sun.com 		} else {
151*9633Sjames.d.carlson@sun.com 			params[i++] = strtoul(tsym, NULL, 0);
152*9633Sjames.d.carlson@sun.com 		}
153*9633Sjames.d.carlson@sun.com 		if (cp == NULL)
154*9633Sjames.d.carlson@sun.com 			break;
155*9633Sjames.d.carlson@sun.com 		param_list = cp + 1;
156*9633Sjames.d.carlson@sun.com 	}
157*9633Sjames.d.carlson@sun.com 
158*9633Sjames.d.carlson@sun.com 	*param_cnt = i;
159*9633Sjames.d.carlson@sun.com 	return (params);
160*9633Sjames.d.carlson@sun.com }
161*9633Sjames.d.carlson@sun.com 
162*9633Sjames.d.carlson@sun.com /*
1633431Scarlsonj  * insert_smach(): Create a state machine instance on a given logical
1643431Scarlsonj  *		   interface.  The state machine holds the caller's LIF
1653431Scarlsonj  *		   reference on success, and frees it on failure.
1663431Scarlsonj  *
1673431Scarlsonj  *   input: dhcp_lif_t *: logical interface name
1683431Scarlsonj  *	    int *: set to DHCP_IPC_E_* if creation fails
1693431Scarlsonj  *  output: dhcp_smach_t *: state machine instance
1703431Scarlsonj  */
1713431Scarlsonj 
1723431Scarlsonj dhcp_smach_t *
1733431Scarlsonj insert_smach(dhcp_lif_t *lif, int *error)
1743431Scarlsonj {
1753431Scarlsonj 	dhcp_smach_t *dsmp, *alt_primary;
1763431Scarlsonj 	boolean_t isv6;
177*9633Sjames.d.carlson@sun.com 	const char *plist;
1783431Scarlsonj 
1793431Scarlsonj 	if ((dsmp = calloc(1, sizeof (*dsmp))) == NULL) {
1803431Scarlsonj 		dhcpmsg(MSG_ERR, "cannot allocate state machine entry for %s",
1813431Scarlsonj 		    lif->lif_name);
1823431Scarlsonj 		remove_lif(lif);
1833431Scarlsonj 		release_lif(lif);
1843431Scarlsonj 		*error = DHCP_IPC_E_MEMORY;
1853431Scarlsonj 		return (NULL);
1863431Scarlsonj 	}
1873431Scarlsonj 	dsmp->dsm_name = lif->lif_name;
1883431Scarlsonj 	dsmp->dsm_lif = lif;
1893431Scarlsonj 	dsmp->dsm_hold_count = 1;
1903431Scarlsonj 	dsmp->dsm_state = INIT;
1913431Scarlsonj 	dsmp->dsm_dflags = DHCP_IF_REMOVED;	/* until added to list */
1923431Scarlsonj 	isv6 = lif->lif_pif->pif_isv6;
1933431Scarlsonj 
1943431Scarlsonj 	/*
1953431Scarlsonj 	 * Now that we have a controlling LIF, we need to assign an IAID to
1963431Scarlsonj 	 * that LIF.
1973431Scarlsonj 	 */
1983431Scarlsonj 	if (lif->lif_iaid == 0 &&
1993431Scarlsonj 	    (lif->lif_iaid = read_stable_iaid(lif->lif_name)) == 0) {
2003431Scarlsonj 		static uint32_t iaidctr = 0x80000000u;
2013431Scarlsonj 
2023431Scarlsonj 		/*
2033431Scarlsonj 		 * If this is a logical interface, then use an arbitrary seed
2043431Scarlsonj 		 * value.  Otherwise, use the ifIndex.
2053431Scarlsonj 		 */
2063431Scarlsonj 		lif->lif_iaid = make_stable_iaid(lif->lif_name,
2073431Scarlsonj 		    strchr(lif->lif_name, ':') != NULL ? iaidctr++ :
2083431Scarlsonj 		    lif->lif_pif->pif_index);
2093431Scarlsonj 		dhcpmsg(MSG_INFO,
2103431Scarlsonj 		    "insert_smach: manufactured IAID %u for v%d %s",
2113431Scarlsonj 		    lif->lif_iaid, isv6 ? 6 : 4, lif->lif_name);
2123431Scarlsonj 		hold_lif(lif);
2133431Scarlsonj 		iaid_retry(NULL, lif);
2143431Scarlsonj 	}
2153431Scarlsonj 
2163431Scarlsonj 	if (isv6) {
2173431Scarlsonj 		dsmp->dsm_dflags |= DHCP_IF_V6;
2183431Scarlsonj 		dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers;
2193431Scarlsonj 
2203431Scarlsonj 		/*
2213431Scarlsonj 		 * With DHCPv6, we do all of our I/O using the common
2223431Scarlsonj 		 * v6_sock_fd.  There's no need for per-interface file
2233431Scarlsonj 		 * descriptors because we have IPV6_PKTINFO.
2243431Scarlsonj 		 */
2253431Scarlsonj 	} else {
2263431Scarlsonj 		IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST),
2273431Scarlsonj 		    &dsmp->dsm_server);
2283431Scarlsonj 
2293431Scarlsonj 		/*
2305381Smeem 		 * With IPv4 DHCP, we use a socket per lif.
2313431Scarlsonj 		 */
2328485SPeter.Memishian@Sun.COM 		if (!open_ip_lif(lif, INADDR_ANY, B_TRUE)) {
2335381Smeem 			dhcpmsg(MSG_ERR, "unable to open socket for %s",
2343431Scarlsonj 			    lif->lif_name);
2353431Scarlsonj 			/* This will also dispose of the LIF */
2363431Scarlsonj 			release_smach(dsmp);
2373431Scarlsonj 			*error = DHCP_IPC_E_SOCKET;
2383431Scarlsonj 			return (NULL);
2393431Scarlsonj 		}
2403431Scarlsonj 	}
2419508SPeter.Memishian@Sun.COM 
2429508SPeter.Memishian@Sun.COM 	script_init(dsmp);
2439508SPeter.Memishian@Sun.COM 	ipc_action_init(&dsmp->dsm_ia);
2443431Scarlsonj 
2459508SPeter.Memishian@Sun.COM 	dsmp->dsm_neg_hrtime = gethrtime();
2469508SPeter.Memishian@Sun.COM 	dsmp->dsm_offer_timer = -1;
2479508SPeter.Memishian@Sun.COM 	dsmp->dsm_start_timer = -1;
2489508SPeter.Memishian@Sun.COM 	dsmp->dsm_retrans_timer = -1;
2493431Scarlsonj 
2503431Scarlsonj 	/*
251*9633Sjames.d.carlson@sun.com 	 * Initialize the parameter request and ignore lists, if any.
2523431Scarlsonj 	 */
253*9633Sjames.d.carlson@sun.com 	plist = df_get_string(dsmp->dsm_name, isv6, DF_PARAM_REQUEST_LIST);
254*9633Sjames.d.carlson@sun.com 	dsmp->dsm_prl = parse_param_list(plist, &dsmp->dsm_prllen, "request",
255*9633Sjames.d.carlson@sun.com 	    dsmp);
256*9633Sjames.d.carlson@sun.com 	plist = df_get_string(dsmp->dsm_name, isv6, DF_PARAM_IGNORE_LIST);
257*9633Sjames.d.carlson@sun.com 	dsmp->dsm_pil = parse_param_list(plist, &dsmp->dsm_pillen, "ignore",
258*9633Sjames.d.carlson@sun.com 	    dsmp);
2593431Scarlsonj 
2603431Scarlsonj 	dsmp->dsm_offer_wait = df_get_int(dsmp->dsm_name, isv6,
2613431Scarlsonj 	    DF_OFFER_WAIT);
2623431Scarlsonj 
2633431Scarlsonj 	/*
2643431Scarlsonj 	 * If there is no primary of this type, and there is one of the other,
2653431Scarlsonj 	 * then make this one primary if it's on the same named PIF.
2663431Scarlsonj 	 */
2673431Scarlsonj 	if (primary_smach(isv6) == NULL &&
2683431Scarlsonj 	    (alt_primary = primary_smach(!isv6)) != NULL) {
2693431Scarlsonj 		if (strcmp(lif->lif_pif->pif_name,
2703431Scarlsonj 		    alt_primary->dsm_lif->lif_pif->pif_name) == 0) {
2713431Scarlsonj 			dhcpmsg(MSG_DEBUG,
2723431Scarlsonj 			    "insert_smach: making %s primary for v%d",
2733431Scarlsonj 			    dsmp->dsm_name, isv6 ? 6 : 4);
2743431Scarlsonj 			dsmp->dsm_dflags |= DHCP_IF_PRIMARY;
2753431Scarlsonj 		}
2763431Scarlsonj 	}
2773431Scarlsonj 
2783431Scarlsonj 	/*
2793431Scarlsonj 	 * We now have at least one state machine running, so cancel any
2803431Scarlsonj 	 * running inactivity timer.
2813431Scarlsonj 	 */
2823431Scarlsonj 	if (inactivity_id != -1 &&
2833431Scarlsonj 	    iu_cancel_timer(tq, inactivity_id, NULL) == 1)
2843431Scarlsonj 		inactivity_id = -1;
2853431Scarlsonj 
2863431Scarlsonj 	dsmp->dsm_dflags &= ~DHCP_IF_REMOVED;
2873431Scarlsonj 	insque(dsmp, &lif->lif_smachs);
2883431Scarlsonj 	global_smach_count++;
2893431Scarlsonj 	dhcpmsg(MSG_DEBUG2, "insert_smach: inserted %s", dsmp->dsm_name);
2903431Scarlsonj 
2913431Scarlsonj 	return (dsmp);
2923431Scarlsonj }
2933431Scarlsonj 
2943431Scarlsonj /*
2953431Scarlsonj  * hold_smach(): acquires a hold on a state machine
2963431Scarlsonj  *
2973431Scarlsonj  *   input: dhcp_smach_t *: the state machine to acquire a hold on
2983431Scarlsonj  *  output: void
2993431Scarlsonj  */
3003431Scarlsonj 
3013431Scarlsonj void
3023431Scarlsonj hold_smach(dhcp_smach_t *dsmp)
3033431Scarlsonj {
3043431Scarlsonj 	dsmp->dsm_hold_count++;
3053431Scarlsonj 
3063431Scarlsonj 	dhcpmsg(MSG_DEBUG2, "hold_smach: hold count on %s: %d",
3073431Scarlsonj 	    dsmp->dsm_name, dsmp->dsm_hold_count);
3083431Scarlsonj }
3093431Scarlsonj 
3103431Scarlsonj /*
3113431Scarlsonj  * free_smach(): frees the memory occupied by a state machine
3123431Scarlsonj  *
3133431Scarlsonj  *   input: dhcp_smach_t *: the DHCP state machine to free
3143431Scarlsonj  *  output: void
3153431Scarlsonj  */
3163431Scarlsonj 
3173431Scarlsonj static void
3183431Scarlsonj free_smach(dhcp_smach_t *dsmp)
3193431Scarlsonj {
3203431Scarlsonj 	dhcpmsg(MSG_DEBUG, "free_smach: freeing state machine %s",
3213431Scarlsonj 	    dsmp->dsm_name);
3223431Scarlsonj 
3233431Scarlsonj 	deprecate_leases(dsmp);
3243431Scarlsonj 	remove_lif(dsmp->dsm_lif);
3253431Scarlsonj 	release_lif(dsmp->dsm_lif);
3263431Scarlsonj 	free_pkt_list(&dsmp->dsm_recv_pkt_list);
3273431Scarlsonj 	if (dsmp->dsm_ack != dsmp->dsm_orig_ack)
3283431Scarlsonj 		free_pkt_entry(dsmp->dsm_orig_ack);
3293431Scarlsonj 	free_pkt_entry(dsmp->dsm_ack);
3303431Scarlsonj 	free(dsmp->dsm_send_pkt.pkt);
3313431Scarlsonj 	free(dsmp->dsm_cid);
3323431Scarlsonj 	free(dsmp->dsm_prl);
333*9633Sjames.d.carlson@sun.com 	free(dsmp->dsm_pil);
3343431Scarlsonj 	free(dsmp->dsm_routers);
3353431Scarlsonj 	free(dsmp->dsm_reqhost);
3363431Scarlsonj 	free(dsmp);
3373431Scarlsonj 
3383431Scarlsonj 	/* no big deal if this fails */
3393431Scarlsonj 	if (global_smach_count == 0 && inactivity_id == -1) {
3403431Scarlsonj 		inactivity_id = iu_schedule_timer(tq, DHCP_INACTIVITY_WAIT,
3413431Scarlsonj 		    inactivity_shutdown, NULL);
3423431Scarlsonj 	}
3433431Scarlsonj }
3443431Scarlsonj 
3453431Scarlsonj /*
3463431Scarlsonj  * release_smach(): releases a hold previously acquired on a state machine.
3473431Scarlsonj  *		    If the hold count reaches 0, the state machine is freed.
3483431Scarlsonj  *
3493431Scarlsonj  *   input: dhcp_smach_t *: the state machine entry to release the hold on
3503431Scarlsonj  *  output: void
3513431Scarlsonj  */
3523431Scarlsonj 
3533431Scarlsonj void
3543431Scarlsonj release_smach(dhcp_smach_t *dsmp)
3553431Scarlsonj {
3563431Scarlsonj 	if (dsmp->dsm_hold_count == 0) {
3573431Scarlsonj 		dhcpmsg(MSG_CRIT, "release_smach: extraneous release");
3583431Scarlsonj 		return;
3593431Scarlsonj 	}
3603431Scarlsonj 
3613431Scarlsonj 	if (dsmp->dsm_hold_count == 1 &&
3623431Scarlsonj 	    !(dsmp->dsm_dflags & DHCP_IF_REMOVED)) {
3633431Scarlsonj 		dhcpmsg(MSG_CRIT, "release_smach: missing removal");
3643431Scarlsonj 		return;
3653431Scarlsonj 	}
3663431Scarlsonj 
3673431Scarlsonj 	if (--dsmp->dsm_hold_count == 0) {
3683431Scarlsonj 		free_smach(dsmp);
3693431Scarlsonj 	} else {
3703431Scarlsonj 		dhcpmsg(MSG_DEBUG2, "release_smach: hold count on %s: %d",
3713431Scarlsonj 		    dsmp->dsm_name, dsmp->dsm_hold_count);
3723431Scarlsonj 	}
3733431Scarlsonj }
3743431Scarlsonj 
3753431Scarlsonj /*
3763431Scarlsonj  * next_smach(): state machine iterator function
3773431Scarlsonj  *
3783431Scarlsonj  *   input: dhcp_smach_t *: current state machine (or NULL for list start)
3793431Scarlsonj  *          boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise
3803431Scarlsonj  *  output: dhcp_smach_t *: next state machine in list
3813431Scarlsonj  */
3823431Scarlsonj 
3833431Scarlsonj dhcp_smach_t *
3843431Scarlsonj next_smach(dhcp_smach_t *dsmp, boolean_t isv6)
3853431Scarlsonj {
3863431Scarlsonj 	dhcp_lif_t *lif;
3873431Scarlsonj 	dhcp_pif_t *pif;
3883431Scarlsonj 
3893431Scarlsonj 	if (dsmp != NULL) {
3903431Scarlsonj 		if (dsmp->dsm_next != NULL)
3913431Scarlsonj 			return (dsmp->dsm_next);
3923431Scarlsonj 
3933431Scarlsonj 		if ((lif = dsmp->dsm_lif) != NULL)
3943431Scarlsonj 			lif = lif->lif_next;
3953431Scarlsonj 		for (; lif != NULL; lif = lif->lif_next) {
3963431Scarlsonj 			if (lif->lif_smachs != NULL)
3973431Scarlsonj 				return (lif->lif_smachs);
3983431Scarlsonj 		}
3993431Scarlsonj 
4003431Scarlsonj 		if ((pif = dsmp->dsm_lif->lif_pif) != NULL)
4013431Scarlsonj 			pif = pif->pif_next;
4023431Scarlsonj 	} else {
4033431Scarlsonj 		pif = isv6 ? v6root : v4root;
4043431Scarlsonj 	}
4053431Scarlsonj 	for (; pif != NULL; pif = pif->pif_next) {
4063431Scarlsonj 		for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) {
4073431Scarlsonj 			if (lif->lif_smachs != NULL)
4083431Scarlsonj 				return (lif->lif_smachs);
4093431Scarlsonj 		}
4103431Scarlsonj 	}
4113431Scarlsonj 	return (NULL);
4123431Scarlsonj }
4133431Scarlsonj 
4143431Scarlsonj /*
4153431Scarlsonj  * primary_smach(): loop through all state machines of the given type (v4 or
4163431Scarlsonj  *		    v6) in the system, and locate the one that's primary.
4173431Scarlsonj  *
4183431Scarlsonj  *   input: boolean_t: B_TRUE for IPv6
4193431Scarlsonj  *  output: dhcp_smach_t *: the primary state machine
4203431Scarlsonj  */
4213431Scarlsonj 
4223431Scarlsonj dhcp_smach_t *
4233431Scarlsonj primary_smach(boolean_t isv6)
4243431Scarlsonj {
4253431Scarlsonj 	dhcp_smach_t *dsmp;
4263431Scarlsonj 
4273431Scarlsonj 	for (dsmp = next_smach(NULL, isv6); dsmp != NULL;
4283431Scarlsonj 	    dsmp = next_smach(dsmp, isv6)) {
4293431Scarlsonj 		if (dsmp->dsm_dflags & DHCP_IF_PRIMARY)
4303431Scarlsonj 			break;
4313431Scarlsonj 	}
4323431Scarlsonj 	return (dsmp);
4333431Scarlsonj }
4343431Scarlsonj 
4353431Scarlsonj /*
436*9633Sjames.d.carlson@sun.com  * info_primary_smach(): loop through all state machines of the given type (v4
437*9633Sjames.d.carlson@sun.com  *			 or v6) in the system, and locate the one that should
438*9633Sjames.d.carlson@sun.com  *			 be considered "primary" for dhcpinfo.
439*9633Sjames.d.carlson@sun.com  *
440*9633Sjames.d.carlson@sun.com  *   input: boolean_t: B_TRUE for IPv6
441*9633Sjames.d.carlson@sun.com  *  output: dhcp_smach_t *: the dhcpinfo primary state machine
442*9633Sjames.d.carlson@sun.com  */
443*9633Sjames.d.carlson@sun.com 
444*9633Sjames.d.carlson@sun.com dhcp_smach_t *
445*9633Sjames.d.carlson@sun.com info_primary_smach(boolean_t isv6)
446*9633Sjames.d.carlson@sun.com {
447*9633Sjames.d.carlson@sun.com 	dhcp_smach_t *bestdsm = NULL;
448*9633Sjames.d.carlson@sun.com 	dhcp_smach_t *dsmp;
449*9633Sjames.d.carlson@sun.com 
450*9633Sjames.d.carlson@sun.com 	for (dsmp = next_smach(NULL, isv6); dsmp != NULL;
451*9633Sjames.d.carlson@sun.com 	    dsmp = next_smach(dsmp, isv6)) {
452*9633Sjames.d.carlson@sun.com 		/*
453*9633Sjames.d.carlson@sun.com 		 * If there is a primary, then something previously went wrong
454*9633Sjames.d.carlson@sun.com 		 * with verification, because the caller uses primary_smach()
455*9633Sjames.d.carlson@sun.com 		 * before calling this routine.  There's nothing else we can do
456*9633Sjames.d.carlson@sun.com 		 * but return failure, as the designated primary must be bad.
457*9633Sjames.d.carlson@sun.com 		 */
458*9633Sjames.d.carlson@sun.com 		if (dsmp->dsm_dflags & DHCP_IF_PRIMARY)
459*9633Sjames.d.carlson@sun.com 			return (NULL);
460*9633Sjames.d.carlson@sun.com 
461*9633Sjames.d.carlson@sun.com 		/* If we have no information, then we're not primary. */
462*9633Sjames.d.carlson@sun.com 		if (dsmp->dsm_ack == NULL)
463*9633Sjames.d.carlson@sun.com 			continue;
464*9633Sjames.d.carlson@sun.com 
465*9633Sjames.d.carlson@sun.com 		/*
466*9633Sjames.d.carlson@sun.com 		 * Among those interfaces that have DHCP information, the
467*9633Sjames.d.carlson@sun.com 		 * "primary" is the one that sorts lexically first.
468*9633Sjames.d.carlson@sun.com 		 */
469*9633Sjames.d.carlson@sun.com 		if (bestdsm == NULL ||
470*9633Sjames.d.carlson@sun.com 		    strcmp(dsmp->dsm_name, bestdsm->dsm_name) < 0)
471*9633Sjames.d.carlson@sun.com 			bestdsm = dsmp;
472*9633Sjames.d.carlson@sun.com 	}
473*9633Sjames.d.carlson@sun.com 	return (bestdsm);
474*9633Sjames.d.carlson@sun.com }
475*9633Sjames.d.carlson@sun.com 
476*9633Sjames.d.carlson@sun.com /*
4773431Scarlsonj  * make_primary(): designate a given state machine as being the primary
4783431Scarlsonj  *		   instance on the primary interface.  Note that the user often
4793431Scarlsonj  *		   thinks in terms of a primary "interface" (rather than just
4803431Scarlsonj  *		   an instance), so we go to lengths here to keep v4 and v6 in
4813431Scarlsonj  *		   sync.
4823431Scarlsonj  *
4833431Scarlsonj  *   input: dhcp_smach_t *: the primary state machine
4843431Scarlsonj  *  output: none
4853431Scarlsonj  */
4863431Scarlsonj 
4873431Scarlsonj void
4883431Scarlsonj make_primary(dhcp_smach_t *dsmp)
4893431Scarlsonj {
4903431Scarlsonj 	dhcp_smach_t *old_primary, *alt_primary;
4913431Scarlsonj 	dhcp_pif_t *pif;
4923431Scarlsonj 
4933431Scarlsonj 	if ((old_primary = primary_smach(dsmp->dsm_isv6)) != NULL)
4943431Scarlsonj 		old_primary->dsm_dflags &= ~DHCP_IF_PRIMARY;
4953431Scarlsonj 	dsmp->dsm_dflags |= DHCP_IF_PRIMARY;
4963431Scarlsonj 
4973431Scarlsonj 	/*
4983431Scarlsonj 	 * Find the primary for the other protocol.
4993431Scarlsonj 	 */
5003431Scarlsonj 	alt_primary = primary_smach(!dsmp->dsm_isv6);
5013431Scarlsonj 
5023431Scarlsonj 	/*
5033431Scarlsonj 	 * If it's on a different interface, then cancel that.  If it's on the
5043431Scarlsonj 	 * same interface, then we're done.
5053431Scarlsonj 	 */
5063431Scarlsonj 	if (alt_primary != NULL) {
5073431Scarlsonj 		if (strcmp(alt_primary->dsm_lif->lif_pif->pif_name,
5083431Scarlsonj 		    dsmp->dsm_lif->lif_pif->pif_name) == 0)
5093431Scarlsonj 			return;
5103431Scarlsonj 		alt_primary->dsm_dflags &= ~DHCP_IF_PRIMARY;
5113431Scarlsonj 	}
5123431Scarlsonj 
5133431Scarlsonj 	/*
5143431Scarlsonj 	 * We need a new primary for the other protocol.  If the PIF exists,
5153431Scarlsonj 	 * there must be at least one state machine.  Just choose the first for
5163431Scarlsonj 	 * consistency with insert_smach().
5173431Scarlsonj 	 */
5183431Scarlsonj 	if ((pif = lookup_pif_by_name(dsmp->dsm_lif->lif_pif->pif_name,
5193431Scarlsonj 	    !dsmp->dsm_isv6)) != NULL) {
5203431Scarlsonj 		pif->pif_lifs->lif_smachs->dsm_dflags |= DHCP_IF_PRIMARY;
5213431Scarlsonj 	}
5223431Scarlsonj }
5233431Scarlsonj 
5243431Scarlsonj /*
5253431Scarlsonj  * lookup_smach(): finds a state machine by name and type; used for dispatching
5263431Scarlsonj  *		   user commands.
5273431Scarlsonj  *
5283431Scarlsonj  *   input: const char *: the name of the state machine
5293431Scarlsonj  *          boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise
5303431Scarlsonj  *  output: dhcp_smach_t *: the state machine found
5313431Scarlsonj  */
5323431Scarlsonj 
5333431Scarlsonj dhcp_smach_t *
5343431Scarlsonj lookup_smach(const char *smname, boolean_t isv6)
5353431Scarlsonj {
5363431Scarlsonj 	dhcp_smach_t *dsmp;
5373431Scarlsonj 
5383431Scarlsonj 	for (dsmp = next_smach(NULL, isv6); dsmp != NULL;
5393431Scarlsonj 	    dsmp = next_smach(dsmp, isv6)) {
5403431Scarlsonj 		if (strcmp(dsmp->dsm_name, smname) == 0)
5413431Scarlsonj 			break;
5423431Scarlsonj 	}
5433431Scarlsonj 	return (dsmp);
5443431Scarlsonj }
5453431Scarlsonj 
5463431Scarlsonj /*
5473431Scarlsonj  * lookup_smach_by_uindex(): iterate through running state machines by
5483431Scarlsonj  *			     truncated interface index.
5493431Scarlsonj  *
5503431Scarlsonj  *   input: uint16_t: the interface index (truncated)
5513431Scarlsonj  *	    dhcp_smach_t *: the previous state machine, or NULL for start
5523431Scarlsonj  *	    boolean_t: B_TRUE for DHCPv6, B_FALSE for IPv4 DHCP
5533431Scarlsonj  *  output: dhcp_smach_t *: next state machine, or NULL at end of list
5543431Scarlsonj  */
5553431Scarlsonj 
5563431Scarlsonj dhcp_smach_t *
5573431Scarlsonj lookup_smach_by_uindex(uint16_t ifindex, dhcp_smach_t *dsmp, boolean_t isv6)
5583431Scarlsonj {
5593431Scarlsonj 	dhcp_pif_t *pif;
5603431Scarlsonj 	dhcp_lif_t *lif;
5613431Scarlsonj 
5623431Scarlsonj 	/*
5633431Scarlsonj 	 * If the user gives us a state machine, then check that the next one
5643431Scarlsonj 	 * available is on the same physical interface.  If so, then go ahead
5653431Scarlsonj 	 * and return that.
5663431Scarlsonj 	 */
5673431Scarlsonj 	if (dsmp != NULL) {
5683431Scarlsonj 		pif = dsmp->dsm_lif->lif_pif;
5693431Scarlsonj 		if ((dsmp = next_smach(dsmp, isv6)) == NULL)
5703431Scarlsonj 			return (NULL);
5713431Scarlsonj 		if (pif == dsmp->dsm_lif->lif_pif)
5723431Scarlsonj 			return (dsmp);
5733431Scarlsonj 	} else {
5743431Scarlsonj 		/* Otherwise, start at the beginning of the list */
5753431Scarlsonj 		pif = NULL;
5763431Scarlsonj 	}
5773431Scarlsonj 
5783431Scarlsonj 	/*
5793431Scarlsonj 	 * Find the next physical interface with the same truncated interface
5803431Scarlsonj 	 * index, and return the first state machine on that.  If there are no
5813431Scarlsonj 	 * more physical interfaces that match, then we're done.
5823431Scarlsonj 	 */
5833431Scarlsonj 	do {
5843431Scarlsonj 		pif = lookup_pif_by_uindex(ifindex, pif, isv6);
5853431Scarlsonj 		if (pif == NULL)
5863431Scarlsonj 			return (NULL);
5873431Scarlsonj 		for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) {
5883431Scarlsonj 			if ((dsmp = lif->lif_smachs) != NULL)
5893431Scarlsonj 				break;
5903431Scarlsonj 		}
5913431Scarlsonj 	} while (dsmp == NULL);
5923431Scarlsonj 	return (dsmp);
5933431Scarlsonj }
5943431Scarlsonj 
5953431Scarlsonj /*
5963431Scarlsonj  * lookup_smach_by_xid(): iterate through running state machines by transaction
5973431Scarlsonj  *			  id.  Transaction ID zero means "all state machines."
5983431Scarlsonj  *
5993431Scarlsonj  *   input: uint32_t: the transaction id to look up
6003431Scarlsonj  *	    dhcp_smach_t *: the previous state machine, or NULL for start
6013431Scarlsonj  *	    boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise
6023431Scarlsonj  *  output: dhcp_smach_t *: next state machine, or NULL at end of list
6033431Scarlsonj  */
6043431Scarlsonj 
6053431Scarlsonj dhcp_smach_t *
6063431Scarlsonj lookup_smach_by_xid(uint32_t xid, dhcp_smach_t *dsmp, boolean_t isv6)
6073431Scarlsonj {
6083431Scarlsonj 	for (dsmp = next_smach(dsmp, isv6); dsmp != NULL;
6093431Scarlsonj 	    dsmp = next_smach(dsmp, isv6)) {
6103431Scarlsonj 		if (xid == 0 ||
6113431Scarlsonj 		    pkt_get_xid(dsmp->dsm_send_pkt.pkt, isv6) == xid)
6123431Scarlsonj 			break;
6133431Scarlsonj 	}
6143431Scarlsonj 
6153431Scarlsonj 	return (dsmp);
6163431Scarlsonj }
6173431Scarlsonj 
6183431Scarlsonj /*
6193431Scarlsonj  * lookup_smach_by_event(): find a state machine busy with a particular event
6203431Scarlsonj  *			    ID.  This is used only for error handling.
6213431Scarlsonj  *
6223431Scarlsonj  *   input: iu_event_id_t: the event id to look up
6233431Scarlsonj  *  output: dhcp_smach_t *: matching state machine, or NULL if none
6243431Scarlsonj  */
6253431Scarlsonj 
6263431Scarlsonj dhcp_smach_t *
6273431Scarlsonj lookup_smach_by_event(iu_event_id_t eid)
6283431Scarlsonj {
6293431Scarlsonj 	dhcp_smach_t *dsmp;
6303431Scarlsonj 	boolean_t isv6 = B_FALSE;
6313431Scarlsonj 
6323431Scarlsonj 	for (;;) {
6333431Scarlsonj 		for (dsmp = next_smach(NULL, isv6); dsmp != NULL;
6343431Scarlsonj 		    dsmp = next_smach(dsmp, isv6)) {
6353431Scarlsonj 			if ((dsmp->dsm_dflags & DHCP_IF_BUSY) &&
6363431Scarlsonj 			    eid == dsmp->dsm_ia.ia_eid)
6373431Scarlsonj 				return (dsmp);
6383431Scarlsonj 		}
6393431Scarlsonj 		if (isv6)
6403431Scarlsonj 			break;
6413431Scarlsonj 		isv6 = B_TRUE;
6423431Scarlsonj 	}
6433431Scarlsonj 
6443431Scarlsonj 	return (dsmp);
6453431Scarlsonj }
6463431Scarlsonj 
6473431Scarlsonj /*
6483431Scarlsonj  * cancel_offer_timer(): stop the offer polling timer on a given state machine
6493431Scarlsonj  *
6503431Scarlsonj  *   input: dhcp_smach_t *: state machine on which to stop polling for offers
6513431Scarlsonj  *  output: none
6523431Scarlsonj  */
6533431Scarlsonj 
6543431Scarlsonj void
6553431Scarlsonj cancel_offer_timer(dhcp_smach_t *dsmp)
6563431Scarlsonj {
6573431Scarlsonj 	int retval;
6583431Scarlsonj 
6593431Scarlsonj 	if (dsmp->dsm_offer_timer != -1) {
6603431Scarlsonj 		retval = iu_cancel_timer(tq, dsmp->dsm_offer_timer, NULL);
6613431Scarlsonj 		dsmp->dsm_offer_timer = -1;
6623431Scarlsonj 		if (retval == 1)
6633431Scarlsonj 			release_smach(dsmp);
6643431Scarlsonj 	}
6653431Scarlsonj }
6663431Scarlsonj 
6673431Scarlsonj /*
6683431Scarlsonj  * cancel_smach_timers(): stop all of the timers related to a given state
6693431Scarlsonj  *			  machine, including lease and LIF expiry.
6703431Scarlsonj  *
6713431Scarlsonj  *   input: dhcp_smach_t *: state machine to cancel
6723431Scarlsonj  *  output: none
6734106Scarlsonj  *    note: this function assumes that the iu timer functions are synchronous
6744106Scarlsonj  *	    and thus don't require any protection or ordering on cancellation.
6753431Scarlsonj  */
6763431Scarlsonj 
6779508SPeter.Memishian@Sun.COM void
6783431Scarlsonj cancel_smach_timers(dhcp_smach_t *dsmp)
6793431Scarlsonj {
6803431Scarlsonj 	dhcp_lease_t *dlp;
6813431Scarlsonj 	dhcp_lif_t *lif;
6823431Scarlsonj 	uint_t nlifs;
6833431Scarlsonj 
6843431Scarlsonj 	for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
6853431Scarlsonj 		cancel_lease_timers(dlp);
6863431Scarlsonj 		lif = dlp->dl_lifs;
6873431Scarlsonj 		nlifs = dlp->dl_nlifs;
6883431Scarlsonj 		for (; nlifs > 0; nlifs--, lif = lif->lif_next)
6893431Scarlsonj 			cancel_lif_timers(lif);
6903431Scarlsonj 	}
6913431Scarlsonj 
6923431Scarlsonj 	cancel_offer_timer(dsmp);
6933431Scarlsonj 	stop_pkt_retransmission(dsmp);
6944106Scarlsonj 	if (dsmp->dsm_start_timer != -1) {
6954106Scarlsonj 		(void) iu_cancel_timer(tq, dsmp->dsm_start_timer, NULL);
6964106Scarlsonj 		dsmp->dsm_start_timer = -1;
6974106Scarlsonj 		release_smach(dsmp);
6984106Scarlsonj 	}
6993431Scarlsonj }
7003431Scarlsonj 
7013431Scarlsonj /*
7023431Scarlsonj  * remove_smach(): removes a given state machine from the system.  marks it
7033431Scarlsonj  *		   for being freed (but may not actually free it).
7043431Scarlsonj  *
7053431Scarlsonj  *   input: dhcp_smach_t *: the state machine to remove
7063431Scarlsonj  *  output: void
7073431Scarlsonj  */
7083431Scarlsonj 
7093522Scarlsonj void
7103431Scarlsonj remove_smach(dhcp_smach_t *dsmp)
7113431Scarlsonj {
7123431Scarlsonj 	if (dsmp->dsm_dflags & DHCP_IF_REMOVED)
7133431Scarlsonj 		return;
7143431Scarlsonj 
7153431Scarlsonj 	dhcpmsg(MSG_DEBUG2, "remove_smach: removing %s", dsmp->dsm_name);
7163431Scarlsonj 	dsmp->dsm_dflags |= DHCP_IF_REMOVED;
7173431Scarlsonj 	remque(dsmp);
7183431Scarlsonj 	global_smach_count--;
7193431Scarlsonj 
7203431Scarlsonj 	/*
7213431Scarlsonj 	 * if we have long term timers, cancel them so that state machine
7223431Scarlsonj 	 * resources can be reclaimed in a reasonable amount of time.
7233431Scarlsonj 	 */
7243431Scarlsonj 	cancel_smach_timers(dsmp);
7253431Scarlsonj 
7263431Scarlsonj 	/* Drop the hold that the LIF's state machine list had on us */
7273431Scarlsonj 	release_smach(dsmp);
7283431Scarlsonj }
7293431Scarlsonj 
7303431Scarlsonj /*
7313431Scarlsonj  * finished_smach(): we're finished with a given state machine; remove it from
7323431Scarlsonj  *		     the system and tell the user (who may have initiated the
7333431Scarlsonj  *		     removal process).  Note that we remove it from the system
7343431Scarlsonj  *		     first to allow back-to-back drop and create invocations.
7353431Scarlsonj  *
7363431Scarlsonj  *   input: dhcp_smach_t *: the state machine to remove
7373431Scarlsonj  *	    int: error for IPC
7383431Scarlsonj  *  output: void
7393431Scarlsonj  */
7403431Scarlsonj 
7413431Scarlsonj void
7423431Scarlsonj finished_smach(dhcp_smach_t *dsmp, int error)
7433431Scarlsonj {
7443431Scarlsonj 	hold_smach(dsmp);
7453431Scarlsonj 	remove_smach(dsmp);
7463431Scarlsonj 	if (dsmp->dsm_ia.ia_fd != -1)
7473431Scarlsonj 		ipc_action_finish(dsmp, error);
7483431Scarlsonj 	else
7493431Scarlsonj 		(void) async_cancel(dsmp);
7503431Scarlsonj 	release_smach(dsmp);
7513431Scarlsonj }
7523431Scarlsonj 
7533431Scarlsonj /*
7545381Smeem  * is_bound_state(): checks if a state indicates the client is bound
7555381Smeem  *
7565381Smeem  *   input: DHCPSTATE: the state to check
7575381Smeem  *  output: boolean_t: B_TRUE if the state is bound, B_FALSE if not
7585381Smeem  */
7595381Smeem 
7605381Smeem boolean_t
7615381Smeem is_bound_state(DHCPSTATE state)
7625381Smeem {
7635381Smeem 	return (state == BOUND || state == REBINDING || state == INFORMATION ||
7645381Smeem 	    state == RELEASING || state == INFORM_SENT || state == RENEWING);
7655381Smeem }
7665381Smeem 
7675381Smeem /*
7683431Scarlsonj  * set_smach_state(): changes state and updates I/O
7693431Scarlsonj  *
7703431Scarlsonj  *   input: dhcp_smach_t *: the state machine to change
7713431Scarlsonj  *	    DHCPSTATE: the new state
7723431Scarlsonj  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
7733431Scarlsonj  */
7743431Scarlsonj 
7753431Scarlsonj boolean_t
7763431Scarlsonj set_smach_state(dhcp_smach_t *dsmp, DHCPSTATE state)
7773431Scarlsonj {
7785381Smeem 	dhcp_lif_t *lif = dsmp->dsm_lif;
7795381Smeem 
7803431Scarlsonj 	if (dsmp->dsm_state != state) {
7813431Scarlsonj 		dhcpmsg(MSG_DEBUG,
7823431Scarlsonj 		    "set_smach_state: changing from %s to %s on %s",
7833431Scarlsonj 		    dhcp_state_to_string(dsmp->dsm_state),
7843431Scarlsonj 		    dhcp_state_to_string(state), dsmp->dsm_name);
7853431Scarlsonj 
7865381Smeem 		/*
7875381Smeem 		 * For IPv4, when we're in a bound state our socket must be
7885381Smeem 		 * bound to our address.  Otherwise, our socket must be bound
7895381Smeem 		 * to INADDR_ANY.  For IPv6, no such change is necessary.
7905381Smeem 		 */
7913431Scarlsonj 		if (!dsmp->dsm_isv6) {
7925381Smeem 			if (is_bound_state(dsmp->dsm_state)) {
7935381Smeem 				if (!is_bound_state(state)) {
7945381Smeem 					close_ip_lif(lif);
7958485SPeter.Memishian@Sun.COM 					if (!open_ip_lif(lif, INADDR_ANY,
7968485SPeter.Memishian@Sun.COM 					    B_FALSE))
7975381Smeem 						return (B_FALSE);
7985381Smeem 				}
7995381Smeem 			} else {
8005381Smeem 				if (is_bound_state(state)) {
8015381Smeem 					close_ip_lif(lif);
8025381Smeem 					if (!open_ip_lif(lif,
8038485SPeter.Memishian@Sun.COM 					    ntohl(lif->lif_addr), B_FALSE))
8045381Smeem 						return (B_FALSE);
8055381Smeem 				}
8063431Scarlsonj 			}
8073431Scarlsonj 		}
8083431Scarlsonj 
8093431Scarlsonj 		dsmp->dsm_state = state;
8103431Scarlsonj 	}
8113431Scarlsonj 	return (B_TRUE);
8123431Scarlsonj }
8133431Scarlsonj 
8143431Scarlsonj /*
8153431Scarlsonj  * duid_retry(): attempt to write DUID again
8163431Scarlsonj  *
8173431Scarlsonj  *   input: iu_tq_t *: ignored
8183431Scarlsonj  *	    void *: ignored
8193431Scarlsonj  *  output: none
8203431Scarlsonj  */
8213431Scarlsonj 
8223431Scarlsonj /* ARGSUSED */
8233431Scarlsonj static void
8243431Scarlsonj duid_retry(iu_tq_t *tqp, void *arg)
8253431Scarlsonj {
8263431Scarlsonj 	if (write_stable_duid(global_duid, global_duidlen) == -1) {
8273431Scarlsonj 		if (errno != EROFS) {
8283431Scarlsonj 			dhcpmsg(MSG_ERR,
8293431Scarlsonj 			    "duid_retry: unable to write out DUID");
8303431Scarlsonj 		} else {
8313431Scarlsonj 			(void) iu_schedule_timer(tq, 60, duid_retry, NULL);
8323431Scarlsonj 		}
8333431Scarlsonj 	}
8343431Scarlsonj }
8353431Scarlsonj 
8363431Scarlsonj /*
8373431Scarlsonj  * get_smach_cid(): gets the client ID for a given state machine.
8383431Scarlsonj  *
8393431Scarlsonj  *   input: dhcp_smach_t *: the state machine to set up
8403431Scarlsonj  *  output: int: DHCP_IPC_SUCCESS or one of DHCP_IPC_E_* on failure.
8413431Scarlsonj  */
8423431Scarlsonj 
8433431Scarlsonj int
8443431Scarlsonj get_smach_cid(dhcp_smach_t *dsmp)
8453431Scarlsonj {
8463431Scarlsonj 	uchar_t *client_id;
8473431Scarlsonj 	uint_t client_id_len;
8483431Scarlsonj 	dhcp_lif_t *lif = dsmp->dsm_lif;
8493431Scarlsonj 	dhcp_pif_t *pif = lif->lif_pif;
8503431Scarlsonj 	const char *value;
8513431Scarlsonj 	size_t slen;
8523431Scarlsonj 
8533431Scarlsonj 	/*
8543431Scarlsonj 	 * Look in defaults file for the client-id.  If present, this takes
8553431Scarlsonj 	 * precedence over all other forms of ID.
8563431Scarlsonj 	 */
8573431Scarlsonj 
8583431Scarlsonj 	dhcpmsg(MSG_DEBUG, "get_smach_cid: getting default client-id "
8593431Scarlsonj 	    "property on %s", dsmp->dsm_name);
8603431Scarlsonj 	value = df_get_string(dsmp->dsm_name, pif->pif_isv6, DF_CLIENT_ID);
8613431Scarlsonj 	if (value != NULL) {
8623431Scarlsonj 		/*
8633431Scarlsonj 		 * The Client ID string can have one of three basic forms:
8643431Scarlsonj 		 *	<decimal>,<data...>
8653431Scarlsonj 		 *	0x<hex...>
8663431Scarlsonj 		 *	<string...>
8673431Scarlsonj 		 *
8683431Scarlsonj 		 * The first form is an RFC 3315 DUID.  This is legal for both
8693431Scarlsonj 		 * IPv4 DHCP and DHCPv6.  For IPv4, an RFC 4361 Client ID is
8703431Scarlsonj 		 * constructed from this value.
8713431Scarlsonj 		 *
8723431Scarlsonj 		 * The second and third forms are legal for IPv4 only.  This is
8733431Scarlsonj 		 * a raw Client ID, in hex or ASCII string format.
8743431Scarlsonj 		 */
8753431Scarlsonj 
8763431Scarlsonj 		if (isdigit(*value) &&
8773431Scarlsonj 		    value[strspn(value, "0123456789")] == ',') {
8783431Scarlsonj 			char *cp;
8793431Scarlsonj 			ulong_t duidtype;
8803431Scarlsonj 			ulong_t subtype;
8813431Scarlsonj 
8823431Scarlsonj 			errno = 0;
8833431Scarlsonj 			duidtype = strtoul(value, &cp, 0);
8843431Scarlsonj 			if (value == cp || errno != 0 || *cp != ',' ||
8853431Scarlsonj 			    duidtype > 65535) {
8863431Scarlsonj 				dhcpmsg(MSG_ERR, "get_smach_cid: cannot parse "
8873431Scarlsonj 				    "DUID type in %s", value);
8883431Scarlsonj 				goto no_specified_id;
8893431Scarlsonj 			}
8903431Scarlsonj 			value = cp + 1;
8913431Scarlsonj 			switch (duidtype) {
8923431Scarlsonj 			case DHCPV6_DUID_LL:
8933431Scarlsonj 			case DHCPV6_DUID_LLT: {
8943431Scarlsonj 				int num;
8953431Scarlsonj 				char chr;
8963431Scarlsonj 
8973431Scarlsonj 				errno = 0;
8983431Scarlsonj 				subtype = strtoul(value, &cp, 0);
8993431Scarlsonj 				if (value == cp || errno != 0 || *cp != ',' ||
9003431Scarlsonj 				    subtype > 65535) {
9013431Scarlsonj 					dhcpmsg(MSG_ERR, "get_smach_cid: "
9023431Scarlsonj 					    "cannot parse MAC type in %s",
9033431Scarlsonj 					    value);
9043431Scarlsonj 					goto no_specified_id;
9053431Scarlsonj 				}
9063431Scarlsonj 				value = cp + 1;
9073431Scarlsonj 				client_id_len = pif->pif_isv6 ? 1 : 5;
9083431Scarlsonj 				for (; *cp != '\0'; cp++) {
9093431Scarlsonj 					if (*cp == ':')
9103431Scarlsonj 						client_id_len++;
9113431Scarlsonj 					else if (!isxdigit(*cp))
9123431Scarlsonj 						break;
9133431Scarlsonj 				}
9143431Scarlsonj 				if (duidtype == DHCPV6_DUID_LL) {
9153431Scarlsonj 					duid_llt_t *dllt;
9163431Scarlsonj 					time_t now;
9173431Scarlsonj 
9183431Scarlsonj 					client_id_len += sizeof (*dllt);
9193431Scarlsonj 					dllt = malloc(client_id_len);
9203431Scarlsonj 					if (dllt == NULL)
9213431Scarlsonj 						goto alloc_failure;
9223431Scarlsonj 					dsmp->dsm_cid = (uchar_t *)dllt;
9233431Scarlsonj 					dllt->dllt_dutype = htons(duidtype);
9243431Scarlsonj 					dllt->dllt_hwtype = htons(subtype);
9253431Scarlsonj 					now = time(NULL) - DUID_TIME_BASE;
9263431Scarlsonj 					dllt->dllt_time = htonl(now);
9273431Scarlsonj 					cp = (char *)(dllt + 1);
9283431Scarlsonj 				} else {
9293431Scarlsonj 					duid_ll_t *dll;
9303431Scarlsonj 
9313431Scarlsonj 					client_id_len += sizeof (*dll);
9323431Scarlsonj 					dll = malloc(client_id_len);
9333431Scarlsonj 					if (dll == NULL)
9343431Scarlsonj 						goto alloc_failure;
9353431Scarlsonj 					dsmp->dsm_cid = (uchar_t *)dll;
9363431Scarlsonj 					dll->dll_dutype = htons(duidtype);
9373431Scarlsonj 					dll->dll_hwtype = htons(subtype);
9383431Scarlsonj 					cp = (char *)(dll + 1);
9393431Scarlsonj 				}
9403431Scarlsonj 				num = 0;
9413431Scarlsonj 				while ((chr = *value) != '\0') {
9423431Scarlsonj 					if (isdigit(chr)) {
9433431Scarlsonj 						num = (num << 4) + chr - '0';
9443431Scarlsonj 					} else if (isxdigit(chr)) {
9453431Scarlsonj 						num = (num << 4) + 10 + chr -
9463431Scarlsonj 						    (isupper(chr) ? 'A' : 'a');
9473431Scarlsonj 					} else if (chr == ':') {
9483431Scarlsonj 						*cp++ = num;
9493431Scarlsonj 						num = 0;
9503431Scarlsonj 					} else {
9513431Scarlsonj 						break;
9523431Scarlsonj 					}
9533431Scarlsonj 				}
9543431Scarlsonj 				break;
9553431Scarlsonj 			}
9563431Scarlsonj 			case DHCPV6_DUID_EN: {
9573431Scarlsonj 				duid_en_t *den;
9583431Scarlsonj 
9593431Scarlsonj 				errno = 0;
9603431Scarlsonj 				subtype = strtoul(value, &cp, 0);
9613431Scarlsonj 				if (value == cp || errno != 0 || *cp != ',') {
9623431Scarlsonj 					dhcpmsg(MSG_ERR, "get_smach_cid: "
9633431Scarlsonj 					    "cannot parse enterprise in %s",
9643431Scarlsonj 					    value);
9653431Scarlsonj 					goto no_specified_id;
9663431Scarlsonj 				}
9673431Scarlsonj 				value = cp + 1;
9683431Scarlsonj 				slen = strlen(value);
9693431Scarlsonj 				client_id_len = (slen + 1) / 2;
9703431Scarlsonj 				den = malloc(sizeof (*den) + client_id_len);
9713431Scarlsonj 				if (den == NULL)
9723431Scarlsonj 					goto alloc_failure;
9733431Scarlsonj 				den->den_dutype = htons(duidtype);
9743431Scarlsonj 				DHCPV6_SET_ENTNUM(den, subtype);
9753431Scarlsonj 				if (hexascii_to_octet(value, slen, den + 1,
9763431Scarlsonj 				    &client_id_len) != 0) {
9773431Scarlsonj 					dhcpmsg(MSG_ERROR, "get_smach_cid: "
9783431Scarlsonj 					    "cannot parse hex string in %s",
9793431Scarlsonj 					    value);
9803431Scarlsonj 					free(den);
9813431Scarlsonj 					goto no_specified_id;
9823431Scarlsonj 				}
9833431Scarlsonj 				dsmp->dsm_cid = (uchar_t *)den;
9843431Scarlsonj 				break;
9853431Scarlsonj 			}
9863431Scarlsonj 			default:
9873431Scarlsonj 				slen = strlen(value);
9883431Scarlsonj 				client_id_len = (slen + 1) / 2;
9893431Scarlsonj 				cp = malloc(client_id_len);
9903431Scarlsonj 				if (cp == NULL)
9913431Scarlsonj 					goto alloc_failure;
9923431Scarlsonj 				if (hexascii_to_octet(value, slen, cp,
9933431Scarlsonj 				    &client_id_len) != 0) {
9943431Scarlsonj 					dhcpmsg(MSG_ERROR, "get_smach_cid: "
9953431Scarlsonj 					    "cannot parse hex string in %s",
9963431Scarlsonj 					    value);
9973431Scarlsonj 					free(cp);
9983431Scarlsonj 					goto no_specified_id;
9993431Scarlsonj 				}
10003431Scarlsonj 				dsmp->dsm_cid = (uchar_t *)cp;
10013431Scarlsonj 				break;
10023431Scarlsonj 			}
10033431Scarlsonj 			dsmp->dsm_cidlen = client_id_len;
10043431Scarlsonj 			if (!pif->pif_isv6) {
10053431Scarlsonj 				(void) memmove(dsmp->dsm_cid + 5,
10063431Scarlsonj 				    dsmp->dsm_cid, client_id_len - 5);
10073431Scarlsonj 				dsmp->dsm_cid[0] = 255;
10083431Scarlsonj 				dsmp->dsm_cid[1] = lif->lif_iaid >> 24;
10093431Scarlsonj 				dsmp->dsm_cid[2] = lif->lif_iaid >> 16;
10103431Scarlsonj 				dsmp->dsm_cid[3] = lif->lif_iaid >> 8;
10113431Scarlsonj 				dsmp->dsm_cid[4] = lif->lif_iaid;
10123431Scarlsonj 			}
10133431Scarlsonj 			return (DHCP_IPC_SUCCESS);
10143431Scarlsonj 		}
10153431Scarlsonj 
10163431Scarlsonj 		if (pif->pif_isv6) {
10173431Scarlsonj 			dhcpmsg(MSG_ERROR,
10183431Scarlsonj 			    "get_smach_cid: client ID for %s invalid: %s",
10193431Scarlsonj 			    dsmp->dsm_name, value);
10203431Scarlsonj 		} else if (strncasecmp("0x", value, 2) == 0 &&
10213431Scarlsonj 		    value[2] != '\0') {
10223431Scarlsonj 			/* skip past the 0x and convert the value to binary */
10233431Scarlsonj 			value += 2;
10243431Scarlsonj 			slen = strlen(value);
10253431Scarlsonj 			client_id_len = (slen + 1) / 2;
10263431Scarlsonj 			dsmp->dsm_cid = malloc(client_id_len);
10273431Scarlsonj 			if (dsmp->dsm_cid == NULL)
10283431Scarlsonj 				goto alloc_failure;
10293431Scarlsonj 			if (hexascii_to_octet(value, slen, dsmp->dsm_cid,
10303431Scarlsonj 			    &client_id_len) == 0) {
10313431Scarlsonj 				dsmp->dsm_cidlen = client_id_len;
10323431Scarlsonj 				return (DHCP_IPC_SUCCESS);
10333431Scarlsonj 			}
10343431Scarlsonj 			dhcpmsg(MSG_WARNING, "get_smach_cid: cannot convert "
10353431Scarlsonj 			    "hex value for Client ID on %s", dsmp->dsm_name);
10363431Scarlsonj 		} else {
10373431Scarlsonj 			client_id_len = strlen(value);
10383431Scarlsonj 			dsmp->dsm_cid = malloc(client_id_len);
10393431Scarlsonj 			if (dsmp->dsm_cid == NULL)
10403431Scarlsonj 				goto alloc_failure;
10413431Scarlsonj 			(void) memcpy(dsmp->dsm_cid, value, client_id_len);
10423431Scarlsonj 			return (DHCP_IPC_SUCCESS);
10433431Scarlsonj 		}
10443431Scarlsonj 	}
10453431Scarlsonj no_specified_id:
10463431Scarlsonj 
10473431Scarlsonj 	/*
10483431Scarlsonj 	 * There was either no user-specified Client ID value, or we were
10493431Scarlsonj 	 * unable to parse it.  We need to determine if a Client ID is required
10503431Scarlsonj 	 * and, if so, generate one.
10513431Scarlsonj 	 *
10528485SPeter.Memishian@Sun.COM 	 * If it's IPv4, not in an IPMP group, and not a logical interface,
10538485SPeter.Memishian@Sun.COM 	 * then we need to preserve backward-compatibility by avoiding
10548485SPeter.Memishian@Sun.COM 	 * new-fangled DUID/IAID construction.  (Note: even for IPMP test
10558485SPeter.Memishian@Sun.COM 	 * addresses, we construct a DUID/IAID since we may renew a lease for
10568485SPeter.Memishian@Sun.COM 	 * an IPMP test address on any functioning IP interface in the group.)
10573431Scarlsonj 	 */
10588485SPeter.Memishian@Sun.COM 	if (!pif->pif_isv6 && pif->pif_grifname[0] == '\0' &&
10598485SPeter.Memishian@Sun.COM 	    strchr(dsmp->dsm_name, ':') == NULL) {
10603431Scarlsonj 		if (pif->pif_hwtype == ARPHRD_IB) {
10613431Scarlsonj 			/*
10623431Scarlsonj 			 * This comes from the DHCP over IPoIB specification.
10633431Scarlsonj 			 * In the absence of an user specified client id, IPoIB
10643431Scarlsonj 			 * automatically uses the required format, with the
10653431Scarlsonj 			 * unique 4 octet value set to 0 (since IPoIB driver
10663431Scarlsonj 			 * allows only a single interface on a port with a
10673431Scarlsonj 			 * specific GID to belong to an IP subnet (PSARC
10683431Scarlsonj 			 * 2001/289, FWARC 2002/702).
10693431Scarlsonj 			 *
10703431Scarlsonj 			 *   Type  Client-Identifier
10713431Scarlsonj 			 * +-----+-----+-----+-----+-----+----....----+
10723431Scarlsonj 			 * |  0  |  0 (4 octets)   |   GID (16 octets)|
10733431Scarlsonj 			 * +-----+-----+-----+-----+-----+----....----+
10743431Scarlsonj 			 */
10753431Scarlsonj 			dsmp->dsm_cidlen = 1 + 4 + 16;
10763431Scarlsonj 			dsmp->dsm_cid = client_id = malloc(dsmp->dsm_cidlen);
10773431Scarlsonj 			if (dsmp->dsm_cid == NULL)
10783431Scarlsonj 				goto alloc_failure;
10793431Scarlsonj 
10803431Scarlsonj 			/*
10813431Scarlsonj 			 * Pick the GID from the mac address. The format
10823431Scarlsonj 			 * of the hardware address is:
10833431Scarlsonj 			 * +-----+-----+-----+-----+----....----+
10843431Scarlsonj 			 * | QPN (4 octets)  |   GID (16 octets)|
10853431Scarlsonj 			 * +-----+-----+-----+-----+----....----+
10863431Scarlsonj 			 */
10873431Scarlsonj 			(void) memcpy(client_id + 5, pif->pif_hwaddr + 4,
10883431Scarlsonj 			    pif->pif_hwlen - 4);
10893431Scarlsonj 			(void) memset(client_id, 0, 5);
10903431Scarlsonj 		}
10913431Scarlsonj 		return (DHCP_IPC_SUCCESS);
10923431Scarlsonj 	}
10933431Scarlsonj 
10943431Scarlsonj 	/*
10953431Scarlsonj 	 * Now check for a saved DUID.  If there is one, then use it.  If there
10963431Scarlsonj 	 * isn't, then generate a new one.  For IPv4, we need to construct the
10973431Scarlsonj 	 * RFC 4361 Client ID with this value and the LIF's IAID.
10983431Scarlsonj 	 */
10993431Scarlsonj 	if (global_duid == NULL &&
11003431Scarlsonj 	    (global_duid = read_stable_duid(&global_duidlen)) == NULL) {
11013431Scarlsonj 		global_duid = make_stable_duid(pif->pif_name, &global_duidlen);
11023431Scarlsonj 		if (global_duid == NULL)
11033431Scarlsonj 			goto alloc_failure;
11043431Scarlsonj 		duid_retry(NULL, NULL);
11053431Scarlsonj 	}
11063431Scarlsonj 
11073431Scarlsonj 	if (pif->pif_isv6) {
11083431Scarlsonj 		dsmp->dsm_cid = malloc(global_duidlen);
11093431Scarlsonj 		if (dsmp->dsm_cid == NULL)
11103431Scarlsonj 			goto alloc_failure;
11113431Scarlsonj 		(void) memcpy(dsmp->dsm_cid, global_duid, global_duidlen);
11123431Scarlsonj 		dsmp->dsm_cidlen = global_duidlen;
11133431Scarlsonj 	} else {
11143431Scarlsonj 		dsmp->dsm_cid = malloc(5 + global_duidlen);
11153431Scarlsonj 		if (dsmp->dsm_cid == NULL)
11163431Scarlsonj 			goto alloc_failure;
11173431Scarlsonj 		dsmp->dsm_cid[0] = 255;
11183431Scarlsonj 		dsmp->dsm_cid[1] = lif->lif_iaid >> 24;
11193431Scarlsonj 		dsmp->dsm_cid[2] = lif->lif_iaid >> 16;
11203431Scarlsonj 		dsmp->dsm_cid[3] = lif->lif_iaid >> 8;
11213431Scarlsonj 		dsmp->dsm_cid[4] = lif->lif_iaid;
11223431Scarlsonj 		(void) memcpy(dsmp->dsm_cid + 5, global_duid, global_duidlen);
11233431Scarlsonj 		dsmp->dsm_cidlen = 5 + global_duidlen;
11243431Scarlsonj 	}
11253431Scarlsonj 
11263431Scarlsonj 	return (DHCP_IPC_SUCCESS);
11273431Scarlsonj 
11283431Scarlsonj alloc_failure:
11293431Scarlsonj 	dhcpmsg(MSG_ERR, "get_smach_cid: cannot allocate Client Id for %s",
11303431Scarlsonj 	    dsmp->dsm_name);
11313431Scarlsonj 	return (DHCP_IPC_E_MEMORY);
11323431Scarlsonj }
11333431Scarlsonj 
11343431Scarlsonj /*
11353431Scarlsonj  * smach_count(): returns the number of state machines running
11363431Scarlsonj  *
11373431Scarlsonj  *   input: void
11383431Scarlsonj  *  output: uint_t: the number of state machines
11393431Scarlsonj  */
11403431Scarlsonj 
11413431Scarlsonj uint_t
11423431Scarlsonj smach_count(void)
11433431Scarlsonj {
11443431Scarlsonj 	return (global_smach_count);
11453431Scarlsonj }
11463431Scarlsonj 
11473431Scarlsonj /*
11484106Scarlsonj  * discard_default_routes(): removes a state machine's default routes alone.
11494106Scarlsonj  *
11504106Scarlsonj  *   input: dhcp_smach_t *: the state machine whose default routes need to be
11514106Scarlsonj  *			    discarded
11524106Scarlsonj  *  output: void
11534106Scarlsonj  */
11544106Scarlsonj 
11554106Scarlsonj void
11564106Scarlsonj discard_default_routes(dhcp_smach_t *dsmp)
11574106Scarlsonj {
11584106Scarlsonj 	free(dsmp->dsm_routers);
11594106Scarlsonj 	dsmp->dsm_routers = NULL;
11604106Scarlsonj 	dsmp->dsm_nrouters = 0;
11614106Scarlsonj }
11624106Scarlsonj 
11634106Scarlsonj /*
11644106Scarlsonj  * remove_default_routes(): removes a state machine's default routes from the
11654106Scarlsonj  *			    kernel and from the state machine.
11663431Scarlsonj  *
11673431Scarlsonj  *   input: dhcp_smach_t *: the state machine whose default routes need to be
11683431Scarlsonj  *			    removed
11693431Scarlsonj  *  output: void
11703431Scarlsonj  */
11713431Scarlsonj 
11723431Scarlsonj void
11733431Scarlsonj remove_default_routes(dhcp_smach_t *dsmp)
11743431Scarlsonj {
11753431Scarlsonj 	int idx;
11764106Scarlsonj 	uint32_t ifindex;
11773431Scarlsonj 
11783431Scarlsonj 	if (dsmp->dsm_routers != NULL) {
11794106Scarlsonj 		ifindex = dsmp->dsm_lif->lif_pif->pif_index;
11803431Scarlsonj 		for (idx = dsmp->dsm_nrouters - 1; idx >= 0; idx--) {
11814106Scarlsonj 			if (del_default_route(ifindex,
11823431Scarlsonj 			    &dsmp->dsm_routers[idx])) {
11833431Scarlsonj 				dhcpmsg(MSG_DEBUG, "remove_default_routes: "
11843431Scarlsonj 				    "removed %s from %s",
11853431Scarlsonj 				    inet_ntoa(dsmp->dsm_routers[idx]),
11863431Scarlsonj 				    dsmp->dsm_name);
11873431Scarlsonj 			} else {
11883431Scarlsonj 				dhcpmsg(MSG_INFO, "remove_default_routes: "
11893431Scarlsonj 				    "unable to remove %s from %s",
11903431Scarlsonj 				    inet_ntoa(dsmp->dsm_routers[idx]),
11913431Scarlsonj 				    dsmp->dsm_name);
11923431Scarlsonj 			}
11933431Scarlsonj 		}
11944106Scarlsonj 		discard_default_routes(dsmp);
11953431Scarlsonj 	}
11963431Scarlsonj }
11973431Scarlsonj 
11983431Scarlsonj /*
11993431Scarlsonj  * reset_smach(): resets a state machine to its initial state
12003431Scarlsonj  *
12013431Scarlsonj  *   input: dhcp_smach_t *: the state machine to reset
12023431Scarlsonj  *  output: void
12033431Scarlsonj  */
12043431Scarlsonj 
12053431Scarlsonj void
12063431Scarlsonj reset_smach(dhcp_smach_t *dsmp)
12073431Scarlsonj {
12083431Scarlsonj 	dsmp->dsm_dflags &= ~DHCP_IF_FAILED;
12093431Scarlsonj 
12103431Scarlsonj 	remove_default_routes(dsmp);
12113431Scarlsonj 
12123431Scarlsonj 	free_pkt_list(&dsmp->dsm_recv_pkt_list);
12139508SPeter.Memishian@Sun.COM 	free_pkt_entry(dsmp->dsm_ack);
12143431Scarlsonj 	if (dsmp->dsm_orig_ack != dsmp->dsm_ack)
12153431Scarlsonj 		free_pkt_entry(dsmp->dsm_orig_ack);
12169508SPeter.Memishian@Sun.COM 	dsmp->dsm_ack = dsmp->dsm_orig_ack = NULL;
12173431Scarlsonj 
12189508SPeter.Memishian@Sun.COM 	free(dsmp->dsm_reqhost);
12199508SPeter.Memishian@Sun.COM 	dsmp->dsm_reqhost = NULL;
12203431Scarlsonj 
12213431Scarlsonj 	cancel_smach_timers(dsmp);
12223431Scarlsonj 
12233431Scarlsonj 	(void) set_smach_state(dsmp, INIT);
12243431Scarlsonj 	if (dsmp->dsm_isv6) {
12253431Scarlsonj 		dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers;
12263431Scarlsonj 	} else {
12273431Scarlsonj 		IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST),
12283431Scarlsonj 		    &dsmp->dsm_server);
12293431Scarlsonj 	}
12309508SPeter.Memishian@Sun.COM 	dsmp->dsm_neg_hrtime = gethrtime();
12319508SPeter.Memishian@Sun.COM 	/*
12329508SPeter.Memishian@Sun.COM 	 * We must never get here with a script running, since it means we're
12339508SPeter.Memishian@Sun.COM 	 * resetting an smach that is still in the middle of another state
12349508SPeter.Memishian@Sun.COM 	 * transition with a pending dsm_script_callback.
12359508SPeter.Memishian@Sun.COM 	 */
12369508SPeter.Memishian@Sun.COM 	assert(dsmp->dsm_script_pid == -1);
12373431Scarlsonj }
12383431Scarlsonj 
12393431Scarlsonj /*
12403431Scarlsonj  * refresh_smach(): refreshes a given state machine, as though awakened from
12413431Scarlsonj  *		    hibernation or by lower layer "link up."
12423431Scarlsonj  *
12433431Scarlsonj  *   input: dhcp_smach_t *: state machine to refresh
12443431Scarlsonj  *  output: void
12453431Scarlsonj  */
12463431Scarlsonj 
12473431Scarlsonj void
12483431Scarlsonj refresh_smach(dhcp_smach_t *dsmp)
12493431Scarlsonj {
12503431Scarlsonj 	if (dsmp->dsm_state == BOUND || dsmp->dsm_state == RENEWING ||
12514106Scarlsonj 	    dsmp->dsm_state == REBINDING || dsmp->dsm_state == INFORMATION) {
12524106Scarlsonj 		dhcpmsg(MSG_WARNING, "refreshing state on %s", dsmp->dsm_name);
12533431Scarlsonj 		cancel_smach_timers(dsmp);
12544106Scarlsonj 		if (dsmp->dsm_state == INFORMATION)
12554106Scarlsonj 			dhcp_inform(dsmp);
12564106Scarlsonj 		else
12574106Scarlsonj 			dhcp_init_reboot(dsmp);
12583431Scarlsonj 	}
12593431Scarlsonj }
12603431Scarlsonj 
12613431Scarlsonj /*
12623431Scarlsonj  * refresh_smachs(): refreshes all finite leases under DHCP control
12633431Scarlsonj  *
12643431Scarlsonj  *   input: iu_eh_t *: unused
12653431Scarlsonj  *	    int: unused
12663431Scarlsonj  *	    void *: unused
12673431Scarlsonj  *  output: void
12683431Scarlsonj  */
12693431Scarlsonj 
12703431Scarlsonj /* ARGSUSED */
12713431Scarlsonj void
12723431Scarlsonj refresh_smachs(iu_eh_t *eh, int sig, void *arg)
12733431Scarlsonj {
12743431Scarlsonj 	boolean_t isv6 = B_FALSE;
12753431Scarlsonj 	dhcp_smach_t *dsmp;
12763431Scarlsonj 
12773431Scarlsonj 	for (;;) {
12783431Scarlsonj 		for (dsmp = next_smach(NULL, isv6); dsmp != NULL;
12793431Scarlsonj 		    dsmp = next_smach(dsmp, isv6)) {
12803431Scarlsonj 			refresh_smach(dsmp);
12813431Scarlsonj 		}
12823431Scarlsonj 		if (isv6)
12833431Scarlsonj 			break;
12843431Scarlsonj 		isv6 = B_TRUE;
12853431Scarlsonj 	}
12863431Scarlsonj }
12873431Scarlsonj 
12883431Scarlsonj /*
12893431Scarlsonj  * nuke_smach_list(): delete the state machine list.  For use when the
12903431Scarlsonj  *		      dhcpagent is exiting.
12913431Scarlsonj  *
12923431Scarlsonj  *   input: none
12933431Scarlsonj  *  output: none
12943431Scarlsonj  */
12953431Scarlsonj 
12963431Scarlsonj void
12973431Scarlsonj nuke_smach_list(void)
12983431Scarlsonj {
12993431Scarlsonj 	boolean_t isv6 = B_FALSE;
13003431Scarlsonj 	dhcp_smach_t *dsmp, *dsmp_next;
13013431Scarlsonj 
13023431Scarlsonj 	for (;;) {
13033431Scarlsonj 		for (dsmp = next_smach(NULL, isv6); dsmp != NULL;
13043431Scarlsonj 		    dsmp = dsmp_next) {
13053431Scarlsonj 			int	status;
13063431Scarlsonj 
13073431Scarlsonj 			dsmp_next = next_smach(dsmp, isv6);
13083431Scarlsonj 
13096508Scarlsonj 			/* If we're already dropping or releasing, skip */
13106508Scarlsonj 			if (dsmp->dsm_droprelease)
13116508Scarlsonj 				continue;
13126508Scarlsonj 			dsmp->dsm_droprelease = B_TRUE;
13136508Scarlsonj 
13143431Scarlsonj 			cancel_smach_timers(dsmp);
13153431Scarlsonj 
13163431Scarlsonj 			/*
13173431Scarlsonj 			 * If the script is started by script_start, dhcp_drop
13183431Scarlsonj 			 * and dhcp_release should and will only be called
13193431Scarlsonj 			 * after the script exits.
13203431Scarlsonj 			 */
13213431Scarlsonj 			if (df_get_bool(dsmp->dsm_name, isv6,
1322*9633Sjames.d.carlson@sun.com 			    DF_RELEASE_ON_SIGTERM) ||
1323*9633Sjames.d.carlson@sun.com 			    df_get_bool(dsmp->dsm_name, isv6,
1324*9633Sjames.d.carlson@sun.com 			    DF_VERIFIED_LEASE_ONLY)) {
13256508Scarlsonj 				if (script_start(dsmp, isv6 ? EVENT_RELEASE6 :
13266508Scarlsonj 				    EVENT_RELEASE, dhcp_release,
13276508Scarlsonj 				    "DHCP agent is exiting", &status)) {
13283431Scarlsonj 					continue;
13293431Scarlsonj 				}
13303431Scarlsonj 				if (status == 1)
13313431Scarlsonj 					continue;
13323431Scarlsonj 			}
13336508Scarlsonj 			(void) script_start(dsmp, isv6 ? EVENT_DROP6 :
13346508Scarlsonj 			    EVENT_DROP, dhcp_drop, NULL, NULL);
13353431Scarlsonj 		}
13363431Scarlsonj 		if (isv6)
13373431Scarlsonj 			break;
13383431Scarlsonj 		isv6 = B_TRUE;
13393431Scarlsonj 	}
13403431Scarlsonj }
13413431Scarlsonj 
13423431Scarlsonj /*
13433431Scarlsonj  * insert_lease(): Create a lease structure on a given state machine.  The
13443431Scarlsonj  *		   lease holds a reference to the state machine.
13453431Scarlsonj  *
13463431Scarlsonj  *   input: dhcp_smach_t *: state machine
13473431Scarlsonj  *  output: dhcp_lease_t *: newly-created lease
13483431Scarlsonj  */
13493431Scarlsonj 
13503431Scarlsonj dhcp_lease_t *
13513431Scarlsonj insert_lease(dhcp_smach_t *dsmp)
13523431Scarlsonj {
13533431Scarlsonj 	dhcp_lease_t *dlp;
13543431Scarlsonj 
13553431Scarlsonj 	if ((dlp = calloc(1, sizeof (*dlp))) == NULL)
13563431Scarlsonj 		return (NULL);
13573431Scarlsonj 	dlp->dl_smach = dsmp;
13583431Scarlsonj 	dlp->dl_hold_count = 1;
13593431Scarlsonj 	init_timer(&dlp->dl_t1, 0);
13603431Scarlsonj 	init_timer(&dlp->dl_t2, 0);
13613431Scarlsonj 	insque(dlp, &dsmp->dsm_leases);
13623431Scarlsonj 	dhcpmsg(MSG_DEBUG2, "insert_lease: new lease for %s", dsmp->dsm_name);
13633431Scarlsonj 	return (dlp);
13643431Scarlsonj }
13653431Scarlsonj 
13663431Scarlsonj /*
13673431Scarlsonj  * hold_lease(): acquires a hold on a lease
13683431Scarlsonj  *
13693431Scarlsonj  *   input: dhcp_lease_t *: the lease to acquire a hold on
13703431Scarlsonj  *  output: void
13713431Scarlsonj  */
13723431Scarlsonj 
13733431Scarlsonj void
13743431Scarlsonj hold_lease(dhcp_lease_t *dlp)
13753431Scarlsonj {
13763431Scarlsonj 	dlp->dl_hold_count++;
13773431Scarlsonj 
13783431Scarlsonj 	dhcpmsg(MSG_DEBUG2, "hold_lease: hold count on lease for %s: %d",
13793431Scarlsonj 	    dlp->dl_smach->dsm_name, dlp->dl_hold_count);
13803431Scarlsonj }
13813431Scarlsonj 
13823431Scarlsonj /*
13833431Scarlsonj  * release_lease(): releases a hold previously acquired on a lease.
13843431Scarlsonj  *		    If the hold count reaches 0, the lease is freed.
13853431Scarlsonj  *
13863431Scarlsonj  *   input: dhcp_lease_t *: the lease to release the hold on
13873431Scarlsonj  *  output: void
13883431Scarlsonj  */
13893431Scarlsonj 
13903431Scarlsonj void
13913431Scarlsonj release_lease(dhcp_lease_t *dlp)
13923431Scarlsonj {
13933431Scarlsonj 	if (dlp->dl_hold_count == 0) {
13943431Scarlsonj 		dhcpmsg(MSG_CRIT, "release_lease: extraneous release");
13953431Scarlsonj 		return;
13963431Scarlsonj 	}
13973431Scarlsonj 
13983431Scarlsonj 	if (dlp->dl_hold_count == 1 && !dlp->dl_removed) {
13993431Scarlsonj 		dhcpmsg(MSG_CRIT, "release_lease: missing removal");
14003431Scarlsonj 		return;
14013431Scarlsonj 	}
14023431Scarlsonj 
14033431Scarlsonj 	if (--dlp->dl_hold_count == 0) {
14043431Scarlsonj 		dhcpmsg(MSG_DEBUG,
14053431Scarlsonj 		    "release_lease: freeing lease on state machine %s",
14063431Scarlsonj 		    dlp->dl_smach->dsm_name);
14073431Scarlsonj 		free(dlp);
14083431Scarlsonj 	} else {
14093431Scarlsonj 		dhcpmsg(MSG_DEBUG2,
14103431Scarlsonj 		    "release_lease: hold count on lease for %s: %d",
14113431Scarlsonj 		    dlp->dl_smach->dsm_name, dlp->dl_hold_count);
14123431Scarlsonj 	}
14133431Scarlsonj }
14143431Scarlsonj 
14153431Scarlsonj /*
14163431Scarlsonj  * remove_lease(): removes a given lease from the state machine and drops the
14173431Scarlsonj  *		   state machine's hold on the lease.
14183431Scarlsonj  *
14193431Scarlsonj  *   input: dhcp_lease_t *: the lease to remove
14203431Scarlsonj  *  output: void
14213431Scarlsonj  */
14223431Scarlsonj 
14233431Scarlsonj void
14243431Scarlsonj remove_lease(dhcp_lease_t *dlp)
14253431Scarlsonj {
14263431Scarlsonj 	if (dlp->dl_removed) {
14273431Scarlsonj 		dhcpmsg(MSG_CRIT, "remove_lease: extraneous removal");
14283431Scarlsonj 	} else {
14293431Scarlsonj 		dhcp_lif_t *lif, *lifnext;
14303431Scarlsonj 		uint_t nlifs;
14313431Scarlsonj 
14323431Scarlsonj 		dhcpmsg(MSG_DEBUG,
14333431Scarlsonj 		    "remove_lease: removed lease from state machine %s",
14343431Scarlsonj 		    dlp->dl_smach->dsm_name);
14353431Scarlsonj 		dlp->dl_removed = B_TRUE;
14363431Scarlsonj 		remque(dlp);
14373431Scarlsonj 
14383431Scarlsonj 		cancel_lease_timers(dlp);
14393431Scarlsonj 
14403431Scarlsonj 		lif = dlp->dl_lifs;
14413431Scarlsonj 		nlifs = dlp->dl_nlifs;
14423431Scarlsonj 		for (; nlifs > 0; nlifs--, lif = lifnext) {
14433431Scarlsonj 			lifnext = lif->lif_next;
14443431Scarlsonj 			unplumb_lif(lif);
14453431Scarlsonj 		}
14463431Scarlsonj 
14473431Scarlsonj 		release_lease(dlp);
14483431Scarlsonj 	}
14493431Scarlsonj }
14503431Scarlsonj 
14513431Scarlsonj /*
14523431Scarlsonj  * cancel_lease_timer(): cancels a lease-related timer
14533431Scarlsonj  *
14543431Scarlsonj  *   input: dhcp_lease_t *: the lease to operate on
14553431Scarlsonj  *	    dhcp_timer_t *: the timer to cancel
14563431Scarlsonj  *  output: void
14573431Scarlsonj  */
14583431Scarlsonj 
14593431Scarlsonj static void
14603431Scarlsonj cancel_lease_timer(dhcp_lease_t *dlp, dhcp_timer_t *dt)
14613431Scarlsonj {
14623431Scarlsonj 	if (dt->dt_id == -1)
14633431Scarlsonj 		return;
14643431Scarlsonj 	if (cancel_timer(dt)) {
14653431Scarlsonj 		release_lease(dlp);
14663431Scarlsonj 	} else {
14673431Scarlsonj 		dhcpmsg(MSG_WARNING,
14683431Scarlsonj 		    "cancel_lease_timer: cannot cancel timer");
14693431Scarlsonj 	}
14703431Scarlsonj }
14713431Scarlsonj 
14723431Scarlsonj /*
14733431Scarlsonj  * cancel_lease_timers(): cancels an lease's pending timers
14743431Scarlsonj  *
14753431Scarlsonj  *   input: dhcp_lease_t *: the lease to operate on
14763431Scarlsonj  *  output: void
14773431Scarlsonj  */
14783431Scarlsonj 
14793431Scarlsonj void
14803431Scarlsonj cancel_lease_timers(dhcp_lease_t *dlp)
14813431Scarlsonj {
14823431Scarlsonj 	cancel_lease_timer(dlp, &dlp->dl_t1);
14833431Scarlsonj 	cancel_lease_timer(dlp, &dlp->dl_t2);
14843431Scarlsonj }
14853431Scarlsonj 
14863431Scarlsonj /*
14873431Scarlsonj  * schedule_lease_timer(): schedules a lease-related timer
14883431Scarlsonj  *
14893431Scarlsonj  *   input: dhcp_lease_t *: the lease to operate on
14903431Scarlsonj  *	    dhcp_timer_t *: the timer to schedule
14913431Scarlsonj  *	    iu_tq_callback_t *: the callback to call upon firing
14923431Scarlsonj  *  output: boolean_t: B_TRUE if the timer was scheduled successfully
14933431Scarlsonj  */
14943431Scarlsonj 
14953431Scarlsonj boolean_t
14963431Scarlsonj schedule_lease_timer(dhcp_lease_t *dlp, dhcp_timer_t *dt,
14973431Scarlsonj     iu_tq_callback_t *expire)
14983431Scarlsonj {
14993431Scarlsonj 	/*
15003431Scarlsonj 	 * If there's a timer running, cancel it and release its lease
15013431Scarlsonj 	 * reference.
15023431Scarlsonj 	 */
15033431Scarlsonj 	if (dt->dt_id != -1) {
15043431Scarlsonj 		if (!cancel_timer(dt))
15053431Scarlsonj 			return (B_FALSE);
15063431Scarlsonj 		release_lease(dlp);
15073431Scarlsonj 	}
15083431Scarlsonj 
15093431Scarlsonj 	if (schedule_timer(dt, expire, dlp)) {
15103431Scarlsonj 		hold_lease(dlp);
15113431Scarlsonj 		return (B_TRUE);
15123431Scarlsonj 	} else {
15133431Scarlsonj 		dhcpmsg(MSG_WARNING,
15143431Scarlsonj 		    "schedule_lease_timer: cannot schedule timer");
15153431Scarlsonj 		return (B_FALSE);
15163431Scarlsonj 	}
15173431Scarlsonj }
15183431Scarlsonj 
15193431Scarlsonj /*
15203431Scarlsonj  * deprecate_leases(): remove all of the leases from a given state machine
15213431Scarlsonj  *
15223431Scarlsonj  *   input: dhcp_smach_t *: the state machine
15233431Scarlsonj  *  output: none
15243431Scarlsonj  */
15253431Scarlsonj 
15263431Scarlsonj void
15273431Scarlsonj deprecate_leases(dhcp_smach_t *dsmp)
15283431Scarlsonj {
15293431Scarlsonj 	dhcp_lease_t *dlp;
15303431Scarlsonj 
15313431Scarlsonj 	/*
15323431Scarlsonj 	 * note that due to infelicities in the routing code, any default
15333431Scarlsonj 	 * routes must be removed prior to canonizing or deprecating the LIF.
15343431Scarlsonj 	 */
15353431Scarlsonj 
15363431Scarlsonj 	remove_default_routes(dsmp);
15373431Scarlsonj 
15383431Scarlsonj 	while ((dlp = dsmp->dsm_leases) != NULL)
15393431Scarlsonj 		remove_lease(dlp);
15403431Scarlsonj }
15413431Scarlsonj 
15423431Scarlsonj /*
15433431Scarlsonj  * verify_smach(): if the state machine is in a bound state, then verify the
15443431Scarlsonj  *		   standing of the configured interfaces.  Abandon those that
15453431Scarlsonj  *		   the user has modified.  If we end up with no valid leases,
15463431Scarlsonj  *		   then just terminate the state machine.
15473431Scarlsonj  *
15483431Scarlsonj  *   input: dhcp_smach_t *: the state machine
15493431Scarlsonj  *  output: boolean_t: B_TRUE if the state machine is still valid.
15503431Scarlsonj  *    note: assumes caller holds a state machine reference; as with most
15513431Scarlsonj  *	    callback functions.
15523431Scarlsonj  */
15533431Scarlsonj 
15543431Scarlsonj boolean_t
15553431Scarlsonj verify_smach(dhcp_smach_t *dsmp)
15563431Scarlsonj {
15573431Scarlsonj 	dhcp_lease_t *dlp, *dlpn;
15583431Scarlsonj 
15593431Scarlsonj 	if (dsmp->dsm_dflags & DHCP_IF_REMOVED) {
15603431Scarlsonj 		release_smach(dsmp);
15613431Scarlsonj 		return (B_FALSE);
15623431Scarlsonj 	}
15633431Scarlsonj 
15643431Scarlsonj 	if (!dsmp->dsm_isv6) {
15653431Scarlsonj 		/*
15663431Scarlsonj 		 * If this is DHCPv4, then verify the main LIF.
15673431Scarlsonj 		 */
15683431Scarlsonj 		if (!verify_lif(dsmp->dsm_lif))
15693431Scarlsonj 			goto smach_terminate;
15703431Scarlsonj 	}
15713431Scarlsonj 
15723431Scarlsonj 	/*
15733431Scarlsonj 	 * If we're not in one of the bound states, then there are no LIFs to
15743431Scarlsonj 	 * verify here.
15753431Scarlsonj 	 */
15763431Scarlsonj 	if (dsmp->dsm_state != BOUND &&
15773431Scarlsonj 	    dsmp->dsm_state != RENEWING &&
15783431Scarlsonj 	    dsmp->dsm_state != REBINDING) {
15793431Scarlsonj 		release_smach(dsmp);
15803431Scarlsonj 		return (B_TRUE);
15813431Scarlsonj 	}
15823431Scarlsonj 
15833431Scarlsonj 	for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlpn) {
15843431Scarlsonj 		dhcp_lif_t *lif, *lifnext;
15853431Scarlsonj 		uint_t nlifs;
15863431Scarlsonj 
15873431Scarlsonj 		dlpn = dlp->dl_next;
15883431Scarlsonj 		lif = dlp->dl_lifs;
15893431Scarlsonj 		nlifs = dlp->dl_nlifs;
15903431Scarlsonj 		for (; nlifs > 0; lif = lifnext, nlifs--) {
15913431Scarlsonj 			lifnext = lif->lif_next;
15923431Scarlsonj 			if (!verify_lif(lif)) {
15933431Scarlsonj 				/*
15943431Scarlsonj 				 * User has manipulated the interface.  Even
15953431Scarlsonj 				 * if we plumbed it, we must now disown it.
15963431Scarlsonj 				 */
15973431Scarlsonj 				lif->lif_plumbed = B_FALSE;
15983431Scarlsonj 				remove_lif(lif);
15993431Scarlsonj 			}
16003431Scarlsonj 		}
16013431Scarlsonj 		if (dlp->dl_nlifs == 0)
16023431Scarlsonj 			remove_lease(dlp);
16033431Scarlsonj 	}
16043431Scarlsonj 
16053431Scarlsonj 	/*
16063431Scarlsonj 	 * If there are leases left, then everything's ok.
16073431Scarlsonj 	 */
16083431Scarlsonj 	if (dsmp->dsm_leases != NULL) {
16093431Scarlsonj 		release_smach(dsmp);
16103431Scarlsonj 		return (B_TRUE);
16113431Scarlsonj 	}
16123431Scarlsonj 
16133431Scarlsonj smach_terminate:
16143431Scarlsonj 	finished_smach(dsmp, DHCP_IPC_E_UNKIF);
16153431Scarlsonj 	release_smach(dsmp);
16163431Scarlsonj 
16173431Scarlsonj 	return (B_FALSE);
16183431Scarlsonj }
1619