#include <arpa/inet.h>
#include <assert.h>
#include <dhcpagent_ipc.h>
#include <dhcp_inittab.h>
#include <dhcp_symbol.h>
#include <dhcpagent_util.h>
#include <errno.h>
#include <execinfo.h>
#include <libnwam.h>
#include <stdlib.h>
#include <strings.h>
#include <ucontext.h>
#include <unistd.h>
#include <libscf.h>
#include "conditions.h"
#include "events.h"
#include "ncp.h"
#include "ncu.h"
#include "objects.h"
#include "util.h"
#define STATELESS_RUNNING (IFF_RUNNING | IFF_UP | IFF_ADDRCONF)
#define DHCP_RUNNING (IFF_RUNNING | IFF_UP | IFF_DHCPRUNNING)
static void nwamd_dhcp(const char *, ipadm_addrobj_t, dhcp_ipc_type_t);
static void nwamd_down_interface(const char *, ipadm_addr_type_t, const char *);
static boolean_t stateless_running(const nwamd_ncu_t *);
static const char *
nwamd_sockaddr2str(const struct sockaddr *addr, char *str, size_t len)
{
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
const char *straddr;
if (addr == NULL)
return (NULL);
if (addr->sa_family == AF_INET) {
sin = (struct sockaddr_in *)addr;
straddr = inet_ntop(AF_INET, (void *)&sin->sin_addr, str, len);
} else if (addr->sa_family == AF_INET6) {
sin6 = (struct sockaddr_in6 *)addr;
straddr = inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, str,
len);
} else {
errno = EINVAL;
return (NULL);
}
return (straddr != NULL ? str : NULL);
}
void
nwamd_propogate_link_up_down_to_ip(const char *linkname, boolean_t up)
{
nwamd_object_t ip_ncu = nwamd_ncu_object_find(NWAM_NCU_TYPE_INTERFACE,
linkname);
nwamd_ncu_t *ncu;
if (ip_ncu == NULL) {
nlog(LOG_DEBUG, "nwamd_propogate_link_up_down_to_ip: no IP NCU "
"for link %s, cannot propogate %s event", linkname,
up ? "up" : "down");
return;
}
ncu = ip_ncu->nwamd_object_data;
if (ncu->ncu_enabled) {
if (ip_ncu->nwamd_object_aux_state ==
NWAM_AUX_STATE_UNINITIALIZED) {
nlog(LOG_DEBUG,
"nwamd_propogate_link_up_down_to_ip: will not "
"propogate link %s event as IP NCU %s is being "
"removed", up ? "up" : "down", linkname);
} else {
nlog(LOG_DEBUG,
"nwamd_propogate_link_up_down_to_ip: propogating "
"link %s event to interface %s",
up ? "up" : "down", linkname);
nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
ip_ncu->nwamd_object_name,
up ?
NWAM_STATE_OFFLINE_TO_ONLINE :
NWAM_STATE_ONLINE_TO_OFFLINE,
up ? NWAM_AUX_STATE_INITIALIZED :
NWAM_AUX_STATE_CONDITIONS_NOT_MET);
}
} else {
nlog(LOG_DEBUG,
"nwamd_propogate_link_up_down_to_ip: not propogating "
"link %s event to interface %s, IP NCU is disabled",
up ? "up" : "down", linkname);
}
nwamd_object_release(ip_ncu);
}
char *
nwamd_get_dhcpinfo_data(const char *sym_name, char *ifname)
{
dhcp_symbol_t *entry;
dhcp_optnum_t optnum;
dhcp_ipc_request_t *request = NULL;
dhcp_ipc_reply_t *reply;
DHCP_OPT *opt;
size_t opt_len;
char *value;
int err;
char errmsg[LINE_MAX];
if (ifname == NULL)
ifname = "";
entry = inittab_getbyname(ITAB_CAT_SITE | ITAB_CAT_STANDARD |
ITAB_CAT_VENDOR | ITAB_CAT_FIELD, ITAB_CONS_INFO, sym_name);
if (entry == NULL) {
(void) snprintf(errmsg, LINE_MAX, "unknown identifier: %s",
sym_name);
goto fail;
}
optnum.code = entry->ds_code;
optnum.category = entry->ds_category;
optnum.size = entry->ds_max * inittab_type_to_size(entry);
request = dhcp_ipc_alloc_request(DHCP_GET_TAG, ifname, &optnum,
sizeof (dhcp_optnum_t), DHCP_TYPE_OPTNUM);
if (request == NULL) {
(void) snprintf(errmsg, LINE_MAX, "failed dhcp alloc request");
goto fail;
}
err = dhcp_ipc_make_request(request, &reply, DHCP_IPC_WAIT_DEFAULT);
if (err != 0 || reply->return_code != 0) {
(void) snprintf(errmsg, LINE_MAX, "%s",
dhcp_ipc_strerror(err == 0 ? reply->return_code : err));
}
opt = dhcp_ipc_get_data(reply, &opt_len, NULL);
if (opt_len == 0) {
(void) snprintf(errmsg, LINE_MAX, "invalid data");
goto fail;
}
if (opt_len < 2 || (opt_len -2 != opt->len)) {
(void) snprintf(errmsg, LINE_MAX, "data length mismatch");
goto fail;
}
opt_len -= 2;
value = inittab_decode(entry, opt->value, opt_len, B_TRUE);
if (value == NULL) {
(void) snprintf(errmsg, LINE_MAX, "cannot decode reply");
goto fail;
}
free(request);
free(reply);
return (value);
fail:
nlog(LOG_DEBUG, "get_dhcpinfo_data() failed: %s", errmsg);
free(request);
free(reply);
return (NULL);
}
void
nwamd_add_default_routes(nwamd_ncu_t *ncu)
{
nwamd_if_t *nif = &ncu->ncu_if;
char str[INET6_ADDRSTRLEN];
if (nif->nwamd_if_ipv4 && nif->nwamd_if_ipv4_default_route_set) {
struct sockaddr_in v4dest, v4mask;
v4dest.sin_addr.s_addr = htonl(INADDR_ANY);
v4dest.sin_family = AF_INET;
v4mask.sin_addr.s_addr = 0;
v4mask.sin_family = AF_INET;
nlog(LOG_DEBUG, "nwamd_add_default_routes: adding default "
"route %s", nwamd_sockaddr2str((struct sockaddr *)
&nif->nwamd_if_ipv4_default_route, str,
sizeof (str)));
nwamd_add_route((struct sockaddr *)&v4dest,
(struct sockaddr *)&v4mask,
(struct sockaddr *)&nif->nwamd_if_ipv4_default_route,
ncu->ncu_name);
}
if (nif->nwamd_if_ipv6 && nif->nwamd_if_ipv6_default_route_set) {
struct sockaddr_in6 v6dest, v6mask;
(void) bzero(&v6dest, sizeof (struct sockaddr_in6));
v6dest.sin6_family = AF_INET6;
(void) bzero(&v6mask, sizeof (struct sockaddr_in6));
v6mask.sin6_family = AF_INET6;
nlog(LOG_DEBUG, "nwamd_add_default_routes: adding default "
"route %s", nwamd_sockaddr2str((struct sockaddr *)
&nif->nwamd_if_ipv6_default_route, str,
sizeof (str)));
nwamd_add_route((struct sockaddr *)&v6dest,
(struct sockaddr *)&v6mask,
(struct sockaddr *)&nif->nwamd_if_ipv6_default_route,
ncu->ncu_name);
}
}
static struct nwamd_if_address *
find_static_address(const struct sockaddr_storage *addr, const nwamd_ncu_t *ncu)
{
struct nwamd_if_address *nifap, *nifa = ncu->ncu_if.nwamd_if_list;
struct sockaddr_storage saddr;
char str[INET6_ADDRSTRLEN];
nlog(LOG_DEBUG, "find_static_address: %s",
nwamd_sockaddr2str((struct sockaddr *)addr, str, sizeof (str)));
for (nifap = nifa; nifap != NULL; nifap = nifap->next) {
if (nifap->ipaddr_atype != IPADM_ADDR_STATIC ||
ipadm_get_addr(nifap->ipaddr, &saddr) != IPADM_SUCCESS)
continue;
if (sockaddrcmp(addr, &saddr))
return (nifap);
}
return (NULL);
}
static struct nwamd_if_address *
find_nonstatic_address(const nwamd_ncu_t *ncu, sa_family_t family)
{
struct nwamd_if_address *nifap, *nifa = ncu->ncu_if.nwamd_if_list;
const nwamd_if_t *u_if = &ncu->ncu_if;
nlog(LOG_DEBUG, "find_nonstatic_address for %s %s",
(family == AF_INET ? "IPv4" : "IPv6"), ncu->ncu_name);
for (nifap = nifa; nifap != NULL; nifap = nifap->next) {
if (nifap->ipaddr_atype == IPADM_ADDR_STATIC)
continue;
if (family == AF_INET) {
if (nifap->ipaddr_atype == IPADM_ADDR_DHCP &&
u_if->nwamd_if_dhcp_requested)
return (nifap);
} else if (family == AF_INET6) {
if (nifap->ipaddr_atype == IPADM_ADDR_IPV6_ADDRCONF &&
(u_if->nwamd_if_stateful_requested ||
u_if->nwamd_if_stateless_requested))
return (nifap);
}
}
return (NULL);
}
static struct nwamd_if_address *
find_configured_address(const struct sockaddr_storage *addr,
const nwamd_ncu_t *ncu)
{
struct nwamd_if_address *nifap, *nifa = ncu->ncu_if.nwamd_if_list;
char str[INET6_ADDRSTRLEN];
nlog(LOG_DEBUG, "find_configured_address: %s",
nwamd_sockaddr2str((struct sockaddr *)addr, str, sizeof (str)));
for (nifap = nifa; nifap != NULL; nifap = nifap->next) {
if (sockaddrcmp(addr, &nifap->conf_addr) ||
sockaddrcmp(addr, &nifap->conf_stateless_addr))
return (nifap);
}
return (NULL);
}
boolean_t
nwamd_static_addresses_configured(nwamd_ncu_t *ncu, sa_family_t family)
{
struct nwamd_if_address *n;
for (n = ncu->ncu_if.nwamd_if_list; n != NULL; n = n->next) {
if (n->ipaddr_atype != IPADM_ADDR_STATIC)
continue;
if ((family == AF_UNSPEC || family == n->family) &&
n->configured)
return (B_TRUE);
}
nlog(LOG_DEBUG, "no static addresses configured for %s", ncu->ncu_name);
return (B_FALSE);
}
boolean_t
nwamd_dhcp_managing(int protocol, nwamd_ncu_t *ncu)
{
struct sockaddr_storage addr;
uint64_t flags;
boolean_t rv = B_FALSE;
ipadm_addr_info_t *addrinfo, *a;
ipadm_status_t ipstatus;
if ((ipstatus = ipadm_addr_info(ipadm_handle, ncu->ncu_name, &addrinfo,
0, 0)) != IPADM_SUCCESS) {
nlog(LOG_ERR, "nwamd_dhcp_managing: "
"ipadm_addr_info failed for %s: %s",
ncu->ncu_name, ipadm_status2str(ipstatus));
return (B_FALSE);
}
for (a = addrinfo; a != NULL; a = IA_NEXT(a)) {
(void) memcpy(&addr, a->ia_ifa.ifa_addr, sizeof (addr));
if (find_static_address(&addr, ncu) != NULL)
continue;
if ((protocol == AF_INET &&
((struct sockaddr_in *)&addr)->sin_addr.s_addr ==
INADDR_ANY) ||
(protocol == AF_INET6 &&
IN6_IS_ADDR_LINKLOCAL(
&((struct sockaddr_in6 *)&addr)->sin6_addr))) {
continue;
}
flags = a->ia_ifa.ifa_flags;
if (flags & IFF_DHCPRUNNING) {
rv = B_TRUE;
break;
}
}
ipadm_free_addr_info(addrinfo);
return (rv);
}
static boolean_t
nwamd_v4_requested(nwamd_ncu_t *ncu)
{
boolean_t anyv4_requested;
nwamd_if_t *u_if;
anyv4_requested = B_FALSE;
u_if = &ncu->ncu_if;
if (u_if->nwamd_if_dhcp_requested) {
anyv4_requested = B_TRUE;
} else {
struct nwamd_if_address *n;
for (n = u_if->nwamd_if_list; n != NULL; n = n->next) {
if (n->family == AF_INET &&
n->ipaddr_atype == IPADM_ADDR_STATIC)
break;
}
if (n != NULL)
anyv4_requested = B_TRUE;
}
return (anyv4_requested);
}
static boolean_t
nwamd_v6_requested(nwamd_ncu_t *ncu)
{
boolean_t anyv6_requested;
nwamd_if_t *u_if;
anyv6_requested = B_FALSE;
u_if = &ncu->ncu_if;
if (u_if->nwamd_if_stateful_requested ||
u_if->nwamd_if_stateless_requested) {
anyv6_requested = B_TRUE;
} else {
struct nwamd_if_address *n;
for (n = u_if->nwamd_if_list; n != NULL; n = n->next) {
if (n->family == AF_INET6 &&
n->ipaddr_atype == IPADM_ADDR_STATIC)
break;
}
if (n != NULL)
anyv6_requested = B_TRUE;
}
return (anyv6_requested);
}
static void
interface_ncu_up_down(nwamd_ncu_t *ncu, boolean_t up)
{
boolean_t ncu_online;
char *name;
assert(ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE);
ncu_online = B_FALSE;
if (nwamd_v4_requested(ncu)) {
if (nwamd_dhcp_managing(AF_INET, ncu) ||
nwamd_static_addresses_configured(ncu, AF_INET))
ncu_online = B_TRUE;
} else if (nwamd_v6_requested(ncu)) {
if ((nwamd_dhcp_managing(AF_INET6, ncu) ||
stateless_running(ncu) ||
nwamd_static_addresses_configured(ncu, AF_INET6)))
ncu_online = B_TRUE;
}
if (nwam_ncu_name_to_typed_name(ncu->ncu_name, ncu->ncu_type, &name) !=
NWAM_SUCCESS) {
nlog(LOG_DEBUG, "interface_ncu_up_down: "
"nwam_ncu_name_to_typed_name failed");
return;
}
if (ncu_online && up) {
nlog(LOG_DEBUG, "interface_ncu_up_down: "
"bringing %s up", name);
nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name,
NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_UP);
} else if (!ncu_online && !up) {
nlog(LOG_DEBUG, "interface_ncu_up_down: "
"bringing %s down", name);
nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name,
NWAM_STATE_ONLINE_TO_OFFLINE,
NWAM_AUX_STATE_DOWN);
}
free(name);
}
static void
interface_ncu_up(nwamd_ncu_t *ncu)
{
interface_ncu_up_down(ncu, B_TRUE);
}
static void
interface_ncu_down(nwamd_ncu_t *ncu)
{
interface_ncu_up_down(ncu, B_FALSE);
}
static boolean_t
stateless_running(const nwamd_ncu_t *ncu)
{
ipadm_addr_info_t *ainfo, *ainfop;
ipadm_status_t ipstatus;
boolean_t rv = B_FALSE;
uint64_t flags;
if ((ipstatus = ipadm_addr_info(ipadm_handle, ncu->ncu_name, &ainfo,
0, 0)) != IPADM_SUCCESS) {
nlog(LOG_ERR, "stateless_running: "
"ipadm_addr_info failed for %s: %s",
ncu->ncu_name, ipadm_status2str(ipstatus));
return (B_FALSE);
}
for (ainfop = ainfo; ainfop != NULL; ainfop = IA_NEXT(ainfop)) {
if (ainfop->ia_ifa.ifa_addr->sa_family != AF_INET6)
continue;
flags = ainfop->ia_ifa.ifa_flags;
if (flags & STATELESS_RUNNING) {
rv = B_TRUE;
break;
}
}
ipadm_free_addr_info(ainfo);
return (rv);
}
static boolean_t
addrinfo_for_addr(const struct sockaddr_storage *caddr, const char *ifname,
ipadm_addr_info_t **ainfo)
{
ipadm_addr_info_t *addrinfo, *ainfop, *last = NULL;
ipadm_status_t ipstatus;
ipstatus = ipadm_addr_info(ipadm_handle, ifname, &addrinfo, 0, 0);
if (ipstatus != IPADM_SUCCESS) {
nlog(LOG_INFO, "addrinfo_for_addr: "
"ipadm_addr_info failed for %s: %s",
ifname, ipadm_status2str(ipstatus));
return (B_FALSE);
}
*ainfo = NULL;
for (ainfop = addrinfo; ainfop != NULL; ainfop = IA_NEXT(ainfop)) {
struct sockaddr_storage addr;
(void) memcpy(&addr, ainfop->ia_ifa.ifa_addr, sizeof (addr));
if (sockaddrcmp(&addr, caddr)) {
if (last != NULL)
last->ia_ifa.ifa_next = ainfop->ia_ifa.ifa_next;
else
addrinfo = IA_NEXT(ainfop);
ainfop->ia_ifa.ifa_next = NULL;
*ainfo = ainfop;
break;
}
last = ainfop;
}
ipadm_free_addr_info(addrinfo);
return (*ainfo == NULL ? B_FALSE : B_TRUE);
}
static boolean_t
addrinfo_for_ipaddr(ipadm_addrobj_t ipaddr, const char *ifname,
ipadm_addr_info_t **ainfo)
{
char aobjname[IPADM_AOBJSIZ];
ipadm_addr_info_t *addrinfo, *ainfop;
ipadm_addr_info_t *last = NULL;
ipadm_status_t ipstatus;
ipstatus = ipadm_get_aobjname(ipaddr, aobjname, sizeof (aobjname));
if (ipstatus != IPADM_SUCCESS)
return (B_FALSE);
ipstatus = ipadm_addr_info(ipadm_handle, ifname, &addrinfo, 0, 0);
if (ipstatus != IPADM_SUCCESS) {
nlog(LOG_INFO, "addrinfo_for_ipaddr: "
"ipadm_addr_info failed for %s: %s",
ifname, ipadm_status2str(ipstatus));
return (B_FALSE);
}
*ainfo = NULL;
ainfop = addrinfo;
while (ainfop != NULL) {
if (strcmp(ainfop->ia_aobjname, aobjname) == 0) {
ipadm_addr_info_t *match = ainfop;
ainfop = IA_NEXT(ainfop);
if (last != NULL)
last->ia_ifa.ifa_next = match->ia_ifa.ifa_next;
else
addrinfo = ainfop;
if (*ainfo == NULL)
match->ia_ifa.ifa_next = NULL;
else
match->ia_ifa.ifa_next = &(*ainfo)->ia_ifa;
*ainfo = match;
} else {
last = ainfop;
ainfop = IA_NEXT(ainfop);
}
}
ipadm_free_addr_info(addrinfo);
return (*ainfo == NULL ? B_FALSE : B_TRUE);
}
static boolean_t
add_ip_address(const char *ifname, const struct nwamd_if_address *nifa,
boolean_t *do_inform)
{
ipadm_status_t ipstatus;
ipadm_addr_info_t *addrinfo = NULL;
uint64_t flags;
if (nifa->ipaddr_atype == IPADM_ADDR_DHCP) {
nlog(LOG_DEBUG, "add_ip_address: "
"adding IPv4 DHCP address on %s", ifname);
nwamd_dhcp(ifname, nifa->ipaddr, DHCP_START);
} else {
nlog(LOG_DEBUG, "add_ip_address: adding %s address on %s",
(nifa->ipaddr_atype == IPADM_ADDR_STATIC ?
"STATIC" : "IPv6 ADDRCONF"), ifname);
if ((ipstatus = ipadm_create_addr(ipadm_handle, nifa->ipaddr,
IPADM_OPT_ACTIVE | IPADM_OPT_UP)) != IPADM_SUCCESS) {
nlog(LOG_ERR, "add_ip_address: "
"ipadm_create_addr failed on %s: %s",
ifname, ipadm_status2str(ipstatus));
return (B_FALSE);
}
if (nifa->ipaddr_atype == IPADM_ADDR_STATIC) {
if (!addrinfo_for_ipaddr(nifa->ipaddr, ifname,
&addrinfo)) {
nlog(LOG_ERR, "add_ip_address: "
"could not find addrinfo on %s", ifname);
return (B_FALSE);
}
flags = addrinfo->ia_ifa.ifa_flags;
ipadm_free_addr_info(addrinfo);
if (flags & IFF_DUPLICATE) {
char *object_name;
nwam_error_t err;
nlog(LOG_INFO, "add_ip_address: "
"duplicate address detected on %s", ifname);
if ((err = nwam_ncu_name_to_typed_name(ifname,
NWAM_NCU_TYPE_INTERFACE, &object_name))
== NWAM_SUCCESS) {
nwamd_object_set_state(
NWAM_OBJECT_TYPE_NCU,
object_name, NWAM_STATE_MAINTENANCE,
NWAM_AUX_STATE_IF_DUPLICATE_ADDR);
free(object_name);
} else {
nlog(LOG_ERR, "add_ip_address: "
"could not create state event "
"for %s: %s",
ifname, nwam_strerror(err));
}
return (B_FALSE);
}
if (*do_inform) {
nwamd_dhcp(ifname, nifa->ipaddr, DHCP_INFORM);
*do_inform = B_FALSE;
}
}
}
return (B_TRUE);
}
void
nwamd_configure_interface_addresses(nwamd_ncu_t *ncu)
{
struct nwamd_if_address *nifap, *nifa = ncu->ncu_if.nwamd_if_list;
boolean_t do_inform;
do_inform = !ncu->ncu_if.nwamd_if_dhcp_requested;
nlog(LOG_DEBUG, "nwamd_configure_interface_addresses(%s)",
ncu->ncu_name);
for (nifap = nifa; nifap != NULL; nifap = nifap->next) {
if (nifap->configured)
continue;
nifap->configured = add_ip_address(ncu->ncu_name, nifap,
&do_inform);
}
}
void
nwamd_ncu_handle_if_state_event(nwamd_event_t event)
{
nwam_event_t evm;
nwamd_object_t ncu_obj;
nwamd_ncu_t *ncu;
nwam_state_t state;
nwam_aux_state_t aux_state;
ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
event->event_object);
if (ncu_obj == NULL) {
nlog(LOG_INFO, "nwamd_ncu_handle_if_state_event: no object %s",
event->event_object);
nwamd_event_do_not_send(event);
return;
}
ncu = ncu_obj->nwamd_object_data;
evm = event->event_msg;
state = ncu_obj->nwamd_object_state;
aux_state = ncu_obj->nwamd_object_aux_state;
nlog(LOG_DEBUG, "nwamd_ncu_handle_if_state_event: "
"if %s, state (%s, %s)", event->event_object,
nwam_state_to_string(state), nwam_aux_state_to_string(aux_state));
switch (state) {
case NWAM_STATE_OFFLINE_TO_ONLINE:
if (aux_state != NWAM_AUX_STATE_IF_WAITING_FOR_ADDR &&
aux_state != NWAM_AUX_STATE_IF_DHCP_TIMED_OUT) {
nlog(LOG_DEBUG, "nwamd_ncu_handle_if_state_event: "
"if %s is in invalid aux state %s for IF_STATE "
"events", event->event_object,
nwam_aux_state_to_string(aux_state));
nwamd_event_do_not_send(event);
nwamd_object_release(ncu_obj);
return;
}
break;
case NWAM_STATE_ONLINE:
case NWAM_STATE_ONLINE_TO_OFFLINE:
case NWAM_STATE_OFFLINE:
break;
default:
nlog(LOG_DEBUG, "nwamd_ncu_handle_if_state_event: "
"if %s is in invalid state %s for IF_STATE events",
event->event_object, nwam_state_to_string(state));
nwamd_event_do_not_send(event);
nwamd_object_release(ncu_obj);
return;
}
if (evm->nwe_data.nwe_if_state.nwe_addr_valid) {
struct nwam_event_if_state *if_state;
char addrstr[INET6_ADDRSTRLEN];
boolean_t static_addr = B_FALSE, addr_added;
boolean_t v4dhcp_running, v6dhcp_running, stateless_running;
ipadm_addr_info_t *ai = NULL, *addrinfo = NULL;
boolean_t stateless_ai_found = B_FALSE;
boolean_t stateful_ai_found = B_FALSE;
struct nwamd_if_address *nifa = NULL;
nwamd_if_t *u_if;
struct sockaddr_storage *addr, ai_addr, *aip = NULL;
ushort_t family;
uint64_t flags = 0;
if_state = &evm->nwe_data.nwe_if_state;
u_if = &ncu->ncu_if;
family = if_state->nwe_addr.ss_family;
addr = &if_state->nwe_addr;
addr_added = if_state->nwe_addr_added;
v4dhcp_running = B_FALSE;
v6dhcp_running = B_FALSE;
stateless_running = B_FALSE;
nlog(LOG_DEBUG,
"nwamd_ncu_handle_if_state_event: addr %s %s",
nwamd_sockaddr2str((struct sockaddr *)addr, addrstr,
sizeof (addrstr)), addr_added ? "added" : "removed");
if (addr_added) {
if (!addrinfo_for_addr(addr, ncu->ncu_name, &ai)) {
nlog(LOG_ERR,
"nwamd_ncu_handle_if_state_event: "
"addrinfo doesn't exist for %s", addrstr);
nwamd_event_do_not_send(event);
goto valid_done;
}
addrinfo = ai;
flags = addrinfo->ia_ifa.ifa_flags;
(void) memcpy(&ai_addr, addrinfo->ia_ifa.ifa_addr,
sizeof (ai_addr));
aip = &ai_addr;
if (addrinfo->ia_atype == IPADM_ADDR_IPV6_ADDRCONF ||
addrinfo->ia_atype == IPADM_ADDR_DHCP)
nifa = find_nonstatic_address(ncu, family);
else if (addrinfo->ia_atype == IPADM_ADDR_STATIC)
nifa = find_static_address(addr, ncu);
if (nifa == NULL) {
nlog(LOG_ERR,
"nwamd_ncu_handle_if_state_event: "
"address %s not managed by nwam added, "
"removing it", addrstr);
nwamd_down_interface(addrinfo->ia_aobjname,
addrinfo->ia_atype, ncu->ncu_name);
nwamd_event_do_not_send(event);
goto valid_done;
}
stateless_running = (family == AF_INET6) &&
((flags & STATELESS_RUNNING) == STATELESS_RUNNING);
v4dhcp_running = (family == AF_INET) &&
((flags & DHCP_RUNNING) == DHCP_RUNNING);
v6dhcp_running = (family == AF_INET6) &&
((flags & DHCP_RUNNING) == DHCP_RUNNING);
static_addr = (addrinfo->ia_atype == IPADM_ADDR_STATIC);
if (stateless_running) {
(void) memcpy(&nifa->conf_stateless_addr,
addrinfo->ia_ifa.ifa_addr,
sizeof (struct sockaddr_storage));
} else {
(void) memcpy(&nifa->conf_addr,
addrinfo->ia_ifa.ifa_addr,
sizeof (struct sockaddr_storage));
}
} else {
nifa = find_configured_address(addr, ncu);
if (nifa == NULL) {
nlog(LOG_ERR,
"nwamd_ncu_handle_if_state_event: "
"address %s not managed by nwam removed, "
"nothing to do", addrstr);
nwamd_event_do_not_send(event);
goto valid_done;
}
if (addrinfo_for_ipaddr(nifa->ipaddr, ncu->ncu_name,
&ai)) {
ipadm_addr_info_t *a;
for (a = ai; a != NULL; a = IA_NEXT(a)) {
struct sockaddr_storage stor;
(void) memcpy(&stor, a->ia_ifa.ifa_addr,
sizeof (stor));
if (sockaddrcmp(addr, &stor)) {
flags = a->ia_ifa.ifa_flags;
(void) memcpy(&ai_addr,
a->ia_ifa.ifa_addr,
sizeof (ai_addr));
aip = &ai_addr;
addrinfo = a;
}
if (family == AF_INET6) {
stateless_ai_found =
(a->ia_ifa.ifa_flags &
STATELESS_RUNNING);
stateful_ai_found =
(a->ia_ifa.ifa_flags &
DHCP_RUNNING);
}
}
}
}
evm->nwe_data.nwe_if_state.nwe_flags = flags;
if (family == AF_INET && !addr_added) {
if (((struct sockaddr_in *)addr)->sin_addr.s_addr
== INADDR_ANY && aip != 0) {
struct sockaddr_in *a;
char astr[INET6_ADDRSTRLEN];
a = (struct sockaddr_in *)aip;
if ((flags & IFF_UP) &&
!(flags & IFF_RUNNING) &&
a->sin_addr.s_addr != INADDR_ANY) {
nlog(LOG_DEBUG,
"nwamd_ncu_handle_if_state_event: "
"bug workaround: clear out addr "
"%s on %s", nwamd_sockaddr2str
((struct sockaddr *)a, astr,
sizeof (astr)),
ncu->ncu_name);
nwamd_down_interface(
addrinfo->ia_aobjname,
IPADM_ADDR_DHCP, ncu->ncu_name);
}
goto valid_done;
}
}
if (addr_added & !(flags & IFF_UP)) {
nlog(LOG_INFO, "nwamd_ncu_handle_if_state_event: "
"address %s added on %s without IFF_UP flag (%x), "
"ignoring IF_STATE event",
addrstr, ncu->ncu_name, flags);
nwamd_event_do_not_send(event);
goto valid_done;
}
if (!addr_added && !(flags & IFF_DUPLICATE)) {
if (aip != 0 && sockaddrcmp(addr, aip)) {
nlog(LOG_INFO,
"nwamd_ncu_handle_if_state_event: "
"address %s is not really gone from %s, "
"ignoring IF_STATE event",
addrstr, ncu->ncu_name);
nwamd_event_do_not_send(event);
goto valid_done;
}
}
if (addr_added) {
if (u_if->nwamd_if_dhcp_requested && v4dhcp_running) {
u_if->nwamd_if_dhcp_configured = B_TRUE;
} else if (u_if->nwamd_if_stateful_requested &&
v6dhcp_running) {
u_if->nwamd_if_stateful_configured = B_TRUE;
} else if (u_if->nwamd_if_stateless_requested &&
stateless_running) {
u_if->nwamd_if_stateless_configured = B_TRUE;
} else if (!static_addr) {
nwamd_down_interface(addrinfo->ia_aobjname,
addrinfo->ia_atype, ncu->ncu_name);
nifa->configured = B_FALSE;
goto valid_done;
}
nifa->configured = B_TRUE;
if (state != NWAM_STATE_ONLINE)
interface_ncu_up(ncu);
nlog(LOG_DEBUG, "nwamd_handle_if_state_event: "
"refreshing %s as we may have other "
"DHCP information", NET_LOC_FMRI);
(void) smf_restore_instance(NET_LOC_FMRI);
if (smf_refresh_instance(NET_LOC_FMRI) != 0) {
nlog(LOG_ERR,
"nwamd_ncu_handle_if_state_"
"event: refresh of %s "
"failed", NET_LOC_FMRI);
}
} else if (state == NWAM_STATE_ONLINE ||
state == NWAM_STATE_OFFLINE_TO_ONLINE) {
nifa->configured = B_FALSE;
if (!static_addr && family == AF_INET) {
u_if->nwamd_if_dhcp_configured = B_FALSE;
} else if (!static_addr && family == AF_INET6) {
u_if->nwamd_if_stateful_configured =
stateless_ai_found;
u_if->nwamd_if_stateless_configured =
stateful_ai_found;
}
if (flags & IFF_DUPLICATE) {
nlog(LOG_INFO,
"nwamd_ncu_handle_if_state_event: "
"duplicate address detected on %s",
ncu->ncu_name);
nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
event->event_object,
NWAM_STATE_MAINTENANCE,
NWAM_AUX_STATE_IF_DUPLICATE_ADDR);
} else {
interface_ncu_down(ncu);
}
}
valid_done:
ipadm_free_addr_info(ai);
}
nwamd_object_release(ncu_obj);
}
void
nwamd_ncu_handle_if_action_event(nwamd_event_t event)
{
nwamd_object_t ncu_obj;
nlog(LOG_DEBUG, "if action event %s",
event->event_object[0] == '\0' ? "n/a" : event->event_object);
ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object);
if (ncu_obj == NULL) {
nlog(LOG_ERR, "nwamd_ncu_handle_if_action_event: no object");
nwamd_event_do_not_send(event);
return;
}
nwamd_object_release(ncu_obj);
}
static void
nwamd_down_interface(const char *aobjname, ipadm_addr_type_t atype,
const char *ifname)
{
ipadm_status_t ipstatus;
uint32_t rflags = (atype == IPADM_ADDR_DHCP ? IPADM_OPT_RELEASE : 0);
nlog(LOG_DEBUG, "nwamd_down_interface: %s [aobjname = %s]",
ifname, aobjname);
if ((ipstatus = ipadm_delete_addr(ipadm_handle, aobjname,
IPADM_OPT_ACTIVE | rflags)) != IPADM_SUCCESS) {
nlog(LOG_ERR, "nwamd_down_interface: "
"ipadm_delete_addr failed on %s: %s",
ifname, ipadm_status2str(ipstatus));
}
}
static void
unconfigure_addresses(nwamd_ncu_t *ncu, sa_family_t af)
{
struct nwamd_if_address *nifap, *nifa = ncu->ncu_if.nwamd_if_list;
for (nifap = nifa; nifap != NULL; nifap = nifap->next)
if (af == AF_UNSPEC || nifap->family == af)
nifap->configured = B_FALSE;
}
static void
dhcp_release(const char *ifname)
{
ipadm_addr_info_t *ainfo, *ainfop;
if (ipadm_addr_info(ipadm_handle, ifname, &ainfo, 0, 0)
!= IPADM_SUCCESS)
return;
for (ainfop = ainfo; ainfop != NULL; ainfop = IA_NEXT(ainfop)) {
if (ainfop->ia_atype == IPADM_ADDR_DHCP)
nwamd_down_interface(ainfop->ia_aobjname,
ainfop->ia_atype, ifname);
}
ipadm_free_addr_info(ainfo);
}
static void
nwamd_plumb_unplumb_interface(nwamd_ncu_t *ncu, sa_family_t af, boolean_t plumb)
{
char *ifname = ncu->ncu_name;
nwamd_if_t *u_if = &ncu->ncu_if;
ipadm_status_t ipstatus;
nlog(LOG_DEBUG, "nwamd_plumb_unplumb_interface: %s %s %s",
(plumb ? "plumb" : "unplumb"), (af == AF_INET ? "IPv4" : "IPv6"),
ifname);
if (plumb) {
ipstatus = ipadm_create_if(ipadm_handle, ifname, af,
IPADM_OPT_ACTIVE);
} else {
if (af == AF_INET)
dhcp_release(ifname);
ipstatus = ipadm_delete_if(ipadm_handle, ifname, af,
IPADM_OPT_ACTIVE);
}
if (ipstatus != IPADM_SUCCESS) {
if ((plumb && ipstatus != IPADM_IF_EXISTS) ||
(!plumb && ipstatus != IPADM_ENXIO)) {
nlog(LOG_ERR, "nwamd_plumb_unplumb_interface: "
"%s %s failed for %s: %s",
(plumb ? "plumb" : "unplumb"),
(af == AF_INET ? "IPv4" : "IPv6"),
ifname, ipadm_status2str(ipstatus));
}
}
if (!plumb) {
unconfigure_addresses(ncu, af);
switch (af) {
case AF_INET:
u_if->nwamd_if_dhcp_configured = B_FALSE;
break;
case AF_INET6:
u_if->nwamd_if_stateful_configured = B_FALSE;
u_if->nwamd_if_stateless_configured = B_FALSE;
break;
}
}
}
void
nwamd_plumb_interface(nwamd_ncu_t *ncu, sa_family_t af)
{
nwamd_escalate();
nwamd_plumb_unplumb_interface(ncu, af, B_TRUE);
nwamd_deescalate();
}
void
nwamd_unplumb_interface(nwamd_ncu_t *ncu, sa_family_t af)
{
nwamd_plumb_unplumb_interface(ncu, af, B_FALSE);
}
static void *
start_dhcp_thread(void *arg)
{
struct nwamd_dhcp_thread_arg *thread_arg = arg;
nwamd_object_t ncu_obj;
dhcp_ipc_type_t type;
char *name;
ipadm_addrobj_t ipaddr;
ipadm_status_t ipstatus;
int retries = 0;
name = thread_arg->name;
type = thread_arg->type;
ipaddr = thread_arg->ipaddr;
retry:
ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_INTERFACE, name);
if (ncu_obj == NULL) {
nlog(LOG_ERR, "start_dhcp: no IP object %s", name);
return (NULL);
}
if (ncu_obj->nwamd_object_state != NWAM_STATE_OFFLINE_TO_ONLINE &&
ncu_obj->nwamd_object_state != NWAM_STATE_ONLINE) {
nlog(LOG_INFO, "start_dhcp: IP NCU %s is in invalid state "
"for DHCP command", ncu_obj->nwamd_object_name);
nwamd_object_release(ncu_obj);
return (NULL);
}
nwamd_object_release(ncu_obj);
switch (type) {
case DHCP_INFORM:
{
char aobjname[IPADM_AOBJSIZ];
if ((ipstatus = ipadm_get_aobjname(ipaddr, aobjname,
sizeof (aobjname))) != IPADM_SUCCESS) {
nlog(LOG_ERR, "start_dhcp: "
"ipadm_get_aobjname failed for %s: %s",
name, ipadm_status2str(ipstatus));
goto done;
}
ipstatus = ipadm_refresh_addr(ipadm_handle, aobjname,
IPADM_OPT_ACTIVE | IPADM_OPT_INFORM);
break;
}
case DHCP_START:
ipstatus = ipadm_create_addr(ipadm_handle, ipaddr,
IPADM_OPT_ACTIVE);
break;
default:
nlog(LOG_ERR, "start_dhcp: invalid dhcp_ipc_type_t: %d", type);
goto done;
}
if (ipstatus == IPADM_DHCP_IPC_TIMEOUT) {
if (type == DHCP_START) {
char *object_name;
nlog(LOG_INFO,
"start_dhcp: DHCP_START timed out for %s", name);
if (nwam_ncu_name_to_typed_name(name,
NWAM_NCU_TYPE_INTERFACE, &object_name)
!= NWAM_SUCCESS) {
nlog(LOG_ERR, "start_dhcp: "
"nwam_ncu_name_to_typed_name failed for %s",
name);
goto done;
}
nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
object_name, NWAM_STATE_OFFLINE_TO_ONLINE,
NWAM_AUX_STATE_IF_DHCP_TIMED_OUT);
nwamd_create_ncu_check_event(0);
free(object_name);
} else {
nlog(LOG_INFO,
"start_dhcp: DHCP_INFORM timed out for %s", name);
}
} else if ((ipstatus == IPADM_DHCP_IPC_ERROR ||
ipstatus == IPADM_IPC_ERROR) && retries++ < NWAMD_DHCP_RETRIES) {
nlog(LOG_ERR, "start_dhcp: ipadm_%s_addr on %s returned: %s, "
"retrying in %d sec",
(type == DHCP_START ? "create" : "refresh"), name,
ipadm_status2str(ipstatus), NWAMD_DHCP_RETRY_WAIT_TIME);
(void) sleep(NWAMD_DHCP_RETRY_WAIT_TIME);
goto retry;
} else if (ipstatus != IPADM_SUCCESS) {
nlog(LOG_ERR, "start_dhcp: ipadm_%s_addr failed for %s: %s",
(type == DHCP_START ? "create" : "refresh"), name,
ipadm_status2str(ipstatus));
}
done:
free(name);
free(arg);
return (NULL);
}
static void
nwamd_dhcp(const char *ifname, ipadm_addrobj_t ipaddr, dhcp_ipc_type_t cmd)
{
struct nwamd_dhcp_thread_arg *arg;
pthread_attr_t attr;
nlog(LOG_DEBUG, "nwamd_dhcp: starting DHCP %s thread for %s",
dhcp_ipc_type_to_string(cmd), ifname);
arg = malloc(sizeof (*arg));
if (arg == NULL) {
nlog(LOG_ERR, "nwamd_dhcp: error allocating memory for "
"dhcp request");
return;
}
arg->name = strdup(ifname);
arg->type = cmd;
arg->ipaddr = ipaddr;
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create(NULL, &attr, start_dhcp_thread, arg) == -1) {
nlog(LOG_ERR, "nwamd_dhcp: cannot start dhcp thread");
free(arg->name);
free(arg);
(void) pthread_attr_destroy(&attr);
return;
}
(void) pthread_attr_destroy(&attr);
}