#include <sys/types.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <locale.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <dhcp_hostconf.h>
#include <dhcpagent_ipc.h>
#include <dhcpagent_util.h>
#include <dhcpmsg.h>
#include <dhcp_inittab.h>
#include <dhcp_symbol.h>
#include <netinet/dhcp.h>
#include <net/route.h>
#include <sys/sockio.h>
#include <sys/stat.h>
#include <stropts.h>
#include <fcntl.h>
#include <sys/scsi/adapters/iscsi_if.h>
#include "async.h"
#include "agent.h"
#include "script_handler.h"
#include "util.h"
#include "class_id.h"
#include "states.h"
#include "packet.h"
#include "interface.h"
#include "defaults.h"
#ifndef TEXT_DOMAIN
#define TEXT_DOMAIN "SYS_TEST"
#endif
iu_timer_id_t inactivity_id;
int class_id_len = 0;
char *class_id;
iu_eh_t *eh;
iu_tq_t *tq;
pid_t grandparent;
int rtsock_fd;
static boolean_t shutdown_started = B_FALSE;
static boolean_t do_adopt = B_FALSE;
static unsigned int debug_level = 0;
static iu_eh_callback_t accept_event, ipc_event, rtsock_event;
static void dhcp_smach_set_msg_reqhost(dhcp_smach_t *dsmp,
ipc_action_t *iap);
static DHCP_OPT * dhcp_get_ack_or_state(const dhcp_smach_t *dsmp,
const PKT_LIST *plp, uint_t codenum, boolean_t *did_alloc);
static int ipc_cmd_allowed[DHCP_NSTATES][DHCP_NIPC] = {
{ 1, 0, 1, 0, 1, 1, 1, 0 },
{ 1, 0, 1, 0, 1, 1, 0, 0 },
{ 1, 0, 1, 0, 1, 1, 0, 0 },
{ 1, 1, 1, 1, 0, 1, 0, 1 },
{ 1, 1, 1, 1, 0, 1, 0, 1 },
{ 1, 1, 1, 1, 0, 1, 0, 1 },
{ 1, 1, 1, 1, 0, 1, 0, 1 },
{ 1, 0, 1, 0, 1, 1, 1, 1 },
{ 1, 0, 1, 1, 1, 1, 0, 0 },
{ 1, 0, 1, 1, 0, 1, 0, 0 },
{ 1, 0, 1, 0, 1, 1, 1, 0 },
{ 1, 1, 1, 1, 0, 1, 0, 1 },
{ 1, 0, 1, 0, 0, 1, 0, 1 },
};
#define CMD_ISPRIV 0x1
#define CMD_CREATE 0x2
#define CMD_BOOTP 0x4
#define CMD_IMMED 0x8
static uint_t ipc_cmd_flags[DHCP_NIPC] = {
CMD_ISPRIV|CMD_BOOTP,
CMD_ISPRIV,
CMD_BOOTP|CMD_IMMED,
CMD_ISPRIV,
CMD_CREATE|CMD_ISPRIV|CMD_BOOTP,
CMD_BOOTP|CMD_IMMED,
CMD_CREATE|CMD_ISPRIV,
CMD_BOOTP|CMD_IMMED
};
static boolean_t is_iscsi_active(void);
int
main(int argc, char **argv)
{
boolean_t is_daemon = B_TRUE;
boolean_t is_verbose;
int ipc_fd;
int c;
int aware = RTAW_UNDER_IPMP;
struct rlimit rl;
debug_level = df_get_int("", B_FALSE, DF_DEBUG_LEVEL);
is_verbose = df_get_bool("", B_FALSE, DF_VERBOSE);
while ((c = getopt(argc, argv, "vd:l:fa")) != EOF) {
switch (c) {
case 'a':
do_adopt = B_TRUE;
grandparent = getpid();
break;
case 'd':
debug_level = strtoul(optarg, NULL, 0);
break;
case 'f':
is_daemon = B_FALSE;
break;
case 'v':
is_verbose = B_TRUE;
break;
case '?':
(void) fprintf(stderr, "usage: %s [-a] [-d n] [-f] [-v]"
"\n", argv[0]);
return (EXIT_FAILURE);
default:
break;
}
}
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
if (geteuid() != 0) {
dhcpmsg_init(argv[0], B_FALSE, is_verbose, debug_level);
dhcpmsg(MSG_ERROR, "must be super-user");
dhcpmsg_fini();
return (EXIT_FAILURE);
}
if (is_daemon && daemonize() == 0) {
dhcpmsg_init(argv[0], B_FALSE, is_verbose, debug_level);
dhcpmsg(MSG_ERR, "cannot become daemon, exiting");
dhcpmsg_fini();
return (EXIT_FAILURE);
}
srand48(gethrtime() ^ gethostid() ^ getpid());
dhcpmsg_init(argv[0], is_daemon, is_verbose, debug_level);
(void) atexit(dhcpmsg_fini);
tq = iu_tq_create();
eh = iu_eh_create();
if (eh == NULL || tq == NULL) {
errno = ENOMEM;
dhcpmsg(MSG_ERR, "cannot create timer queue or event handler");
return (EXIT_FAILURE);
}
(void) signal(SIGTERM, graceful_shutdown);
(void) signal(SIGQUIT, graceful_shutdown);
(void) signal(SIGPIPE, SIG_IGN);
(void) signal(SIGUSR1, SIG_IGN);
(void) signal(SIGUSR2, SIG_IGN);
(void) signal(SIGINT, SIG_IGN);
(void) signal(SIGHUP, SIG_IGN);
(void) signal(SIGCHLD, SIG_IGN);
(void) iu_eh_register_signal(eh, SIGTHAW, refresh_smachs, NULL);
class_id = get_class_id();
if (class_id != NULL)
class_id_len = strlen(class_id);
else
dhcpmsg(MSG_WARNING, "get_class_id failed, continuing "
"with no vendor class id");
inactivity_id = iu_schedule_timer(tq, DHCP_INACTIVITY_WAIT,
inactivity_shutdown, NULL);
rl.rlim_cur = RLIM_INFINITY;
rl.rlim_max = RLIM_INFINITY;
if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
dhcpmsg(MSG_ERR, "setrlimit failed");
(void) enable_extended_FILE_stdio(-1, -1);
if (!dhcp_ip_default())
return (EXIT_FAILURE);
switch (dhcp_ipc_init(&ipc_fd)) {
case 0:
break;
case DHCP_IPC_E_BIND:
dhcpmsg(MSG_ERROR, "dhcp_ipc_init: cannot bind to port "
"%i (agent already running?)", IPPORT_DHCPAGENT);
return (EXIT_FAILURE);
default:
dhcpmsg(MSG_ERROR, "dhcp_ipc_init failed");
return (EXIT_FAILURE);
}
if (iu_register_event(eh, ipc_fd, POLLIN, accept_event, 0) == -1) {
dhcpmsg(MSG_ERR, "cannot register ipc fd for messages");
return (EXIT_FAILURE);
}
rtsock_fd = socket(PF_ROUTE, SOCK_RAW, 0);
if (rtsock_fd == -1) {
dhcpmsg(MSG_ERR, "cannot open routing socket");
return (EXIT_FAILURE);
}
if (setsockopt(rtsock_fd, SOL_ROUTE, RT_AWARE, &aware,
sizeof (aware)) == -1) {
dhcpmsg(MSG_ERR, "cannot set RT_AWARE on routing socket");
return (EXIT_FAILURE);
}
if (iu_register_event(eh, rtsock_fd, POLLIN, rtsock_event, 0) == -1) {
dhcpmsg(MSG_ERR, "cannot register routing socket for messages");
return (EXIT_FAILURE);
}
if (do_adopt && !dhcp_adopt())
return (EXIT_FAILURE);
remove_v6_strays();
switch (iu_handle_events(eh, tq)) {
case -1:
dhcpmsg(MSG_WARNING, "iu_handle_events exited abnormally");
break;
case DHCP_REASON_INACTIVITY:
dhcpmsg(MSG_INFO, "no interfaces to manage, shutting down...");
break;
case DHCP_REASON_TERMINATE:
dhcpmsg(MSG_INFO, "received SIGTERM, shutting down...");
break;
case DHCP_REASON_SIGNAL:
dhcpmsg(MSG_WARNING, "received unexpected signal, shutting "
"down...");
break;
}
(void) iu_eh_unregister_signal(eh, SIGTHAW, NULL);
iu_eh_destroy(eh);
iu_tq_destroy(tq);
return (EXIT_SUCCESS);
}
boolean_t
drain_script(iu_eh_t *ehp, void *arg)
{
if (shutdown_started == B_FALSE) {
shutdown_started = B_TRUE;
if (!do_adopt && !is_iscsi_active()) {
nuke_smach_list();
}
}
return (script_count == 0);
}
static void
accept_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg)
{
int client_fd;
int is_priv;
if (dhcp_ipc_accept(fd, &client_fd, &is_priv) != 0) {
dhcpmsg(MSG_ERR, "accept_event: accept on ipc socket");
return;
}
if (iu_register_event(eh, client_fd, POLLIN, ipc_event,
(void *)is_priv) == -1) {
dhcpmsg(MSG_ERROR, "accept_event: cannot register ipc socket "
"for callback");
}
}
static void
ipc_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg)
{
ipc_action_t ia, *iap;
dhcp_smach_t *dsmp;
int error, is_priv = (int)arg;
const char *ifname;
boolean_t isv6;
boolean_t dsm_created = B_FALSE;
ipc_action_init(&ia);
error = dhcp_ipc_recv_request(fd, &ia.ia_request,
DHCP_IPC_REQUEST_WAIT);
if (error != DHCP_IPC_SUCCESS) {
if (error != DHCP_IPC_E_EOF) {
dhcpmsg(MSG_ERROR,
"ipc_event: dhcp_ipc_recv_request failed: %s",
dhcp_ipc_strerror(error));
} else {
dhcpmsg(MSG_DEBUG, "ipc_event: connection closed");
}
if ((dsmp = lookup_smach_by_event(id)) != NULL) {
ipc_action_finish(dsmp, error);
} else {
(void) iu_unregister_event(eh, id, NULL);
(void) dhcp_ipc_close(fd);
}
return;
}
ia.ia_cmd = DHCP_IPC_CMD(ia.ia_request->message_type);
ia.ia_fd = fd;
ia.ia_eid = id;
if (ia.ia_cmd >= DHCP_NIPC) {
dhcpmsg(MSG_ERROR,
"ipc_event: invalid command (%s) attempted on %s",
dhcp_ipc_type_to_string(ia.ia_cmd), ia.ia_request->ifname);
send_error_reply(&ia, DHCP_IPC_E_CMD_UNKNOWN);
return;
}
if (!is_priv && (ipc_cmd_flags[ia.ia_cmd] & CMD_ISPRIV)) {
dhcpmsg(MSG_WARNING,
"ipc_event: privileged ipc command (%s) attempted on %s",
dhcp_ipc_type_to_string(ia.ia_cmd), ia.ia_request->ifname);
send_error_reply(&ia, DHCP_IPC_E_PERM);
return;
}
isv6 = (ia.ia_request->message_type & DHCP_V6) != 0;
ifname = ia.ia_request->ifname;
if (*ifname == '\0')
dsmp = primary_smach(isv6);
else
dsmp = lookup_smach(ifname, isv6);
if (dsmp != NULL) {
hold_smach(dsmp);
if (!verify_smach(dsmp))
dsmp = NULL;
}
if (dsmp == NULL) {
if (ifname[0] == '\0') {
if (ia.ia_cmd == DHCP_GET_TAG)
dsmp = info_primary_smach(isv6);
if (dsmp == NULL)
error = DHCP_IPC_E_NOPRIMARY;
} else if (ipc_cmd_flags[ia.ia_cmd] & CMD_CREATE) {
dhcp_lif_t *lif;
lif = attach_lif(ifname, isv6, &error);
if (lif != NULL &&
(dsmp = insert_smach(lif, &error)) != NULL) {
error = get_smach_cid(dsmp);
if (error != DHCP_IPC_SUCCESS) {
remove_smach(dsmp);
dsmp = NULL;
}
dsm_created = (dsmp != NULL);
}
} else {
error = DHCP_IPC_E_UNKIF;
}
if (dsmp == NULL) {
send_error_reply(&ia, error);
return;
}
}
if (ia.ia_cmd == DHCP_START &&
(error = set_lif_dhcp(dsmp->dsm_lif)) != DHCP_IPC_SUCCESS) {
if (dsm_created)
remove_smach(dsmp);
send_error_reply(&ia, error);
return;
}
if ((dsmp->dsm_dflags & DHCP_IF_BOOTP) &&
!(ipc_cmd_flags[ia.ia_cmd] & CMD_BOOTP)) {
dhcpmsg(MSG_ERROR, "command %s not valid for BOOTP on %s",
dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name);
send_error_reply(&ia, DHCP_IPC_E_BOOTP);
return;
}
if (!check_cmd_allowed(dsmp->dsm_state, ia.ia_cmd)) {
dhcpmsg(MSG_DEBUG,
"in state %s; not allowing %s command on %s",
dhcp_state_to_string(dsmp->dsm_state),
dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name);
send_error_reply(&ia,
ia.ia_cmd == DHCP_START && dsmp->dsm_state != INIT ?
DHCP_IPC_E_RUNNING : DHCP_IPC_E_OUTSTATE);
return;
}
dhcpmsg(MSG_DEBUG, "in state %s; allowing %s command on %s",
dhcp_state_to_string(dsmp->dsm_state),
dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name);
if ((ia.ia_request->message_type & DHCP_PRIMARY) && is_priv)
make_primary(dsmp);
if (ipc_cmd_flags[ia.ia_cmd] & CMD_IMMED) {
iap = &ia;
} else {
if (shutdown_started) {
send_error_reply(&ia, DHCP_IPC_E_OUTSTATE);
return;
}
if (dsmp->dsm_dflags & DHCP_IF_BUSY) {
send_error_reply(&ia, DHCP_IPC_E_PEND);
return;
}
if (!ipc_action_start(dsmp, &ia)) {
dhcpmsg(MSG_WARNING, "ipc_event: ipc_action_start "
"failed for %s", dsmp->dsm_name);
send_error_reply(&ia, DHCP_IPC_E_MEMORY);
return;
}
iap = &dsmp->dsm_ia;
}
switch (iap->ia_cmd) {
case DHCP_DROP:
if (dsmp->dsm_droprelease)
break;
dsmp->dsm_droprelease = B_TRUE;
cancel_smach_timers(dsmp);
(void) script_start(dsmp, isv6 ? EVENT_DROP6 : EVENT_DROP,
dhcp_drop, NULL, NULL);
break;
case DHCP_EXTEND:
dhcp_smach_set_msg_reqhost(dsmp, iap);
(void) dhcp_extending(dsmp);
break;
case DHCP_GET_TAG: {
dhcp_optnum_t optnum;
void *opt = NULL;
uint_t optlen;
boolean_t did_alloc = B_FALSE;
PKT_LIST *ack = dsmp->dsm_ack;
int i;
if (iap->ia_request->data_type != DHCP_TYPE_OPTNUM ||
iap->ia_request->data_length != sizeof (dhcp_optnum_t)) {
send_error_reply(iap, DHCP_IPC_E_PROTO);
break;
}
(void) memcpy(&optnum, iap->ia_request->buffer,
sizeof (dhcp_optnum_t));
load_option:
switch (optnum.category) {
case DSYM_SITE:
case DSYM_STANDARD:
for (i = 0; i < dsmp->dsm_pillen; i++) {
if (dsmp->dsm_pil[i] == optnum.code)
break;
}
if (i < dsmp->dsm_pillen)
break;
if (isv6) {
opt = dhcpv6_pkt_option(ack, NULL, optnum.code,
NULL);
} else {
opt = dhcp_get_ack_or_state(dsmp, ack,
optnum.code, &did_alloc);
}
break;
case DSYM_VENDOR:
if (isv6) {
dhcpv6_option_t *d6o;
uint32_t ent;
d6o = NULL;
for (;;) {
d6o = dhcpv6_pkt_option(ack, d6o,
DHCPV6_OPT_VENDOR_OPT, &optlen);
if (d6o == NULL)
break;
optlen -= sizeof (*d6o);
if (optlen < sizeof (ent))
continue;
(void) memcpy(&ent, d6o + 1,
sizeof (ent));
if (ntohl(ent) != DHCPV6_SUN_ENT)
continue;
break;
}
if (d6o != NULL) {
opt = dhcpv6_find_option(
(char *)(d6o + 1) + sizeof (ent),
optlen - sizeof (ent), NULL,
optnum.code, NULL);
}
} else {
if ((optnum.code > VS_OPTION_START ||
optnum.code == VS_OPTION_START) &&
optnum.code <= VS_OPTION_END)
opt = ack->vs[optnum.code];
}
break;
case DSYM_FIELD:
if (isv6) {
dhcpv6_message_t *d6m =
(dhcpv6_message_t *)ack->pkt;
dhcpv6_option_t *d6o;
optlen = optnum.code + optnum.size;
if (d6m->d6m_msg_type ==
DHCPV6_MSG_RELAY_FORW ||
d6m->d6m_msg_type ==
DHCPV6_MSG_RELAY_REPL) {
if (optlen > sizeof (dhcpv6_relay_t))
break;
} else {
if (optlen > sizeof (*d6m))
break;
}
opt = malloc(sizeof (*d6o) + optnum.size);
if (opt != NULL) {
d6o = opt;
d6o->d6o_code = htons(optnum.code);
d6o->d6o_len = htons(optnum.size);
(void) memcpy(d6o + 1, (caddr_t)d6m +
optnum.code, optnum.size);
}
} else {
if (optnum.code + optnum.size > sizeof (PKT))
break;
opt = malloc(optnum.size + DHCP_OPT_META_LEN);
if (opt != NULL) {
DHCP_OPT *v4opt = opt;
v4opt->len = optnum.size;
v4opt->code = optnum.code;
(void) memcpy(v4opt->value,
(caddr_t)ack->pkt + optnum.code,
optnum.size);
}
}
if (opt == NULL) {
send_error_reply(iap, DHCP_IPC_E_MEMORY);
return;
}
did_alloc = B_TRUE;
break;
default:
send_error_reply(iap, DHCP_IPC_E_PROTO);
return;
}
if (opt != NULL) {
if (isv6) {
dhcpv6_option_t d6ov;
(void) memcpy(&d6ov, opt, sizeof (d6ov));
optlen = ntohs(d6ov.d6o_len) + sizeof (d6ov);
} else {
optlen = ((DHCP_OPT *)opt)->len +
DHCP_OPT_META_LEN;
}
send_data_reply(iap, 0, DHCP_TYPE_OPTION, opt, optlen);
if (did_alloc)
free(opt);
break;
} else if (ack != dsmp->dsm_orig_ack) {
ack = dsmp->dsm_orig_ack;
goto load_option;
}
send_ok_reply(iap);
break;
}
case DHCP_INFORM:
dhcp_inform(dsmp);
break;
case DHCP_PING:
if (dsmp->dsm_dflags & DHCP_IF_FAILED)
send_error_reply(iap, DHCP_IPC_E_FAILEDIF);
else
send_ok_reply(iap);
break;
case DHCP_RELEASE:
if (dsmp->dsm_droprelease)
break;
dsmp->dsm_droprelease = B_TRUE;
cancel_smach_timers(dsmp);
(void) script_start(dsmp, isv6 ? EVENT_RELEASE6 :
EVENT_RELEASE, dhcp_release, "Finished with lease.", NULL);
break;
case DHCP_START: {
PKT_LIST *ack, *oack;
PKT_LIST *plp[2];
deprecate_leases(dsmp);
dhcp_smach_set_msg_reqhost(dsmp, iap);
error = read_hostconf(dsmp->dsm_name, plp, 2, dsmp->dsm_isv6);
ack = error > 0 ? plp[0] : NULL;
oack = error > 1 ? plp[1] : NULL;
if (oack == NULL)
oack = ack;
if (ack != NULL) {
dsmp->dsm_orig_ack = oack;
dsmp->dsm_ack = ack;
dhcp_init_reboot(dsmp);
break;
}
if (debug_level != 0 || !set_start_timer(dsmp)) {
dhcp_selecting(dsmp);
}
}
break;
case DHCP_STATUS: {
dhcp_status_t status;
dhcp_lease_t *dlp;
status.if_began = monosec_to_time(dsmp->dsm_curstart_monosec);
dlp = dsmp->dsm_leases;
if (dlp == NULL ||
dlp->dl_lifs->lif_expire.dt_start == DHCP_PERM) {
status.if_t1 = DHCP_PERM;
status.if_t2 = DHCP_PERM;
status.if_lease = DHCP_PERM;
} else {
status.if_t1 = status.if_began +
dlp->dl_t1.dt_start;
status.if_t2 = status.if_began +
dlp->dl_t2.dt_start;
status.if_lease = status.if_began +
dlp->dl_lifs->lif_expire.dt_start;
}
status.version = DHCP_STATUS_VER;
status.if_state = dsmp->dsm_state;
status.if_dflags = dsmp->dsm_dflags;
status.if_sent = dsmp->dsm_sent;
status.if_recv = dsmp->dsm_received;
status.if_bad_offers = dsmp->dsm_bad_offers;
(void) strlcpy(status.if_name, dsmp->dsm_name, LIFNAMSIZ);
send_data_reply(iap, 0, DHCP_TYPE_STATUS, &status,
sizeof (dhcp_status_t));
break;
}
}
}
static void
dhcp_smach_set_msg_reqhost(dhcp_smach_t *dsmp, ipc_action_t *iap)
{
DHCP_OPT *d4o;
dhcp_symbol_t *entry;
char *value;
if (dsmp->dsm_msg_reqhost != NULL) {
dhcpmsg(MSG_DEBUG,
"dhcp_smach_set_msg_reqhost: nullify former value, %s",
dsmp->dsm_msg_reqhost);
free(dsmp->dsm_msg_reqhost);
dsmp->dsm_msg_reqhost = NULL;
}
if (dsmp->dsm_isv6) {
dhcpmsg(MSG_DEBUG, "dhcp_smach_set_msg_reqhost: ipv6 is not"
" handled");
return;
} else if (iap->ia_request->data_type != DHCP_TYPE_OPTION) {
dhcpmsg(MSG_DEBUG, "dhcp_smach_set_msg_reqhost: request type"
" %d is not DHCP_TYPE_OPTION", iap->ia_request->data_type);
return;
}
if (iap->ia_request->data_length <= DHCP_OPT_META_LEN) {
dhcpmsg(MSG_WARNING, "dhcp_smach_set_msg_reqhost:"
" DHCP_TYPE_OPTION ia_request buffer is short");
return;
}
d4o = (DHCP_OPT *)iap->ia_request->buffer;
if (d4o->code != CD_HOSTNAME) {
dhcpmsg(MSG_DEBUG,
"dhcp_smach_set_msg_reqhost: ignoring DHCPv4"
" option %u", d4o->code);
return;
} else if (iap->ia_request->data_length - DHCP_OPT_META_LEN
!= d4o->len) {
dhcpmsg(MSG_WARNING, "dhcp_smach_set_msg_reqhost:"
" unexpected DHCP_OPT buffer length %u for CD_HOSTNAME"
" option length %u", iap->ia_request->data_length,
d4o->len);
return;
}
entry = inittab_getbycode(ITAB_CAT_STANDARD, ITAB_CONS_INFO,
CD_HOSTNAME);
if (entry == NULL) {
dhcpmsg(MSG_WARNING,
"dhcp_smach_set_msg_reqhost: error getting"
" ITAB_CAT_STANDARD ITAB_CONS_INFO"
" CD_HOSTNAME entry");
return;
}
value = inittab_decode(entry, d4o->value, d4o->len,
B_TRUE);
if (value == NULL) {
dhcpmsg(MSG_WARNING,
"dhcp_smach_set_msg_reqhost: error decoding"
" CD_HOSTNAME value from DHCP_OPT");
} else {
dhcpmsg(MSG_DEBUG,
"dhcp_smach_set_msg_reqhost: host %s", value);
free(dsmp->dsm_msg_reqhost);
dsmp->dsm_msg_reqhost = value;
}
free(entry);
}
static DHCP_OPT *
dhcp_get_ack_or_state(const dhcp_smach_t *dsmp, const PKT_LIST *plp,
uint_t codenum, boolean_t *did_alloc)
{
DHCP_OPT *opt;
*did_alloc = B_FALSE;
if (codenum > DHCP_LAST_OPT)
return (NULL);
opt = plp->opts[codenum];
if (opt != NULL)
return (opt);
switch (codenum) {
case CD_CLIENT_ID:
if (dsmp->dsm_cidlen > 0) {
if ((opt = malloc(dsmp->dsm_cidlen + DHCP_OPT_META_LEN))
!= NULL) {
*did_alloc = B_TRUE;
(void) encode_dhcp_opt(opt,
B_FALSE , CD_CLIENT_ID,
dsmp->dsm_cid, dsmp->dsm_cidlen);
}
}
break;
default:
break;
}
return (opt);
}
static boolean_t
check_rtm_addr(const struct ifa_msghdr *ifam, int msglen, boolean_t isv6,
const in6_addr_t *addr)
{
const char *cp, *lim;
uint_t flag;
const struct sockaddr *sa;
if (!(ifam->ifam_addrs & RTA_IFA))
return (B_FALSE);
cp = (const char *)(ifam + 1);
lim = (const char *)ifam + msglen;
for (flag = 1; flag < RTA_IFA; flag <<= 1) {
if (ifam->ifam_addrs & flag) {
sa = (const struct sockaddr *)cp;
if ((const char *)(sa + 1) > lim)
return (B_FALSE);
switch (sa->sa_family) {
case AF_INET:
cp += sizeof (struct sockaddr_in);
break;
case AF_LINK:
cp += sizeof (struct sockaddr_dl);
break;
case AF_INET6:
cp += sizeof (struct sockaddr_in6);
break;
default:
cp += sizeof (struct sockaddr);
break;
}
}
}
if (isv6) {
const struct sockaddr_in6 *sin6;
sin6 = (const struct sockaddr_in6 *)cp;
if ((const char *)(sin6 + 1) > lim)
return (B_FALSE);
if (sin6->sin6_family != AF_INET6)
return (B_FALSE);
return (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, addr));
} else {
const struct sockaddr_in *sinp;
ipaddr_t v4addr;
sinp = (const struct sockaddr_in *)cp;
if ((const char *)(sinp + 1) > lim)
return (B_FALSE);
if (sinp->sin_family != AF_INET)
return (B_FALSE);
IN6_V4MAPPED_TO_IPADDR(addr, v4addr);
return (sinp->sin_addr.s_addr == v4addr);
}
}
static boolean_t
is_rtm_v6(const struct ifa_msghdr *ifam, int msglen)
{
const char *cp, *lim;
uint_t flag;
const struct sockaddr *sa;
cp = (const char *)(ifam + 1);
lim = (const char *)ifam + msglen;
for (flag = ifam->ifam_addrs; flag != 0; flag &= flag - 1) {
sa = (const struct sockaddr *)cp;
if ((const char *)(sa + 1) > lim)
return (B_FALSE);
switch (sa->sa_family) {
case AF_INET:
return (B_FALSE);
case AF_LINK:
cp += sizeof (struct sockaddr_dl);
break;
case AF_INET6:
return (B_TRUE);
default:
cp += sizeof (struct sockaddr);
break;
}
}
return (B_FALSE);
}
static boolean_t
check_lif(dhcp_lif_t *lif, const struct ifa_msghdr *ifam, int msglen)
{
boolean_t isv6, dad_wait, unplumb;
int fd;
struct lifreq lifr;
isv6 = lif->lif_pif->pif_isv6;
fd = isv6 ? v6_sock_fd : v4_sock_fd;
unplumb = B_FALSE;
(void) memset(&lifr, 0, sizeof (lifr));
(void) strlcpy(lifr.lifr_name, lif->lif_name, sizeof (lifr.lifr_name));
if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) {
lifr.lifr_flags = 0;
if (errno == ENXIO) {
lif->lif_plumbed = B_FALSE;
dhcpmsg(MSG_INFO, "%s has been removed; abandoning",
lif->lif_name);
if (!isv6)
discard_default_routes(lif->lif_smachs);
} else {
dhcpmsg(MSG_ERR,
"unable to retrieve interface flags on %s",
lif->lif_name);
}
unplumb = B_TRUE;
} else if (!check_rtm_addr(ifam, msglen, isv6, &lif->lif_v6addr)) {
return (B_FALSE);
} else if (lifr.lifr_flags & IFF_DUPLICATE) {
dhcpmsg(MSG_ERROR, "interface %s has duplicate address",
lif->lif_name);
lif_mark_decline(lif, "duplicate address");
close_ip_lif(lif);
(void) open_ip_lif(lif, INADDR_ANY, B_TRUE);
}
dad_wait = lif->lif_dad_wait;
if (dad_wait) {
dhcpmsg(MSG_VERBOSE, "check_lif: %s has finished DAD",
lif->lif_name);
lif->lif_dad_wait = B_FALSE;
}
if (unplumb)
unplumb_lif(lif);
return (dad_wait);
}
static boolean_t
check_main_lif(dhcp_smach_t *dsmp, const struct ifa_msghdr *ifam, int msglen)
{
dhcp_lif_t *lif = dsmp->dsm_lif;
struct lifreq lifr;
(void) memset(&lifr, 0, sizeof (lifr));
(void) strlcpy(lifr.lifr_name, lif->lif_name, sizeof (lifr.lifr_name));
if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) {
if (errno == ENXIO) {
dhcpmsg(MSG_INFO, "%s has been removed; abandoning",
lif->lif_name);
} else {
dhcpmsg(MSG_ERR,
"unable to retrieve interface flags on %s",
lif->lif_name);
}
return (B_FALSE);
} else if (!check_rtm_addr(ifam, msglen, B_TRUE, &lif->lif_v6addr)) {
return (B_TRUE);
} else if (lifr.lifr_flags & IFF_DUPLICATE) {
dhcpmsg(MSG_ERROR, "interface %s has duplicate address",
lif->lif_name);
return (B_FALSE);
} else {
return (B_TRUE);
}
}
static void
process_link_up_down(dhcp_pif_t *pif, const struct if_msghdr *ifm)
{
struct lifreq lifr;
boolean_t isv6;
int fd;
if ((ifm->ifm_flags & IFF_RUNNING) && pif->pif_running ||
!(ifm->ifm_flags & IFF_RUNNING) && !pif->pif_running)
return;
isv6 = pif->pif_isv6;
fd = isv6 ? v6_sock_fd : v4_sock_fd;
(void) memset(&lifr, 0, sizeof (lifr));
(void) strlcpy(lifr.lifr_name, pif->pif_name, sizeof (lifr.lifr_name));
if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1 ||
!(lifr.lifr_flags & IFF_RUNNING)) {
pif_status(pif, B_FALSE);
} else {
pif_status(pif, B_TRUE);
}
}
static void
rtsock_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg)
{
dhcp_smach_t *dsmp, *dsmnext;
union {
struct ifa_msghdr ifam;
struct if_msghdr ifm;
char buf[1024];
} msg;
uint16_t ifindex;
int msglen;
boolean_t isv6;
if ((msglen = read(fd, &msg, sizeof (msg))) <= 0)
return;
if (msg.ifm.ifm_type == RTM_IFINFO) {
ifindex = msg.ifm.ifm_index;
isv6 = (msg.ifm.ifm_flags & IFF_IPV6) ? B_TRUE : B_FALSE;
} else if (msg.ifam.ifam_type == RTM_DELADDR ||
msg.ifam.ifam_type == RTM_NEWADDR) {
ifindex = msg.ifam.ifam_index;
isv6 = is_rtm_v6(&msg.ifam, msglen);
} else {
return;
}
for (dsmp = lookup_smach_by_uindex(ifindex, NULL, isv6);
dsmp != NULL; dsmp = dsmnext) {
DHCPSTATE oldstate;
boolean_t lif_finished;
boolean_t lease_removed;
dhcp_lease_t *dlp, *dlnext;
dsmnext = lookup_smach_by_uindex(ifindex, dsmp, isv6);
oldstate = dsmp->dsm_state;
if (dsmp->dsm_droprelease)
continue;
if (msg.ifm.ifm_type == RTM_IFINFO) {
process_link_up_down(dsmp->dsm_lif->lif_pif, &msg.ifm);
continue;
}
lif_finished = B_FALSE;
lease_removed = B_FALSE;
for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlnext) {
dhcp_lif_t *lif, *lifnext;
uint_t nlifs = dlp->dl_nlifs;
dlnext = dlp->dl_next;
for (lif = dlp->dl_lifs; lif != NULL && nlifs > 0;
lif = lifnext, nlifs--) {
lifnext = lif->lif_next;
if (check_lif(lif, &msg.ifam, msglen)) {
dsmp->dsm_lif_wait--;
lif_finished = B_TRUE;
}
}
if (dlp->dl_nlifs == 0) {
remove_lease(dlp);
lease_removed = B_TRUE;
}
}
if ((isv6 && !check_main_lif(dsmp, &msg.ifam, msglen)) ||
(!isv6 && !verify_lif(dsmp->dsm_lif))) {
finished_smach(dsmp, DHCP_IPC_E_INVIF);
continue;
}
if (!lif_finished && dsmp->dsm_lif_down == 0 &&
(dsmp->dsm_leases != NULL || !lease_removed))
continue;
if (dsmp->dsm_lif_wait != 0) {
dhcpmsg(MSG_VERBOSE, "rtsock_event: %s still has %d "
"LIFs waiting on DAD", dsmp->dsm_name,
dsmp->dsm_lif_wait);
continue;
}
if (dsmp->dsm_lif_down != 0)
send_declines(dsmp);
if (dsmp->dsm_leases == NULL) {
dsmp->dsm_bad_offers++;
if (!dsmp->dsm_isv6) {
dhcpmsg(MSG_VERBOSE, "rtsock_event: %s has no "
"LIFs left", dsmp->dsm_name);
dhcp_restart(dsmp);
}
} else {
dhcpmsg(MSG_VERBOSE, "rtsock_event: all LIFs verified "
"on %s in %s state", dsmp->dsm_name,
dhcp_state_to_string(oldstate));
if (oldstate == PRE_BOUND ||
oldstate == ADOPTING)
dhcp_bound_complete(dsmp);
if (oldstate == ADOPTING)
dhcp_adopt_complete(dsmp);
}
}
}
boolean_t
check_cmd_allowed(DHCPSTATE state, dhcp_ipc_type_t cmd)
{
return (ipc_cmd_allowed[state][cmd] != 0);
}
static boolean_t
is_iscsi_active(void)
{
int fd;
int active = 0;
if ((fd = open(ISCSI_DRIVER_DEVCTL, O_RDONLY)) != -1) {
if (ioctl(fd, ISCSI_IS_ACTIVE, &active) != 0)
active = 0;
(void) close(fd);
}
return (active != 0);
}