#include <assert.h>
#include <stdlib.h>
#include <search.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/arp.h>
#include <arpa/inet.h>
#include <dhcpmsg.h>
#include <dhcpagent_util.h>
#include <dhcp_stable.h>
#include <dhcp_inittab.h>
#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;
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);
}
}
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++;
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);
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);
}
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;
isv6 = lif->lif_pif->pif_isv6;
if (lif->lif_iaid == 0 &&
(lif->lif_iaid = read_stable_iaid(lif->lif_name)) == 0) {
static uint32_t iaidctr = 0x80000000u;
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;
} else {
IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST),
&dsmp->dsm_server);
if (!open_ip_lif(lif, INADDR_ANY, B_TRUE)) {
dhcpmsg(MSG_ERR, "unable to open socket for %s",
lif->lif_name);
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;
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 (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;
}
}
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);
}
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);
}
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);
if (global_smach_count == 0 && inactivity_id == -1) {
inactivity_id = iu_schedule_timer(tq, DHCP_INACTIVITY_WAIT,
inactivity_shutdown, NULL);
}
}
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);
}
}
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);
}
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);
}
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 (dsmp->dsm_dflags & DHCP_IF_PRIMARY)
return (NULL);
if (dsmp->dsm_ack == NULL)
continue;
if (bestdsm == NULL ||
strcmp(dsmp->dsm_name, bestdsm->dsm_name) < 0)
bestdsm = dsmp;
}
return (bestdsm);
}
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;
alt_primary = primary_smach(!dsmp->dsm_isv6);
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;
}
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;
}
}
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);
}
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 (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 {
pif = NULL;
}
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);
}
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);
}
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);
}
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);
}
}
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);
}
}
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--;
cancel_smach_timers(dsmp);
release_smach(dsmp);
}
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);
}
boolean_t
is_bound_state(DHCPSTATE state)
{
return (state == BOUND || state == REBINDING || state == INFORMATION ||
state == RELEASING || state == INFORM_SENT || state == RENEWING);
}
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);
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);
}
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);
}
}
}
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;
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) {
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') {
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:
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) {
dsmp->dsm_cidlen = 1 + 4 + 16;
dsmp->dsm_cid = client_id = malloc(dsmp->dsm_cidlen);
if (dsmp->dsm_cid == NULL)
goto alloc_failure;
(void) memcpy(client_id + 5, pif->pif_hwaddr + 4,
pif->pif_hwlen - 4);
(void) memset(client_id, 0, 5);
}
return (DHCP_IPC_SUCCESS);
}
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);
}
uint_t
smach_count(void)
{
return (global_smach_count);
}
void
discard_default_routes(dhcp_smach_t *dsmp)
{
free(dsmp->dsm_routers);
dsmp->dsm_routers = NULL;
dsmp->dsm_nrouters = 0;
}
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);
}
}
void
reset_smach(dhcp_smach_t *dsmp)
{
dsmp->dsm_dflags &= ~DHCP_IF_FAILED;
remove_default_routes(dsmp);
clear_lif_mtu(dsmp->dsm_lif);
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;
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();
assert(dsmp->dsm_script_pid == -1);
}
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);
}
}
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;
}
}
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 (dsmp->dsm_droprelease)
continue;
dsmp->dsm_droprelease = B_TRUE;
cancel_smach_timers(dsmp);
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;
}
}
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);
}
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);
}
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);
}
}
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);
}
}
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");
}
}
void
cancel_lease_timers(dhcp_lease_t *dlp)
{
cancel_lease_timer(dlp, &dlp->dl_t1);
cancel_lease_timer(dlp, &dlp->dl_t2);
}
boolean_t
schedule_lease_timer(dhcp_lease_t *dlp, dhcp_timer_t *dt,
iu_tq_callback_t *expire)
{
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);
}
}
void
deprecate_leases(dhcp_smach_t *dsmp)
{
dhcp_lease_t *dlp;
remove_default_routes(dsmp);
while ((dlp = dsmp->dsm_leases) != NULL)
remove_lease(dlp);
}
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 (!verify_lif(dsmp->dsm_lif))
goto smach_terminate;
}
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)) {
lif->lif_plumbed = B_FALSE;
remove_lif(lif);
}
}
if (dlp->dl_nlifs == 0)
remove_lease(dlp);
}
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);
}