#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#include <netinet/dhcp.h>
#include <netinet/udp.h>
#include <netinet/ip_var.h>
#include <netinet/udp_var.h>
#include <libinetutil.h>
#include <dhcpmsg.h>
#include <dhcp_hostconf.h>
#include <string.h>
#include "packet.h"
#include "agent.h"
#include "script_handler.h"
#include "interface.h"
#include "states.h"
#include "util.h"
#define RETRY_DELAY 10
#define TOO_CLOSE 2
static boolean_t stop_extending(dhcp_smach_t *, unsigned int);
void
dhcp_renew(iu_tq_t *tqp, void *arg)
{
dhcp_lease_t *dlp = arg;
dhcp_smach_t *dsmp = dlp->dl_smach;
uint32_t t2;
dhcpmsg(MSG_VERBOSE, "dhcp_renew: T1 timer expired on %s",
dsmp->dsm_name);
dlp->dl_t1.dt_id = -1;
if (dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) {
dhcpmsg(MSG_DEBUG, "dhcp_renew: already renewing");
release_lease(dlp);
return;
}
t2 = dsmp->dsm_curstart_monosec + dlp->dl_t2.dt_start;
if (monosec() + TOO_CLOSE >= t2) {
dhcpmsg(MSG_DEBUG, "dhcp_renew: %spast T2 on %s",
monosec() > t2 ? "" : "almost ", dsmp->dsm_name);
release_lease(dlp);
return;
}
if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) ||
!dhcp_extending(dsmp)) {
if (monosec() + RETRY_DELAY < t2) {
init_timer(&dlp->dl_t1, RETRY_DELAY);
(void) set_smach_state(dsmp, BOUND);
if (!schedule_lease_timer(dlp, &dlp->dl_t1,
dhcp_renew)) {
dhcpmsg(MSG_INFO, "dhcp_renew: unable to "
"reschedule renewal around user command "
"on %s; will wait for rebind",
dsmp->dsm_name);
}
} else {
dhcpmsg(MSG_DEBUG, "dhcp_renew: user busy on %s; will "
"wait for rebind", dsmp->dsm_name);
}
}
release_lease(dlp);
}
void
dhcp_rebind(iu_tq_t *tqp, void *arg)
{
dhcp_lease_t *dlp = arg;
dhcp_smach_t *dsmp = dlp->dl_smach;
int nlifs;
dhcp_lif_t *lif;
boolean_t some_valid;
uint32_t expiremax;
DHCPSTATE oldstate;
dhcpmsg(MSG_VERBOSE, "dhcp_rebind: T2 timer expired on %s",
dsmp->dsm_name);
dlp->dl_t2.dt_id = -1;
if ((oldstate = dsmp->dsm_state) == REBINDING) {
dhcpmsg(MSG_DEBUG, "dhcp_renew: already rebinding");
release_lease(dlp);
return;
}
some_valid = B_FALSE;
expiremax = monosec();
lif = dlp->dl_lifs;
for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lif->lif_next) {
uint32_t expire;
expire = dsmp->dsm_curstart_monosec + lif->lif_expire.dt_start;
if (expire > expiremax) {
expiremax = expire;
some_valid = B_TRUE;
}
}
if (!some_valid) {
dhcpmsg(MSG_DEBUG, "dhcp_rebind: all leases expired on %s",
dsmp->dsm_name);
release_lease(dlp);
return;
}
if (dsmp->dsm_isv6) {
dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers;
} else {
IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST),
&dsmp->dsm_server);
}
(void) set_smach_state(dsmp, REBINDING);
if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) ||
!dhcp_extending(dsmp)) {
if (monosec() + RETRY_DELAY < expiremax) {
init_timer(&dlp->dl_t2, RETRY_DELAY);
(void) set_smach_state(dsmp, oldstate);
if (!schedule_lease_timer(dlp, &dlp->dl_t2,
dhcp_rebind)) {
dhcpmsg(MSG_INFO, "dhcp_rebind: unable to "
"reschedule rebind around user command on "
"%s; lease may expire", dsmp->dsm_name);
}
} else {
dhcpmsg(MSG_WARNING, "dhcp_rebind: user busy on %s; "
"will expire", dsmp->dsm_name);
}
}
release_lease(dlp);
}
static int
dhcp_finish_expire(dhcp_smach_t *dsmp, void *arg)
{
dhcp_lif_t *lif = arg;
dhcp_lease_t *dlp;
dhcpmsg(MSG_DEBUG, "lease expired on %s; removing", lif->lif_name);
dlp = lif->lif_lease;
unplumb_lif(lif);
if (dlp->dl_nlifs == 0)
remove_lease(dlp);
release_lif(lif);
if (dsmp->dsm_leases != NULL) {
dhcpmsg(MSG_DEBUG,
"dhcp_finish_expire: some leases remain on %s",
dsmp->dsm_name);
return (1);
}
(void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
dhcpmsg(MSG_INFO, "last lease expired on %s -- restarting DHCP",
dsmp->dsm_name);
if (dsmp->dsm_state == BOUND) {
dsmp->dsm_bad_offers = 0;
dsmp->dsm_sent = 0;
dsmp->dsm_received = 0;
}
deprecate_leases(dsmp);
dhcp_selecting(dsmp);
return (1);
}
void
dhcp_deprecate(iu_tq_t *tqp, void *arg)
{
dhcp_lif_t *lif = arg;
set_lif_deprecated(lif);
release_lif(lif);
}
void
dhcp_expire(iu_tq_t *tqp, void *arg)
{
dhcp_lif_t *lif = arg;
dhcp_smach_t *dsmp;
const char *event;
dhcpmsg(MSG_VERBOSE, "dhcp_expire: lease timer expired on %s",
lif->lif_name);
lif->lif_expire.dt_id = -1;
if (lif->lif_lease == NULL) {
release_lif(lif);
return;
}
set_lif_deprecated(lif);
dsmp = lif->lif_lease->dl_smach;
if (!async_cancel(dsmp)) {
dhcpmsg(MSG_WARNING,
"dhcp_expire: cannot cancel current asynchronous command "
"on %s", dsmp->dsm_name);
init_timer(&lif->lif_expire, DHCP_EXPIRE_WAIT);
if (schedule_lif_timer(lif, &lif->lif_expire, dhcp_expire))
return;
dhcpmsg(MSG_CRIT, "dhcp_expire: cannot reschedule dhcp_expire "
"to get called back, proceeding...");
}
if (!async_start(dsmp, DHCP_START, B_FALSE))
dhcpmsg(MSG_WARNING, "dhcp_expire: cannot start asynchronous "
"transaction on %s, continuing...", dsmp->dsm_name);
if (expired_lif_state(dsmp) == DHCP_EXP_SOMEEXP)
event = EVENT_LOSS6;
else if (dsmp->dsm_isv6)
event = EVENT_EXPIRE6;
else
event = EVENT_EXPIRE;
(void) script_start(dsmp, event, dhcp_finish_expire, lif, NULL);
}
boolean_t
dhcp_extending(dhcp_smach_t *dsmp)
{
dhcp_pkt_t *dpkt;
stop_pkt_retransmission(dsmp);
if (dsmp->dsm_state == BOUND) {
dsmp->dsm_neg_hrtime = gethrtime();
dsmp->dsm_bad_offers = 0;
dsmp->dsm_sent = 0;
dsmp->dsm_received = 0;
(void) set_smach_state(dsmp, RENEWING);
}
dhcpmsg(MSG_DEBUG, "dhcp_extending: sending request on %s",
dsmp->dsm_name);
if (dsmp->dsm_isv6) {
dhcp_lease_t *dlp;
dhcp_lif_t *lif;
uint_t nlifs;
uint_t irt, mrt;
if (dsmp->dsm_state == RENEWING) {
dpkt = init_pkt(dsmp, DHCPV6_MSG_RENEW);
(void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID,
dsmp->dsm_serverid, dsmp->dsm_serveridlen);
irt = DHCPV6_REN_TIMEOUT;
mrt = DHCPV6_REN_MAX_RT;
} else {
dpkt = init_pkt(dsmp, DHCPV6_MSG_REBIND);
irt = DHCPV6_REB_TIMEOUT;
mrt = DHCPV6_REB_MAX_RT;
}
for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
lif = dlp->dl_lifs;
for (nlifs = dlp->dl_nlifs; nlifs > 0;
nlifs--, lif = lif->lif_next) {
(void) add_pkt_lif(dpkt, lif,
DHCPV6_STAT_SUCCESS, NULL);
}
}
(void) add_pkt_prl(dpkt, dsmp);
return (send_pkt_v6(dsmp, dpkt, dsmp->dsm_server,
stop_extending, irt, mrt));
} else {
dhcp_lif_t *lif = dsmp->dsm_lif;
ipaddr_t server;
dpkt = init_pkt(dsmp, REQUEST);
dpkt->pkt->ciaddr.s_addr = lif->lif_addr;
(void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE,
htons(lif->lif_pif->pif_mtu - sizeof (struct udpiphdr)));
(void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
if (class_id_len != 0) {
(void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id,
class_id_len);
}
(void) add_pkt_prl(dpkt, dsmp);
if (!dhcp_add_fqdn_opt(dpkt, dsmp) &&
dsmp->dsm_reqhost != NULL) {
(void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost,
strlen(dsmp->dsm_reqhost));
}
(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, server);
return (send_pkt(dsmp, dpkt, server, stop_extending));
}
}
static boolean_t
stop_extending(dhcp_smach_t *dsmp, unsigned int n_requests)
{
dhcp_lease_t *dlp;
if (dsmp->dsm_state == RENEWING) {
monosec_t t2;
t2 = 0;
for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
if (dlp->dl_t2.dt_start > t2)
t2 = dlp->dl_t2.dt_start;
}
t2 += dsmp->dsm_curstart_monosec;
if (monosec() + TOO_CLOSE >= t2) {
dhcpmsg(MSG_DEBUG, "stop_extending: %spast T2 on %s",
monosec() > t2 ? "" : "almost ", dsmp->dsm_name);
return (B_TRUE);
}
}
if (!dsmp->dsm_isv6 &&
dsmp->dsm_send_timeout < DHCP_REBIND_MIN * MILLISEC / 2) {
dhcpmsg(MSG_DEBUG, "stop_extending: next retry would be in "
"%d.%03d; stopping", dsmp->dsm_send_timeout / MILLISEC,
dsmp->dsm_send_timeout % MILLISEC);
return (B_TRUE);
}
return (B_FALSE);
}