/*
* 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
* 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
*/
/*
*/
#include <assert.h>
#include <libdlaggr.h>
#include <libdllink.h>
#include <libdlstat.h>
#include <libnwam.h>
#include <libscf.h>
#include <stdlib.h>
#include <strings.h>
#include <values.h>
#include <zone.h>
#include "conditions.h"
#include "events.h"
#include "objects.h"
#include "ncp.h"
#include "util.h"
/*
* ncu.c - handles various NCU tasks - intialization/refresh, state machine
* for NCUs etc.
*/
/*
*/
{
char *object_name;
!= NWAM_SUCCESS) {
return (NULL);
}
return (ncu_obj);
}
const char *prop)
{
!= NWAM_SUCCESS)
return (err);
return (err);
}
const char *prop)
{
!= NWAM_SUCCESS)
return (err);
return (err);
}
{
return (err);
}
{
return (err);
}
/*
*/
static void
{
== NULL) {
"request for nonexistent NCU %s", object_name);
return;
}
switch (object->nwamd_object_aux_state) {
/*
* dlpi_open()ing the link prevents the driver from
* being unloaded.
*/
/*
* First, if we're unexpectedly connected,
* disconnect.
*/
if (!link->nwamd_link_wifi_connected &&
"nwamd_ncu_state_machine: "
"WiFi unexpectedly connected, "
"disconnecting...");
(void) dladm_wlan_disconnect(dld_handle,
}
/* move to scanning aux state */
} else {
/*
* If initial wired link state is unknown, we
* will need to assume the link is up, since
* we won´t get DL_NOTE_LINK_UP/DOWN events.
*/
if (link_state == LINK_STATE_UP ||
link_state == LINK_STATE_UNKNOWN) {
} else {
}
}
} else {
/*
* In the current implementation, initialization has to
* start from scratch since the complexity of minimizing
* configuration change is considerable (e.g. if we
* refresh and had DHCP running on the physical
* interface, and now have changed to static assignment,
* we need to remove DHCP etc). To avoid all this,
* unplumb before re-plumbing the protocols and
* addresses we wish to configure. In the future, it
* would be good to try and minimize configuration
* changes.
*/
/*
* We may be restarting the state machine. Re-read
* the IP NCU properties as the ipadm_addrobj_t in
* nwamd_if_address should not be reused.
*/
ncu);
/*
* Enqueue a WAITING_FOR_ADDR aux state change so that
* we are eligible to receive the IF_STATE events
* associated with static, DHCP, DHCPv6 and autoconf
* address assignment. The latter two can happen
* quite quickly after plumbing so we need to be ready.
*/
/* Configure addresses */
}
break;
/*
* nothing to do here - RTM_NEWADDRs will trigger IF_STATE
* events to move us online.
*/
break;
/* launch scan thread */
(void) nwamd_wlan_scan(linkname);
/* Create periodic scan event */
break;
/* send "need choice" event */
break;
break;
/*
* Send "need key" event. Set selected to true, connected
* and have_key to false. Do not fill in WLAN details as
* multiple WLANs may match the ESSID name, and each may
* have a different speed and channel.
*/
&key_wlan, 1);
break;
break;
break;
case NWAM_AUX_STATE_UP:
case NWAM_AUX_STATE_DOWN:
/*
* Connected/disconnected - send WLAN
* connection report.
*/
sizeof (connected_wlan.nww_essid));
sizeof (connected_wlan.nww_bssid));
&connected_wlan, 1);
break;
/*
* If disconnected, restart the state machine
* for the WiFi link (WiFi is always trying
* to connect).
*
* If connected, start signal strength
* monitoring thread.
*/
"nwamd_ncu_state_machine: "
"wifi disconnect - start over "
"after %dsec interval",
/* propogate down event to IP NCU */
} else {
"nwamd_ncu_state_machine: "
"wifi connected, start monitoring");
sizeof (linkname));
}
}
}
"%s is moving %s", object_name,
if (up) {
/*
* routes (if any).
*/
} else {
/*
* If this is an interface NCU and we
* got a down event, it is a consequence
* of NCU refresh, so reapply addresses
* by reinitializing.
*/
}
}
} else {
"%s is %s", object_name,
}
/*
* NCU is UP or DOWN, trigger all condition checking, even if
* the NCU is already in the ONLINE state - an ENM may depend
* on NCU activity.
*/
break;
/*
* for WiFi, where we disconnect. Don't unplumb IP on
* a link since it may be a transient change.
*/
(void) dladm_wlan_disconnect(dld_handle,
B_FALSE);
}
} else {
/*
* Unplumb here. In the future we may elaborate on
* the approach used and not unplumb for WiFi
* until we reconnect to a different WLAN (i.e. with
* a different ESSID).
*/
}
}
break;
/* Manual disable, set enabled state appropriately. */
/* FALLTHROUGH */
case NWAM_AUX_STATE_NOT_FOUND:
/*
* Link/interface NCU has been disabled/deactivated/removed.
* For WiFi links disconnect, and for IP interfaces we unplumb.
*/
(void) dladm_wlan_disconnect(dld_handle,
B_FALSE);
}
} else {
/* Unplumb here. */
}
}
/* trigger location condition checking */
}
switch (object->nwamd_object_aux_state) {
/* Change state to DISABLED if manually disabled */
/* Note that NCU has been disabled */
break;
case NWAM_AUX_STATE_NOT_FOUND:
/* Change state to UNINITIALIZED for device removal */
break;
default:
break;
}
break;
default:
break;
}
}
static int
{
"ncu_create_init_fini_event: could not get NCU name");
return (0);
}
NWAM_NCU_PROP_TYPE)) != NWAM_SUCCESS) {
return (0);
}
/* convert name to typedname for event */
!= NWAM_SUCCESS) {
return (0);
}
return (0);
}
/*
* Initialization - walk the NCUs, creating initialization events for each
* NCU. nwamd_ncu_handle_init_event() will check if the associated
* physical link exists or not.
*/
void
nwamd_init_ncus(void)
{
(void) pthread_mutex_lock(&active_ncp_mutex);
if (active_ncph != NULL) {
"(re)intializing NCUs for NCP %s", active_ncp);
(void) nwam_ncp_walk_ncus(active_ncph,
NULL);
}
(void) pthread_mutex_unlock(&active_ncp_mutex);
}
void
nwamd_fini_ncus(void)
{
/* We may not have an active NCP on initialization, so skip fini */
(void) pthread_mutex_lock(&active_ncp_mutex);
if (active_ncph != NULL) {
(void) nwam_ncp_walk_ncus(active_ncph,
NULL);
}
(void) pthread_mutex_unlock(&active_ncp_mutex);
}
/*
* Most properties of this type don't need to be cached locally. Only those
* interesting to the daemon are stored in an nwamd_ncu_t.
*/
static void
{
char **parent;
&ncu_prop)) != NWAM_SUCCESS) {
char *name;
} else {
NWAM_SUCCESS) {
} else {
}
}
} else {
sizeof (ncu_data->ncu_parent));
}
}
/*
* Read in link properties.
*/
static void
{
char **mac_addr;
/* activation-mode */
"populate_link_ncu_properties: could not get %s value: %s",
} else {
}
/* priority-group and priority-mode for prioritized activation */
/* ncus with prioritized activation are always enabled */
!= NWAM_SUCCESS) {
"could not get %s value: %s",
} else {
uintval[0];
}
!= NWAM_SUCCESS) {
"could not get %s value: %s",
} else {
uintval[0];
}
}
/* link-mac-addr */
"populate_link_ncu_properties: could not get %s value: %s",
} else {
}
/* link-mtu */
NWAM_NCU_PROP_LINK_MTU)) != NWAM_SUCCESS) {
"populate_link_ncu_properties: could not get %s value: %s",
} else {
}
/* link-autopush */
"populate_link_ncu_properties: could not get %s value: %s",
}
}
static void
{
char **addrvalue;
int i;
/* ip-version */
NWAM_NCU_PROP_IP_VERSION)) != NWAM_SUCCESS) {
"populate_ip_ncu_properties: could not get %s value: %s",
} else {
for (i = 0; i < numvalues; i++) {
switch (ipversion[i]) {
case IPV4_VERSION:
break;
case IPV6_VERSION:
break;
default:
ipversion[i]);
break;
}
}
}
/* Free the old list. */
}
if (!nif->nwamd_if_ipv4)
goto skip_ipv4;
/* ipv4-addrsrc */
"populate_ip_ncu_properties: could not get %s value: %s",
} else {
for (i = 0; i < numvalues; i++) {
switch (addrsrcvalue[i]) {
case NWAM_ADDRSRC_DHCP:
break;
case NWAM_ADDRSRC_STATIC:
break;
default:
break;
}
}
}
if (nif->nwamd_if_dhcp_requested) {
if (ipstatus != IPADM_SUCCESS) {
"ipadm_create_addrobj failed for v4 dhcp: %s",
goto skip_ipv4_dhcp;
}
if (ipstatus != IPADM_SUCCESS) {
"ipadm_set_wait_time failed for v4 dhcp: %s",
goto skip_ipv4_dhcp;
}
} else {
"couldn't allocate nwamd address for v4 dhcp: %s",
}
}
/* ipv4-addr */
if (static_addr) {
"could not get %s value; %s",
} else {
for (i = 0; i < numvalues; i++) {
&ipaddr);
if (ipstatus != IPADM_SUCCESS) {
"populate_ip_ncu_properties: "
"ipadm_create_addrobj failed "
"for %s: %s", addrvalue[i],
continue;
}
/* ipadm_set_addr takes <addr>[/<mask>] */
AF_INET);
if (ipstatus != IPADM_SUCCESS) {
"populate_ip_ncu_properties: "
"ipadm_set_addr failed for %s: %s",
addrvalue[i],
continue;
}
!= NULL) {
(*nifa)->ipaddr_atype =
} else {
"populate_ip_ncu_properties: "
"couldn't allocate nwamd address "
"for %s: %s", addrvalue[i],
}
}
}
}
/* get default route, if any */
/* Only one default route is allowed. */
}
if (!nif->nwamd_if_ipv6)
goto skip_ipv6;
/* ipv6-addrsrc */
"populate_ip_ncu_properties: could not get %s value: %s",
} else {
for (i = 0; i < numvalues; i++) {
switch (addrsrcvalue[i]) {
case NWAM_ADDRSRC_DHCP:
break;
case NWAM_ADDRSRC_AUTOCONF:
break;
case NWAM_ADDRSRC_STATIC:
break;
default:
break;
}
}
}
/*
* Both stateful and stateless share the same nwamd_if_address because
* only one ipaddr for both of these addresses can be created.
* ipadm_create_addr() adds both addresses from the same ipaddr.
*/
if (nif->nwamd_if_stateful_requested ||
if (ipstatus != IPADM_SUCCESS) {
"ipadm_create_addrobj failed for v6 "
goto skip_ipv6_addrconf;
}
/* create_addrobj sets both stateless and stateful to B_TRUE */
if (!nif->nwamd_if_stateful_requested) {
if (ipstatus != IPADM_SUCCESS) {
"ipadm_set_stateful failed for v6: %s",
goto skip_ipv6_addrconf;
}
}
if (!nif->nwamd_if_stateless_requested) {
if (ipstatus != IPADM_SUCCESS) {
"ipadm_set_stateless failed for v6: %s",
goto skip_ipv6_addrconf;
}
}
} else {
"couldn't allocate nwamd address for "
}
}
/* ipv6-addr */
if (static_addr) {
"could not get %s value; %s",
} else {
for (i = 0; i < numvalues; i++) {
&ipaddr);
if (ipstatus != IPADM_SUCCESS) {
"populate_ip_ncu_properties: "
"ipadm_create_addrobj failed "
"for %s: %s", addrvalue[i],
continue;
}
/* ipadm_set_addr takes <addr>[/<mask>] */
AF_INET6);
if (ipstatus != IPADM_SUCCESS) {
"populate_ip_ncu_properties: "
"ipadm_set_addr failed for %s: %s",
addrvalue[i],
continue;
}
!= NULL) {
(*nifa)->ipaddr_atype =
} else {
"populate_ip_ncu_properties: "
"couldn't allocate nwamd address "
"for %s: %s", addrvalue[i],
}
}
}
}
/* get default route, if any */
/* Only one default route is allowed. */
}
;
}
static nwamd_ncu_t *
{
return (NULL);
/* Initialize link/interface-specific data */
(void) pthread_mutex_init(
} else {
}
return (rv);
}
void
{
int i;
free(l->nwamd_link_wifi_key);
free(l->nwamd_link_mac_addr);
for (i = 0; i < l->nwamd_link_num_autopush; i++)
free(l->nwamd_link_autopush[i]);
struct nwamd_if_address *n;
n = nifa;
free(n);
}
}
}
}
static int
{
return (0);
}
void
nwamd_log_ncus(void)
{
NULL);
}
int
{
return (1);
return (0);
}
static void
{
&media)) != DLADM_STATUS_OK) {
return;
}
nwam_strerror(err));
if (err == NWAM_ENTITY_READ_ONLY) {
/*
* Root filesystem may be read-only, retry in
* a few seconds.
*/
name);
if (retry_event != NULL) {
}
}
return;
}
goto finish;
}
goto finish;
}
goto finish;
}
if (err != NWAM_SUCCESS) {
"failed to create automatic link ncu for %s: %s",
}
}
static void
{
nwam_strerror(err));
/*
* Root filesystem may be read-only, but no need to
* retry here since add_phys_ncu_to_ncp() enqueues
* a retry event which will lead to add_ip_ncu_to_ncp()
* being called.
*/
return;
}
/* IP NCU has the default values, so nothing else to do */
if (err != NWAM_SUCCESS) {
"failed to create ip ncu for %s: %s", name,
nwam_strerror(err));
}
}
static void
{
nwam_strerror(err));
return;
}
if (err != NWAM_SUCCESS) {
nwam_strerror(err));
}
}
/*
* Device represented by NCU has been added or removed for the active
* User NCP. If an associated NCU of the given type is found, transition it
* to the appropriate state.
*/
void
const char *name)
{
return;
/*
* If device has been added, transition from uninitialized to offline.
* If device has been removed, transition to uninitialized (via online*
* if the NCU is currently enabled in order to tear down config).
*/
if (action == NWAM_ACTION_ADD) {
} else {
if (ncu->ncu_enabled) {
} else {
}
}
}
/*
* Called with hotplug sysevent or when nwam is started and walking the
* Automatic NCP. Assumes that both link and interface NCUs don't exist.
*/
void
{
char *name;
return;
}
&name)) != NWAM_SUCCESS) {
return;
}
(void) pthread_mutex_lock(&active_ncp_mutex);
active_ncph != NULL) {
}
(void) pthread_mutex_unlock(&active_ncp_mutex);
/*
* We could use active_ncph for cases where the Automatic NCP is active,
* but that would involve holding the active_ncp_mutex for too long.
*/
== NWAM_ENTITY_NOT_FOUND) {
/* Automatic NCP doesn't exist, create it */
}
if (err != NWAM_SUCCESS)
goto fail;
/* add or remove NCUs from Automatic NCP */
if (action == NWAM_ACTION_ADD) {
} else {
/*
* Order is important here, remove IP NCU first to prevent
* propogation of down event from link to IP. No need to
* create REFRESH or DESTROY events. They are generated by
* nwam_ncu_commit() and nwam_ncu_destroy().
*/
}
/*
* If the Automatic NCP is not active, and the associated NCUs
* exist, they must be moved into the appropriate states given the
* action that has occurred.
*/
if (!automatic_ncp_active) {
}
NWAM_OBJECT_TYPE_NCP, NULL)) {
}
fail:
if (err != NWAM_SUCCESS) {
action);
if (retry_event == NULL) {
"%s NCP", NWAM_NCP_NAME_AUTOMATIC);
return;
}
}
}
/*
* Figure out if this link is part of an aggregation. This is fairly
* inefficient since we generate this list for every query and search
* linearly. A better way would be to generate the list of links in an
* aggregation once and then check each link against it.
*/
struct link_aggr_search_data {
};
static int
{
int i;
return (DLADM_WALK_CONTINUE);
return (DLADM_WALK_CONTINUE);
return (DLADM_WALK_TERMINATE);
}
}
return (DLADM_WALK_CONTINUE);
}
static boolean_t
{
!= DLADM_STATUS_OK)
return (B_FALSE);
}
/*
* If NCU doesn't exist for interface with given name, enqueue a ADD
* LINK_ACTION event.
*/
static int
{
/* Do not generate an event if this is a VirtualBox interface. */
return (DLADM_WALK_CONTINUE);
/* Do not generate an event if this link belongs to another zone. */
return (DLADM_WALK_CONTINUE);
/* Do not generate an event if this link belongs to an aggregation. */
if (nwamd_link_belongs_to_an_aggr(name)) {
return (DLADM_WALK_CONTINUE);
}
/* Don't create an event if the NCU already exists. */
&ncuh) == NWAM_SUCCESS) {
return (DLADM_WALK_CONTINUE);
}
name);
if (link_event != NULL)
return (DLADM_WALK_CONTINUE);
}
/*
* Check if interface exists for this NCU. If not, enqueue a REMOVE
* LINK_ACTION event.
*/
/* ARGSUSED */
static int
{
char *name;
return (0);
}
/* Interfaces that exist return DLADM_OPT_ACTIVE flag */
return (0);
}
if (link_event != NULL)
return (0);
}
/*
* Called when nwamd is starting up.
*
* Walk all NCUs and destroy any NCU from the Automatic NCP without an
* underlying interface (assumption here is that the interface was removed
* when nwam was disabled).
*
* Walk the physical interfaces and create ADD LINK_ACTION event, which
* will create appropriate interface and link NCUs in the Automatic NCP.
*/
void
{
(void) pthread_mutex_lock(&active_ncp_mutex);
active_ncph != NULL) {
ncph = active_ncph;
} else {
!= NWAM_SUCCESS) {
}
}
/* destroy NCUs for interfaces that don't exist */
}
/* In non-global zones NWAM can support VNICs */
if (zoneid != GLOBAL_ZONEID)
/* create NCUs for interfaces without NCUs */
active_ncph == NULL) {
}
(void) pthread_mutex_unlock(&active_ncp_mutex);
}
/*
* Handle NCU initialization/refresh event.
*/
void
{
char *name;
/* Get base linkname rather than interface:linkname or link:linkname */
if (err != NWAM_SUCCESS) {
"nwam_ncu_typed_name_to_name returned %s",
nwam_strerror(err));
return;
}
(void) pthread_mutex_lock(&active_ncp_mutex);
if (active_ncph == NULL) {
"nwamd_ncu_handle_init_event: active NCP handle NULL");
(void) pthread_mutex_unlock(&active_ncp_mutex);
return;
}
(void) pthread_mutex_unlock(&active_ncp_mutex);
if (err != NWAM_SUCCESS) {
"could not read object '%s': %s",
return;
}
/*
* For new NCUs, or interface NCUs, we (re)initialize data from scratch.
* For link NCUs, we want to retain object data.
*/
switch (type) {
case NWAM_NCU_TYPE_LINK:
if (new) {
} else {
}
break;
case NWAM_NCU_TYPE_INTERFACE:
if (!new) {
}
break;
default:
return;
}
if (new) {
"ncu so create it %s", name);
} else {
"ncu %s", name);
}
/*
* If the physical link for this NCU doesn't exist in the system,
* the state should be UNINITIALIZED/NOT_FOUND. Interfaces that
* exist return DLADM_OPT_ACTIVE flag.
*/
"interface for NCU %s doesn't exist",
return;
}
/*
* If NCU is being initialized (rather than refreshed), the
* object_state is INITIALIZED (from nwamd_object_init()).
*/
/*
* If the NCU is disabled, initial state should be DISABLED.
*
* Otherwise, the initial state will be
* OFFLINE/CONDITIONS_NOT_MET, and the link selection
* algorithm will do the rest.
*/
if (!ncu->ncu_enabled) {
} else {
}
} else {
/*
* Refresh NCU. Deal with disabled cases first, moving NCUs
* that are not disabled - but have the enabled value set - to
* the disabled state. Then handle cases where the NCU was
* disabled but is no longer. Finally, deal with refresh of
* link and interface NCUs, as these are handled differently.
*/
if (!ncu->ncu_enabled) {
}
goto done;
} else {
int64_t c;
/*
* Try to activate the NCU if manual or
* prioritized (when priority <= current).
*/
(void) pthread_mutex_lock(&active_ncp_mutex);
(void) pthread_mutex_unlock(&active_ncp_mutex);
if (link->nwamd_link_activation_mode ==
link->nwamd_link_priority_mode <= c)) {
} else {
}
goto done;
}
}
switch (type) {
case NWAM_NCU_TYPE_LINK:
/*
* Do rescan. If the current state and the
* active priority-group do not allow wireless
* network selection, then it won't happen.
*/
}
break;
case NWAM_NCU_TYPE_INTERFACE:
/*
* If interface NCU is offline*, online or in
* maintenance, mark it down (from there, it will be
* reinitialized to reapply addresses).
*/
} else {
}
break;
}
}
done:
if (type == NWAM_NCU_TYPE_LINK &&
NWAM_OBJECT_TYPE_NCP, NULL)) {
}
}
void
{
/*
* Simulate a state event so that the state machine can correctly
* disable the NCU. Then free up allocated objects.
*/
if (state_event == NULL) {
return;
}
return;
}
}
void
{
(void) pthread_mutex_lock(&active_ncp_mutex);
active_ncp) != 0) {
"inactive NCP %s, nothing to do",
(void) pthread_mutex_unlock(&active_ncp_mutex);
return;
}
(void) pthread_mutex_unlock(&active_ncp_mutex);
case NWAM_ACTION_ENABLE:
return;
}
"ncu %s already online, nothing to do",
return;
}
break;
case NWAM_ACTION_DISABLE:
return;
}
"ncu %s already disabled, nothing to do",
return;
}
break;
case NWAM_ACTION_ADD:
case NWAM_ACTION_REFRESH:
break;
case NWAM_ACTION_DESTROY:
break;
default:
"unexpected action");
break;
}
}
void
{
return;
}
/*
* For NCU state changes, we need to supply the parent NCP name also,
* regardless of whether the event is handled or not. It is best to
* fill this in here as we have the object lock - when we create
* object state events we sometimes do not have the object lock, but
* at this point in consuming the events (and prior to the associated
* event message being sent out) we do.
*/
/*
* If we receive a state change event moving this NCU to
* DHCP_TIMED_OUT or UP state but this NCU is already ONLINE, then
* ignore this state change event.
*/
if ((new_aux_state == NWAM_AUX_STATE_IF_DHCP_TIMED_OUT ||
new_aux_state == NWAM_AUX_STATE_UP) &&
"NCU %s already online, not going to '%s' state",
return;
}
"NCU %s already in state (%s, %s)",
return;
}
if (old_state == NWAM_STATE_MAINTENANCE &&
(new_state == NWAM_STATE_ONLINE ||
"NCU %s cannot transition from state %s to state (%s, %s)",
return;
}
if (is_link)
/*
* State machine for NCUs
*/
switch (new_state) {
if (enabled) {
} else {
"cannot move disabled NCU %s online",
}
break;
break;
case NWAM_STATE_ONLINE:
/*
* We usually don't need to do anything when we're in the
* ONLINE state. However, for WiFi we can be in INIT or
* SCAN aux states while being ONLINE.
*/
break;
case NWAM_STATE_OFFLINE:
/* Reassess priority group now member is offline */
if (prioritized) {
}
break;
case NWAM_STATE_DISABLED:
case NWAM_STATE_UNINITIALIZED:
case NWAM_STATE_MAINTENANCE:
case NWAM_STATE_DEGRADED:
default:
/* do nothing */
break;
}
if (is_link) {
if ((new_state == NWAM_STATE_ONLINE_TO_OFFLINE &&
new_state == NWAM_STATE_DISABLED) {
/*
* Going offline, propogate down event to IP NCU. Do
* not propogate event if new aux state is uninitialized
* or not found as these auxiliary states signify
*/
}
if (new_state == NWAM_STATE_ONLINE) {
/* gone online, propogate up event to IP NCU */
}
} else {
/* If IP NCU is online, reasses priority group */
if (new_state == NWAM_STATE_ONLINE)
}
}