/* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016-2017, Chris Fraire . * * This module contains core functions for managing DHCP state machine * instances. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "agent.h" #include "states.h" #include "interface.h" #include "defaults.h" #include "script_handler.h" static uint_t global_smach_count; static uchar_t *global_duid; static size_t global_duidlen; /* * iaid_retry(): attempt to write LIF IAID again * * input: iu_tq_t *: ignored * void *: pointer to LIF * output: none */ /* ARGSUSED */ static void iaid_retry(iu_tq_t *tqp, void *arg) { dhcp_lif_t *lif = arg; if (write_stable_iaid(lif->lif_name, lif->lif_iaid) == -1) { if (errno != EROFS) { dhcpmsg(MSG_ERR, "iaid_retry: unable to write out IAID for %s", lif->lif_name); release_lif(lif); } else { lif->lif_iaid_id = iu_schedule_timer(tq, 60, iaid_retry, lif); } } else { release_lif(lif); } } /* * parse_param_list(): parse a parameter list. * * input: const char *: parameter list string with comma-separated entries * uint_t *: return parameter; number of entries decoded * const char *: name of parameter list for logging purposes * dhcp_smach_t *: smach pointer for logging * output: uint16_t *: allocated array of parameters, or NULL if none. */ static uint16_t * parse_param_list(const char *param_list, uint_t *param_cnt, const char *param_name, dhcp_smach_t *dsmp) { int i, maxparam; char tsym[DSYM_MAX_SYM_LEN + 1]; uint16_t *params; const char *cp; dhcp_symbol_t *entry; *param_cnt = 0; if (param_list == NULL) return (NULL); for (maxparam = 1, i = 0; param_list[i] != '\0'; i++) { if (param_list[i] == ',') maxparam++; } params = malloc(maxparam * sizeof (*params)); if (params == NULL) { dhcpmsg(MSG_WARNING, "cannot allocate parameter %s list for %s (continuing)", param_name, dsmp->dsm_name); return (NULL); } for (i = 0; i < maxparam; ) { if (isspace(*param_list)) param_list++; /* extract the next element on the list */ cp = strchr(param_list, ','); if (cp == NULL || cp - param_list >= sizeof (tsym)) (void) strlcpy(tsym, param_list, sizeof (tsym)); else (void) strlcpy(tsym, param_list, cp - param_list + 1); /* LINTED -- do nothing with blanks on purpose */ if (tsym[0] == '\0') { ; } else if (isalpha(tsym[0])) { entry = inittab_getbyname(ITAB_CAT_SITE | ITAB_CAT_STANDARD | (dsmp->dsm_isv6 ? ITAB_CAT_V6 : 0), ITAB_CONS_INFO, tsym); if (entry == NULL) { dhcpmsg(MSG_INFO, "ignored unknown %s list " "entry '%s' for %s", param_name, tsym, dsmp->dsm_name); } else { params[i++] = entry->ds_code; free(entry); } } else { params[i++] = strtoul(tsym, NULL, 0); } if (cp == NULL) break; param_list = cp + 1; } *param_cnt = i; return (params); } /* * insert_smach(): Create a state machine instance on a given logical * interface. The state machine holds the caller's LIF * reference on success, and frees it on failure. * * input: dhcp_lif_t *: logical interface name * int *: set to DHCP_IPC_E_* if creation fails * output: dhcp_smach_t *: state machine instance */ dhcp_smach_t * insert_smach(dhcp_lif_t *lif, int *error) { dhcp_smach_t *dsmp, *alt_primary; boolean_t isv6; const char *plist; if ((dsmp = calloc(1, sizeof (*dsmp))) == NULL) { dhcpmsg(MSG_ERR, "cannot allocate state machine entry for %s", lif->lif_name); remove_lif(lif); release_lif(lif); *error = DHCP_IPC_E_MEMORY; return (NULL); } dsmp->dsm_name = lif->lif_name; dsmp->dsm_lif = lif; dsmp->dsm_hold_count = 1; dsmp->dsm_state = INIT; dsmp->dsm_dflags = DHCP_IF_REMOVED; /* until added to list */ isv6 = lif->lif_pif->pif_isv6; /* * Now that we have a controlling LIF, we need to assign an IAID to * that LIF. */ if (lif->lif_iaid == 0 && (lif->lif_iaid = read_stable_iaid(lif->lif_name)) == 0) { static uint32_t iaidctr = 0x80000000u; /* * If this is a logical interface, then use an arbitrary seed * value. Otherwise, use the ifIndex. */ lif->lif_iaid = make_stable_iaid(lif->lif_name, strchr(lif->lif_name, ':') != NULL ? iaidctr++ : lif->lif_pif->pif_index); dhcpmsg(MSG_INFO, "insert_smach: manufactured IAID %u for v%d %s", lif->lif_iaid, isv6 ? 6 : 4, lif->lif_name); hold_lif(lif); iaid_retry(NULL, lif); } if (isv6) { dsmp->dsm_dflags |= DHCP_IF_V6; dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; /* * With DHCPv6, we do all of our I/O using the common * v6_sock_fd. There's no need for per-interface file * descriptors because we have IPV6_PKTINFO. */ } else { IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST), &dsmp->dsm_server); /* * With IPv4 DHCP, we use a socket per lif. */ if (!open_ip_lif(lif, INADDR_ANY, B_TRUE)) { dhcpmsg(MSG_ERR, "unable to open socket for %s", lif->lif_name); /* This will also dispose of the LIF */ release_smach(dsmp); *error = DHCP_IPC_E_SOCKET; return (NULL); } } script_init(dsmp); ipc_action_init(&dsmp->dsm_ia); dsmp->dsm_neg_hrtime = gethrtime(); dsmp->dsm_offer_timer = -1; dsmp->dsm_start_timer = -1; dsmp->dsm_retrans_timer = -1; /* * Initialize the parameter request and ignore lists, if any. */ plist = df_get_string(dsmp->dsm_name, isv6, DF_PARAM_REQUEST_LIST); dsmp->dsm_prl = parse_param_list(plist, &dsmp->dsm_prllen, "request", dsmp); plist = df_get_string(dsmp->dsm_name, isv6, DF_PARAM_IGNORE_LIST); dsmp->dsm_pil = parse_param_list(plist, &dsmp->dsm_pillen, "ignore", dsmp); dsmp->dsm_offer_wait = df_get_int(dsmp->dsm_name, isv6, DF_OFFER_WAIT); /* * If there is no primary of this type, and there is one of the other, * then make this one primary if it's on the same named PIF. */ if (primary_smach(isv6) == NULL && (alt_primary = primary_smach(!isv6)) != NULL) { if (strcmp(lif->lif_pif->pif_name, alt_primary->dsm_lif->lif_pif->pif_name) == 0) { dhcpmsg(MSG_DEBUG, "insert_smach: making %s primary for v%d", dsmp->dsm_name, isv6 ? 6 : 4); dsmp->dsm_dflags |= DHCP_IF_PRIMARY; } } /* * We now have at least one state machine running, so cancel any * running inactivity timer. */ if (inactivity_id != -1 && iu_cancel_timer(tq, inactivity_id, NULL) == 1) inactivity_id = -1; dsmp->dsm_dflags &= ~DHCP_IF_REMOVED; insque(dsmp, &lif->lif_smachs); global_smach_count++; dhcpmsg(MSG_DEBUG2, "insert_smach: inserted %s", dsmp->dsm_name); return (dsmp); } /* * hold_smach(): acquires a hold on a state machine * * input: dhcp_smach_t *: the state machine to acquire a hold on * output: void */ void hold_smach(dhcp_smach_t *dsmp) { dsmp->dsm_hold_count++; dhcpmsg(MSG_DEBUG2, "hold_smach: hold count on %s: %d", dsmp->dsm_name, dsmp->dsm_hold_count); } /* * free_smach(): frees the memory occupied by a state machine * * input: dhcp_smach_t *: the DHCP state machine to free * output: void */ static void free_smach(dhcp_smach_t *dsmp) { dhcpmsg(MSG_DEBUG, "free_smach: freeing state machine %s", dsmp->dsm_name); deprecate_leases(dsmp); remove_lif(dsmp->dsm_lif); release_lif(dsmp->dsm_lif); free_pkt_list(&dsmp->dsm_recv_pkt_list); if (dsmp->dsm_ack != dsmp->dsm_orig_ack) free_pkt_entry(dsmp->dsm_orig_ack); free_pkt_entry(dsmp->dsm_ack); free(dsmp->dsm_send_pkt.pkt); free(dsmp->dsm_cid); free(dsmp->dsm_prl); free(dsmp->dsm_pil); free(dsmp->dsm_routers); free(dsmp->dsm_reqhost); free(dsmp->dsm_msg_reqhost); free(dsmp->dsm_dhcp_domainname); free(dsmp); /* no big deal if this fails */ if (global_smach_count == 0 && inactivity_id == -1) { inactivity_id = iu_schedule_timer(tq, DHCP_INACTIVITY_WAIT, inactivity_shutdown, NULL); } } /* * release_smach(): releases a hold previously acquired on a state machine. * If the hold count reaches 0, the state machine is freed. * * input: dhcp_smach_t *: the state machine entry to release the hold on * output: void */ void release_smach(dhcp_smach_t *dsmp) { if (dsmp->dsm_hold_count == 0) { dhcpmsg(MSG_CRIT, "release_smach: extraneous release"); return; } if (dsmp->dsm_hold_count == 1 && !(dsmp->dsm_dflags & DHCP_IF_REMOVED)) { dhcpmsg(MSG_CRIT, "release_smach: missing removal"); return; } if (--dsmp->dsm_hold_count == 0) { free_smach(dsmp); } else { dhcpmsg(MSG_DEBUG2, "release_smach: hold count on %s: %d", dsmp->dsm_name, dsmp->dsm_hold_count); } } /* * next_smach(): state machine iterator function * * input: dhcp_smach_t *: current state machine (or NULL for list start) * boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise * output: dhcp_smach_t *: next state machine in list */ dhcp_smach_t * next_smach(dhcp_smach_t *dsmp, boolean_t isv6) { dhcp_lif_t *lif; dhcp_pif_t *pif; if (dsmp != NULL) { if (dsmp->dsm_next != NULL) return (dsmp->dsm_next); if ((lif = dsmp->dsm_lif) != NULL) lif = lif->lif_next; for (; lif != NULL; lif = lif->lif_next) { if (lif->lif_smachs != NULL) return (lif->lif_smachs); } if ((pif = dsmp->dsm_lif->lif_pif) != NULL) pif = pif->pif_next; } else { pif = isv6 ? v6root : v4root; } for (; pif != NULL; pif = pif->pif_next) { for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { if (lif->lif_smachs != NULL) return (lif->lif_smachs); } } return (NULL); } /* * primary_smach(): loop through all state machines of the given type (v4 or * v6) in the system, and locate the one that's primary. * * input: boolean_t: B_TRUE for IPv6 * output: dhcp_smach_t *: the primary state machine */ dhcp_smach_t * primary_smach(boolean_t isv6) { dhcp_smach_t *dsmp; for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { if (dsmp->dsm_dflags & DHCP_IF_PRIMARY) break; } return (dsmp); } /* * info_primary_smach(): loop through all state machines of the given type (v4 * or v6) in the system, and locate the one that should * be considered "primary" for dhcpinfo. * * input: boolean_t: B_TRUE for IPv6 * output: dhcp_smach_t *: the dhcpinfo primary state machine */ dhcp_smach_t * info_primary_smach(boolean_t isv6) { dhcp_smach_t *bestdsm = NULL; dhcp_smach_t *dsmp; for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { /* * If there is a primary, then something previously went wrong * with verification, because the caller uses primary_smach() * before calling this routine. There's nothing else we can do * but return failure, as the designated primary must be bad. */ if (dsmp->dsm_dflags & DHCP_IF_PRIMARY) return (NULL); /* If we have no information, then we're not primary. */ if (dsmp->dsm_ack == NULL) continue; /* * Among those interfaces that have DHCP information, the * "primary" is the one that sorts lexically first. */ if (bestdsm == NULL || strcmp(dsmp->dsm_name, bestdsm->dsm_name) < 0) bestdsm = dsmp; } return (bestdsm); } /* * make_primary(): designate a given state machine as being the primary * instance on the primary interface. Note that the user often * thinks in terms of a primary "interface" (rather than just * an instance), so we go to lengths here to keep v4 and v6 in * sync. * * input: dhcp_smach_t *: the primary state machine * output: none */ void make_primary(dhcp_smach_t *dsmp) { dhcp_smach_t *old_primary, *alt_primary; dhcp_pif_t *pif; if ((old_primary = primary_smach(dsmp->dsm_isv6)) != NULL) old_primary->dsm_dflags &= ~DHCP_IF_PRIMARY; dsmp->dsm_dflags |= DHCP_IF_PRIMARY; /* * Find the primary for the other protocol. */ alt_primary = primary_smach(!dsmp->dsm_isv6); /* * If it's on a different interface, then cancel that. If it's on the * same interface, then we're done. */ if (alt_primary != NULL) { if (strcmp(alt_primary->dsm_lif->lif_pif->pif_name, dsmp->dsm_lif->lif_pif->pif_name) == 0) return; alt_primary->dsm_dflags &= ~DHCP_IF_PRIMARY; } /* * We need a new primary for the other protocol. If the PIF exists, * there must be at least one state machine. Just choose the first for * consistency with insert_smach(). */ if ((pif = lookup_pif_by_name(dsmp->dsm_lif->lif_pif->pif_name, !dsmp->dsm_isv6)) != NULL) { pif->pif_lifs->lif_smachs->dsm_dflags |= DHCP_IF_PRIMARY; } } /* * lookup_smach(): finds a state machine by name and type; used for dispatching * user commands. * * input: const char *: the name of the state machine * boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise * output: dhcp_smach_t *: the state machine found */ dhcp_smach_t * lookup_smach(const char *smname, boolean_t isv6) { dhcp_smach_t *dsmp; for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { if (strcmp(dsmp->dsm_name, smname) == 0) break; } return (dsmp); } /* * lookup_smach_by_uindex(): iterate through running state machines by * truncated interface index. * * input: uint16_t: the interface index (truncated) * dhcp_smach_t *: the previous state machine, or NULL for start * boolean_t: B_TRUE for DHCPv6, B_FALSE for IPv4 DHCP * output: dhcp_smach_t *: next state machine, or NULL at end of list */ dhcp_smach_t * lookup_smach_by_uindex(uint16_t ifindex, dhcp_smach_t *dsmp, boolean_t isv6) { dhcp_pif_t *pif; dhcp_lif_t *lif; /* * If the user gives us a state machine, then check that the next one * available is on the same physical interface. If so, then go ahead * and return that. */ if (dsmp != NULL) { pif = dsmp->dsm_lif->lif_pif; if ((dsmp = next_smach(dsmp, isv6)) == NULL) return (NULL); if (pif == dsmp->dsm_lif->lif_pif) return (dsmp); } else { /* Otherwise, start at the beginning of the list */ pif = NULL; } /* * Find the next physical interface with the same truncated interface * index, and return the first state machine on that. If there are no * more physical interfaces that match, then we're done. */ do { pif = lookup_pif_by_uindex(ifindex, pif, isv6); if (pif == NULL) return (NULL); for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { if ((dsmp = lif->lif_smachs) != NULL) break; } } while (dsmp == NULL); return (dsmp); } /* * lookup_smach_by_xid(): iterate through running state machines by transaction * id. Transaction ID zero means "all state machines." * * input: uint32_t: the transaction id to look up * dhcp_smach_t *: the previous state machine, or NULL for start * boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise * output: dhcp_smach_t *: next state machine, or NULL at end of list */ dhcp_smach_t * lookup_smach_by_xid(uint32_t xid, dhcp_smach_t *dsmp, boolean_t isv6) { for (dsmp = next_smach(dsmp, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { if (xid == 0 || pkt_get_xid(dsmp->dsm_send_pkt.pkt, isv6) == xid) break; } return (dsmp); } /* * lookup_smach_by_event(): find a state machine busy with a particular event * ID. This is used only for error handling. * * input: iu_event_id_t: the event id to look up * output: dhcp_smach_t *: matching state machine, or NULL if none */ dhcp_smach_t * lookup_smach_by_event(iu_event_id_t eid) { dhcp_smach_t *dsmp; boolean_t isv6 = B_FALSE; for (;;) { for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { if ((dsmp->dsm_dflags & DHCP_IF_BUSY) && eid == dsmp->dsm_ia.ia_eid) return (dsmp); } if (isv6) break; isv6 = B_TRUE; } return (dsmp); } /* * cancel_offer_timer(): stop the offer polling timer on a given state machine * * input: dhcp_smach_t *: state machine on which to stop polling for offers * output: none */ void cancel_offer_timer(dhcp_smach_t *dsmp) { int retval; if (dsmp->dsm_offer_timer != -1) { retval = iu_cancel_timer(tq, dsmp->dsm_offer_timer, NULL); dsmp->dsm_offer_timer = -1; if (retval == 1) release_smach(dsmp); } } /* * cancel_smach_timers(): stop all of the timers related to a given state * machine, including lease and LIF expiry. * * input: dhcp_smach_t *: state machine to cancel * output: none * note: this function assumes that the iu timer functions are synchronous * and thus don't require any protection or ordering on cancellation. */ void cancel_smach_timers(dhcp_smach_t *dsmp) { dhcp_lease_t *dlp; dhcp_lif_t *lif; uint_t nlifs; for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { cancel_lease_timers(dlp); lif = dlp->dl_lifs; nlifs = dlp->dl_nlifs; for (; nlifs > 0; nlifs--, lif = lif->lif_next) cancel_lif_timers(lif); } cancel_offer_timer(dsmp); stop_pkt_retransmission(dsmp); if (dsmp->dsm_start_timer != -1) { (void) iu_cancel_timer(tq, dsmp->dsm_start_timer, NULL); dsmp->dsm_start_timer = -1; release_smach(dsmp); } } /* * remove_smach(): removes a given state machine from the system. marks it * for being freed (but may not actually free it). * * input: dhcp_smach_t *: the state machine to remove * output: void */ void remove_smach(dhcp_smach_t *dsmp) { if (dsmp->dsm_dflags & DHCP_IF_REMOVED) return; dhcpmsg(MSG_DEBUG2, "remove_smach: removing %s", dsmp->dsm_name); dsmp->dsm_dflags |= DHCP_IF_REMOVED; remque(dsmp); global_smach_count--; /* * if we have long term timers, cancel them so that state machine * resources can be reclaimed in a reasonable amount of time. */ cancel_smach_timers(dsmp); /* Drop the hold that the LIF's state machine list had on us */ release_smach(dsmp); } /* * finished_smach(): we're finished with a given state machine; remove it from * the system and tell the user (who may have initiated the * removal process). Note that we remove it from the system * first to allow back-to-back drop and create invocations. * * input: dhcp_smach_t *: the state machine to remove * int: error for IPC * output: void */ void finished_smach(dhcp_smach_t *dsmp, int error) { hold_smach(dsmp); remove_smach(dsmp); if (dsmp->dsm_ia.ia_fd != -1) ipc_action_finish(dsmp, error); else (void) async_cancel(dsmp); release_smach(dsmp); } /* * is_bound_state(): checks if a state indicates the client is bound * * input: DHCPSTATE: the state to check * output: boolean_t: B_TRUE if the state is bound, B_FALSE if not */ boolean_t is_bound_state(DHCPSTATE state) { return (state == BOUND || state == REBINDING || state == INFORMATION || state == RELEASING || state == INFORM_SENT || state == RENEWING); } /* * set_smach_state(): changes state and updates I/O * * input: dhcp_smach_t *: the state machine to change * DHCPSTATE: the new state * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t set_smach_state(dhcp_smach_t *dsmp, DHCPSTATE state) { dhcp_lif_t *lif = dsmp->dsm_lif; if (dsmp->dsm_state != state) { dhcpmsg(MSG_DEBUG, "set_smach_state: changing from %s to %s on %s", dhcp_state_to_string(dsmp->dsm_state), dhcp_state_to_string(state), dsmp->dsm_name); /* * For IPv4, when we're in a bound state our socket must be * bound to our address. Otherwise, our socket must be bound * to INADDR_ANY. For IPv6, no such change is necessary. */ if (!dsmp->dsm_isv6) { if (is_bound_state(dsmp->dsm_state)) { if (!is_bound_state(state)) { close_ip_lif(lif); if (!open_ip_lif(lif, INADDR_ANY, B_FALSE)) return (B_FALSE); } } else { if (is_bound_state(state)) { close_ip_lif(lif); if (!open_ip_lif(lif, ntohl(lif->lif_addr), B_FALSE)) return (B_FALSE); } } } dsmp->dsm_state = state; } return (B_TRUE); } /* * duid_retry(): attempt to write DUID again * * input: iu_tq_t *: ignored * void *: ignored * output: none */ /* ARGSUSED */ static void duid_retry(iu_tq_t *tqp, void *arg) { if (write_stable_duid(global_duid, global_duidlen) == -1) { if (errno != EROFS) { dhcpmsg(MSG_ERR, "duid_retry: unable to write out DUID"); } else { (void) iu_schedule_timer(tq, 60, duid_retry, NULL); } } } /* * get_smach_cid(): gets the client ID for a given state machine. * * input: dhcp_smach_t *: the state machine to set up * output: int: DHCP_IPC_SUCCESS or one of DHCP_IPC_E_* on failure. */ int get_smach_cid(dhcp_smach_t *dsmp) { uchar_t *client_id; uint_t client_id_len; dhcp_lif_t *lif = dsmp->dsm_lif; dhcp_pif_t *pif = lif->lif_pif; const char *value; size_t slen; /* * Look in defaults file for the client-id. If present, this takes * precedence over all other forms of ID. */ dhcpmsg(MSG_DEBUG, "get_smach_cid: getting default client-id " "property on %s", dsmp->dsm_name); value = df_get_string(dsmp->dsm_name, pif->pif_isv6, DF_CLIENT_ID); if (value != NULL) { /* * The Client ID string can have one of three basic forms: * , * 0x * * * The first form is an RFC 3315 DUID. This is legal for both * IPv4 DHCP and DHCPv6. For IPv4, an RFC 4361 Client ID is * constructed from this value. * * The second and third forms are legal for IPv4 only. This is * a raw Client ID, in hex or ASCII string format. */ if (isdigit(*value) && value[strspn(value, "0123456789")] == ',') { char *cp; ulong_t duidtype; ulong_t subtype; errno = 0; duidtype = strtoul(value, &cp, 0); if (value == cp || errno != 0 || *cp != ',' || duidtype > 65535) { dhcpmsg(MSG_ERR, "get_smach_cid: cannot parse " "DUID type in %s", value); goto no_specified_id; } value = cp + 1; switch (duidtype) { case DHCPV6_DUID_LL: case DHCPV6_DUID_LLT: { int num; char chr; errno = 0; subtype = strtoul(value, &cp, 0); if (value == cp || errno != 0 || *cp != ',' || subtype > 65535) { dhcpmsg(MSG_ERR, "get_smach_cid: " "cannot parse MAC type in %s", value); goto no_specified_id; } value = cp + 1; client_id_len = pif->pif_isv6 ? 1 : 5; for (; *cp != '\0'; cp++) { if (*cp == ':') client_id_len++; else if (!isxdigit(*cp)) break; } if (duidtype == DHCPV6_DUID_LL) { duid_llt_t *dllt; time_t now; client_id_len += sizeof (*dllt); dllt = malloc(client_id_len); if (dllt == NULL) goto alloc_failure; dsmp->dsm_cid = (uchar_t *)dllt; dllt->dllt_dutype = htons(duidtype); dllt->dllt_hwtype = htons(subtype); now = time(NULL) - DUID_TIME_BASE; dllt->dllt_time = htonl(now); cp = (char *)(dllt + 1); } else { duid_ll_t *dll; client_id_len += sizeof (*dll); dll = malloc(client_id_len); if (dll == NULL) goto alloc_failure; dsmp->dsm_cid = (uchar_t *)dll; dll->dll_dutype = htons(duidtype); dll->dll_hwtype = htons(subtype); cp = (char *)(dll + 1); } num = 0; while ((chr = *value) != '\0') { if (isdigit(chr)) { num = (num << 4) + chr - '0'; } else if (isxdigit(chr)) { num = (num << 4) + 10 + chr - (isupper(chr) ? 'A' : 'a'); } else if (chr == ':') { *cp++ = num; num = 0; } else { break; } } break; } case DHCPV6_DUID_EN: { duid_en_t *den; errno = 0; subtype = strtoul(value, &cp, 0); if (value == cp || errno != 0 || *cp != ',') { dhcpmsg(MSG_ERR, "get_smach_cid: " "cannot parse enterprise in %s", value); goto no_specified_id; } value = cp + 1; slen = strlen(value); client_id_len = (slen + 1) / 2; den = malloc(sizeof (*den) + client_id_len); if (den == NULL) goto alloc_failure; den->den_dutype = htons(duidtype); DHCPV6_SET_ENTNUM(den, subtype); if (hexascii_to_octet(value, slen, den + 1, &client_id_len) != 0) { dhcpmsg(MSG_ERROR, "get_smach_cid: " "cannot parse hex string in %s", value); free(den); goto no_specified_id; } dsmp->dsm_cid = (uchar_t *)den; break; } default: slen = strlen(value); client_id_len = (slen + 1) / 2; cp = malloc(client_id_len); if (cp == NULL) goto alloc_failure; if (hexascii_to_octet(value, slen, cp, &client_id_len) != 0) { dhcpmsg(MSG_ERROR, "get_smach_cid: " "cannot parse hex string in %s", value); free(cp); goto no_specified_id; } dsmp->dsm_cid = (uchar_t *)cp; break; } dsmp->dsm_cidlen = client_id_len; if (!pif->pif_isv6) { (void) memmove(dsmp->dsm_cid + 5, dsmp->dsm_cid, client_id_len - 5); dsmp->dsm_cid[0] = 255; dsmp->dsm_cid[1] = lif->lif_iaid >> 24; dsmp->dsm_cid[2] = lif->lif_iaid >> 16; dsmp->dsm_cid[3] = lif->lif_iaid >> 8; dsmp->dsm_cid[4] = lif->lif_iaid; } return (DHCP_IPC_SUCCESS); } if (pif->pif_isv6) { dhcpmsg(MSG_ERROR, "get_smach_cid: client ID for %s invalid: %s", dsmp->dsm_name, value); } else if (strncasecmp("0x", value, 2) == 0 && value[2] != '\0') { /* skip past the 0x and convert the value to binary */ value += 2; slen = strlen(value); client_id_len = (slen + 1) / 2; dsmp->dsm_cid = malloc(client_id_len); if (dsmp->dsm_cid == NULL) goto alloc_failure; if (hexascii_to_octet(value, slen, dsmp->dsm_cid, &client_id_len) == 0) { dsmp->dsm_cidlen = client_id_len; return (DHCP_IPC_SUCCESS); } dhcpmsg(MSG_WARNING, "get_smach_cid: cannot convert " "hex value for Client ID on %s", dsmp->dsm_name); } else { client_id_len = strlen(value); dsmp->dsm_cid = malloc(client_id_len); if (dsmp->dsm_cid == NULL) goto alloc_failure; dsmp->dsm_cidlen = client_id_len; (void) memcpy(dsmp->dsm_cid, value, client_id_len); return (DHCP_IPC_SUCCESS); } } no_specified_id: /* * There was either no user-specified Client ID value, or we were * unable to parse it. We need to determine if a Client ID is required * and, if so, generate one. * * If it's IPv4, not in an IPMP group, not a logical interface, * and a DHCP default for DF_V4_DEFAULT_IAID_DUID is not affirmative, * then we need to preserve backward-compatibility by avoiding * new-fangled DUID/IAID construction. (Note: even for IPMP test * addresses, we construct a DUID/IAID since we may renew a lease for * an IPMP test address on any functioning IP interface in the group.) */ if (!pif->pif_isv6 && pif->pif_grifname[0] == '\0' && strchr(dsmp->dsm_name, ':') == NULL && !df_get_bool(dsmp->dsm_name, pif->pif_isv6, DF_V4_DEFAULT_IAID_DUID)) { if (pif->pif_hwtype == ARPHRD_IB) { /* * This comes from the DHCP over IPoIB specification. * In the absence of an user specified client id, IPoIB * automatically uses the required format, with the * unique 4 octet value set to 0 (since IPoIB driver * allows only a single interface on a port with a * specific GID to belong to an IP subnet (PSARC * 2001/289, FWARC 2002/702). * * Type Client-Identifier * +-----+-----+-----+-----+-----+----....----+ * | 0 | 0 (4 octets) | GID (16 octets)| * +-----+-----+-----+-----+-----+----....----+ */ dsmp->dsm_cidlen = 1 + 4 + 16; dsmp->dsm_cid = client_id = malloc(dsmp->dsm_cidlen); if (dsmp->dsm_cid == NULL) goto alloc_failure; /* * Pick the GID from the mac address. The format * of the hardware address is: * +-----+-----+-----+-----+----....----+ * | QPN (4 octets) | GID (16 octets)| * +-----+-----+-----+-----+----....----+ */ (void) memcpy(client_id + 5, pif->pif_hwaddr + 4, pif->pif_hwlen - 4); (void) memset(client_id, 0, 5); } return (DHCP_IPC_SUCCESS); } /* * Now check for a saved DUID. If there is one, then use it. If there * isn't, then generate a new one. For IPv4, we need to construct the * RFC 4361 Client ID with this value and the LIF's IAID. */ if (global_duid == NULL && (global_duid = read_stable_duid(&global_duidlen)) == NULL) { global_duid = make_stable_duid(pif->pif_name, &global_duidlen); if (global_duid == NULL) goto alloc_failure; duid_retry(NULL, NULL); } if (pif->pif_isv6) { dsmp->dsm_cid = malloc(global_duidlen); if (dsmp->dsm_cid == NULL) goto alloc_failure; (void) memcpy(dsmp->dsm_cid, global_duid, global_duidlen); dsmp->dsm_cidlen = global_duidlen; } else { dsmp->dsm_cid = malloc(5 + global_duidlen); if (dsmp->dsm_cid == NULL) goto alloc_failure; dsmp->dsm_cid[0] = 255; dsmp->dsm_cid[1] = lif->lif_iaid >> 24; dsmp->dsm_cid[2] = lif->lif_iaid >> 16; dsmp->dsm_cid[3] = lif->lif_iaid >> 8; dsmp->dsm_cid[4] = lif->lif_iaid; (void) memcpy(dsmp->dsm_cid + 5, global_duid, global_duidlen); dsmp->dsm_cidlen = 5 + global_duidlen; } return (DHCP_IPC_SUCCESS); alloc_failure: dhcpmsg(MSG_ERR, "get_smach_cid: cannot allocate Client Id for %s", dsmp->dsm_name); return (DHCP_IPC_E_MEMORY); } /* * smach_count(): returns the number of state machines running * * input: void * output: uint_t: the number of state machines */ uint_t smach_count(void) { return (global_smach_count); } /* * discard_default_routes(): removes a state machine's default routes alone. * * input: dhcp_smach_t *: the state machine whose default routes need to be * discarded * output: void */ void discard_default_routes(dhcp_smach_t *dsmp) { free(dsmp->dsm_routers); dsmp->dsm_routers = NULL; dsmp->dsm_nrouters = 0; } /* * remove_default_routes(): removes a state machine's default routes from the * kernel and from the state machine. * * input: dhcp_smach_t *: the state machine whose default routes need to be * removed * output: void */ void remove_default_routes(dhcp_smach_t *dsmp) { int idx; uint32_t ifindex; if (dsmp->dsm_routers != NULL) { ifindex = dsmp->dsm_lif->lif_pif->pif_index; for (idx = dsmp->dsm_nrouters - 1; idx >= 0; idx--) { if (del_default_route(ifindex, &dsmp->dsm_routers[idx])) { dhcpmsg(MSG_DEBUG, "remove_default_routes: " "removed %s from %s", inet_ntoa(dsmp->dsm_routers[idx]), dsmp->dsm_name); } else { dhcpmsg(MSG_INFO, "remove_default_routes: " "unable to remove %s from %s", inet_ntoa(dsmp->dsm_routers[idx]), dsmp->dsm_name); } } discard_default_routes(dsmp); } } /* * reset_smach(): resets a state machine to its initial state * * input: dhcp_smach_t *: the state machine to reset * output: void */ void reset_smach(dhcp_smach_t *dsmp) { dsmp->dsm_dflags &= ~DHCP_IF_FAILED; remove_default_routes(dsmp); free_pkt_list(&dsmp->dsm_recv_pkt_list); free_pkt_entry(dsmp->dsm_ack); if (dsmp->dsm_orig_ack != dsmp->dsm_ack) free_pkt_entry(dsmp->dsm_orig_ack); dsmp->dsm_ack = dsmp->dsm_orig_ack = NULL; free(dsmp->dsm_reqhost); dsmp->dsm_reqhost = NULL; /* * Do not reset dsm_msg_reqhost here. Unlike dsm_reqhost coming from * /etc/host.*, dsm_msg_reqhost comes externally, and it survives until * it is reset from another external message. */ free(dsmp->dsm_dhcp_domainname); dsmp->dsm_dhcp_domainname = NULL; cancel_smach_timers(dsmp); (void) set_smach_state(dsmp, INIT); if (dsmp->dsm_isv6) { dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; } else { IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST), &dsmp->dsm_server); } dsmp->dsm_neg_hrtime = gethrtime(); /* * We must never get here with a script running, since it means we're * resetting an smach that is still in the middle of another state * transition with a pending dsm_script_callback. */ assert(dsmp->dsm_script_pid == -1); } /* * refresh_smach(): refreshes a given state machine, as though awakened from * hibernation or by lower layer "link up." * * input: dhcp_smach_t *: state machine to refresh * output: void */ void refresh_smach(dhcp_smach_t *dsmp) { if (dsmp->dsm_state == BOUND || dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING || dsmp->dsm_state == INFORMATION) { dhcpmsg(MSG_WARNING, "refreshing state on %s", dsmp->dsm_name); cancel_smach_timers(dsmp); if (dsmp->dsm_state == INFORMATION) dhcp_inform(dsmp); else dhcp_init_reboot(dsmp); } } /* * refresh_smachs(): refreshes all finite leases under DHCP control * * input: iu_eh_t *: unused * int: unused * void *: unused * output: void */ /* ARGSUSED */ void refresh_smachs(iu_eh_t *eh, int sig, void *arg) { boolean_t isv6 = B_FALSE; dhcp_smach_t *dsmp; for (;;) { for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { refresh_smach(dsmp); } if (isv6) break; isv6 = B_TRUE; } } /* * nuke_smach_list(): delete the state machine list. For use when the * dhcpagent is exiting. * * input: none * output: none */ void nuke_smach_list(void) { boolean_t isv6 = B_FALSE; dhcp_smach_t *dsmp, *dsmp_next; for (;;) { for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = dsmp_next) { int status; dsmp_next = next_smach(dsmp, isv6); /* If we're already dropping or releasing, skip */ if (dsmp->dsm_droprelease) continue; dsmp->dsm_droprelease = B_TRUE; cancel_smach_timers(dsmp); /* * If the script is started by script_start, dhcp_drop * and dhcp_release should and will only be called * after the script exits. */ if (df_get_bool(dsmp->dsm_name, isv6, DF_RELEASE_ON_SIGTERM) || df_get_bool(dsmp->dsm_name, isv6, DF_VERIFIED_LEASE_ONLY)) { if (script_start(dsmp, isv6 ? EVENT_RELEASE6 : EVENT_RELEASE, dhcp_release, "DHCP agent is exiting", &status)) { continue; } if (status == 1) continue; } (void) script_start(dsmp, isv6 ? EVENT_DROP6 : EVENT_DROP, dhcp_drop, NULL, NULL); } if (isv6) break; isv6 = B_TRUE; } } /* * insert_lease(): Create a lease structure on a given state machine. The * lease holds a reference to the state machine. * * input: dhcp_smach_t *: state machine * output: dhcp_lease_t *: newly-created lease */ dhcp_lease_t * insert_lease(dhcp_smach_t *dsmp) { dhcp_lease_t *dlp; if ((dlp = calloc(1, sizeof (*dlp))) == NULL) return (NULL); dlp->dl_smach = dsmp; dlp->dl_hold_count = 1; init_timer(&dlp->dl_t1, 0); init_timer(&dlp->dl_t2, 0); insque(dlp, &dsmp->dsm_leases); dhcpmsg(MSG_DEBUG2, "insert_lease: new lease for %s", dsmp->dsm_name); return (dlp); } /* * hold_lease(): acquires a hold on a lease * * input: dhcp_lease_t *: the lease to acquire a hold on * output: void */ void hold_lease(dhcp_lease_t *dlp) { dlp->dl_hold_count++; dhcpmsg(MSG_DEBUG2, "hold_lease: hold count on lease for %s: %d", dlp->dl_smach->dsm_name, dlp->dl_hold_count); } /* * release_lease(): releases a hold previously acquired on a lease. * If the hold count reaches 0, the lease is freed. * * input: dhcp_lease_t *: the lease to release the hold on * output: void */ void release_lease(dhcp_lease_t *dlp) { if (dlp->dl_hold_count == 0) { dhcpmsg(MSG_CRIT, "release_lease: extraneous release"); return; } if (dlp->dl_hold_count == 1 && !dlp->dl_removed) { dhcpmsg(MSG_CRIT, "release_lease: missing removal"); return; } if (--dlp->dl_hold_count == 0) { dhcpmsg(MSG_DEBUG, "release_lease: freeing lease on state machine %s", dlp->dl_smach->dsm_name); free(dlp); } else { dhcpmsg(MSG_DEBUG2, "release_lease: hold count on lease for %s: %d", dlp->dl_smach->dsm_name, dlp->dl_hold_count); } } /* * remove_lease(): removes a given lease from the state machine and drops the * state machine's hold on the lease. * * input: dhcp_lease_t *: the lease to remove * output: void */ void remove_lease(dhcp_lease_t *dlp) { if (dlp->dl_removed) { dhcpmsg(MSG_CRIT, "remove_lease: extraneous removal"); } else { dhcp_lif_t *lif, *lifnext; uint_t nlifs; dhcpmsg(MSG_DEBUG, "remove_lease: removed lease from state machine %s", dlp->dl_smach->dsm_name); dlp->dl_removed = B_TRUE; remque(dlp); cancel_lease_timers(dlp); lif = dlp->dl_lifs; nlifs = dlp->dl_nlifs; for (; nlifs > 0; nlifs--, lif = lifnext) { lifnext = lif->lif_next; unplumb_lif(lif); } release_lease(dlp); } } /* * cancel_lease_timer(): cancels a lease-related timer * * input: dhcp_lease_t *: the lease to operate on * dhcp_timer_t *: the timer to cancel * output: void */ static void cancel_lease_timer(dhcp_lease_t *dlp, dhcp_timer_t *dt) { if (dt->dt_id == -1) return; if (cancel_timer(dt)) { release_lease(dlp); } else { dhcpmsg(MSG_WARNING, "cancel_lease_timer: cannot cancel timer"); } } /* * cancel_lease_timers(): cancels an lease's pending timers * * input: dhcp_lease_t *: the lease to operate on * output: void */ void cancel_lease_timers(dhcp_lease_t *dlp) { cancel_lease_timer(dlp, &dlp->dl_t1); cancel_lease_timer(dlp, &dlp->dl_t2); } /* * schedule_lease_timer(): schedules a lease-related timer * * input: dhcp_lease_t *: the lease to operate on * dhcp_timer_t *: the timer to schedule * iu_tq_callback_t *: the callback to call upon firing * output: boolean_t: B_TRUE if the timer was scheduled successfully */ boolean_t schedule_lease_timer(dhcp_lease_t *dlp, dhcp_timer_t *dt, iu_tq_callback_t *expire) { /* * If there's a timer running, cancel it and release its lease * reference. */ if (dt->dt_id != -1) { if (!cancel_timer(dt)) return (B_FALSE); release_lease(dlp); } if (schedule_timer(dt, expire, dlp)) { hold_lease(dlp); return (B_TRUE); } else { dhcpmsg(MSG_WARNING, "schedule_lease_timer: cannot schedule timer"); return (B_FALSE); } } /* * deprecate_leases(): remove all of the leases from a given state machine * * input: dhcp_smach_t *: the state machine * output: none */ void deprecate_leases(dhcp_smach_t *dsmp) { dhcp_lease_t *dlp; /* * note that due to infelicities in the routing code, any default * routes must be removed prior to canonizing or deprecating the LIF. */ remove_default_routes(dsmp); while ((dlp = dsmp->dsm_leases) != NULL) remove_lease(dlp); } /* * verify_smach(): if the state machine is in a bound state, then verify the * standing of the configured interfaces. Abandon those that * the user has modified. If we end up with no valid leases, * then just terminate the state machine. * * input: dhcp_smach_t *: the state machine * output: boolean_t: B_TRUE if the state machine is still valid. * note: assumes caller holds a state machine reference; as with most * callback functions. */ boolean_t verify_smach(dhcp_smach_t *dsmp) { dhcp_lease_t *dlp, *dlpn; if (dsmp->dsm_dflags & DHCP_IF_REMOVED) { release_smach(dsmp); return (B_FALSE); } if (!dsmp->dsm_isv6) { /* * If this is DHCPv4, then verify the main LIF. */ if (!verify_lif(dsmp->dsm_lif)) goto smach_terminate; } /* * If we're not in one of the bound states, then there are no LIFs to * verify here. */ if (dsmp->dsm_state != BOUND && dsmp->dsm_state != RENEWING && dsmp->dsm_state != REBINDING) { release_smach(dsmp); return (B_TRUE); } for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlpn) { dhcp_lif_t *lif, *lifnext; uint_t nlifs; dlpn = dlp->dl_next; lif = dlp->dl_lifs; nlifs = dlp->dl_nlifs; for (; nlifs > 0; lif = lifnext, nlifs--) { lifnext = lif->lif_next; if (!verify_lif(lif)) { /* * User has manipulated the interface. Even * if we plumbed it, we must now disown it. */ lif->lif_plumbed = B_FALSE; remove_lif(lif); } } if (dlp->dl_nlifs == 0) remove_lease(dlp); } /* * If there are leases left, then everything's ok. */ if (dsmp->dsm_leases != NULL) { release_smach(dsmp); return (B_TRUE); } smach_terminate: finished_smach(dsmp, DHCP_IPC_E_INVIF); release_smach(dsmp); return (B_FALSE); }