/* * 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 * or http://www.opensolaris.org/os/licensing. * 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 */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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. */ #define VBOX_IFACE_PREFIX "vboxnet" /* * Find ncu of specified type for link/interface name. */ nwamd_object_t nwamd_ncu_object_find(nwam_ncu_type_t type, const char *name) { nwam_error_t err; char *object_name; nwamd_object_t ncu_obj = NULL; if ((err = nwam_ncu_name_to_typed_name(name, type, &object_name)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_ncu_find: nwam_ncu_name_to_typed_name " "returned %s", nwam_strerror(err)); return (NULL); } ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, object_name); free(object_name); return (ncu_obj); } nwam_error_t nwamd_set_ncu_string(nwam_ncu_handle_t ncuh, char **strval, uint_t cnt, const char *prop) { nwam_error_t err; nwam_value_t val; if ((err = nwam_value_create_string_array(strval, cnt, &val)) != NWAM_SUCCESS) return (err); err = nwam_ncu_set_prop_value(ncuh, prop, val); nwam_value_free(val); return (err); } nwam_error_t nwamd_set_ncu_uint(nwam_ncu_handle_t ncuh, uint64_t *uintval, uint_t cnt, const char *prop) { nwam_error_t err; nwam_value_t val; if ((err = nwam_value_create_uint64_array(uintval, cnt, &val)) != NWAM_SUCCESS) return (err); err = nwam_ncu_set_prop_value(ncuh, prop, val); nwam_value_free(val); return (err); } nwam_error_t nwamd_get_ncu_string(nwam_ncu_handle_t ncuh, nwam_value_t *val, char ***strval, uint_t *cnt, const char *prop) { nwam_error_t err; if ((err = nwam_ncu_get_prop_value(ncuh, prop, val)) != NWAM_SUCCESS) return (err); return (nwam_value_get_string_array(*val, strval, cnt)); } nwam_error_t nwamd_get_ncu_uint(nwam_ncu_handle_t ncuh, nwam_value_t *val, uint64_t **uintval, uint_t *cnt, const char *prop) { nwam_error_t err; if ((err = nwam_ncu_get_prop_value(ncuh, prop, val)) != NWAM_SUCCESS) return (err); return (nwam_value_get_uint64_array(*val, uintval, cnt)); } /* * Run link/interface state machine in response to a state change * or enable/disable action event. */ static void nwamd_ncu_state_machine(const char *object_name) { nwamd_object_t object; nwamd_ncu_t *ncu; link_state_t link_state; nwamd_event_t event; nwam_wlan_t key_wlan, connected_wlan; nwamd_link_t *link; char linkname[NWAM_MAX_NAME_LEN]; boolean_t up; if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, object_name)) == NULL) { nlog(LOG_ERR, "nwamd_ncu_state_machine: " "request for nonexistent NCU %s", object_name); return; } ncu = object->nwamd_object_data; link = &ncu->ncu_node.u_link; switch (object->nwamd_object_aux_state) { case NWAM_AUX_STATE_INITIALIZED: if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) { /* * For wired/wireless links, need to get link * up/down events and even if these are not supported, * dlpi_open()ing the link prevents the driver from * being unloaded. */ nwamd_dlpi_add_link(object); if (link->nwamd_link_media == DL_WIFI) { /* * First, if we're unexpectedly connected, * disconnect. */ if (!link->nwamd_link_wifi_connected && nwamd_wlan_connected(object)) { nlog(LOG_DEBUG, "nwamd_ncu_state_machine: " "WiFi unexpectedly connected, " "disconnecting..."); (void) dladm_wlan_disconnect(dld_handle, link->nwamd_link_id); nwamd_set_selected_connected(ncu, B_FALSE, B_FALSE); } /* move to scanning aux state */ nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, object->nwamd_object_state, NWAM_AUX_STATE_LINK_WIFI_SCANNING); } 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. */ link_state = nwamd_get_link_state (ncu->ncu_name); if (link_state == LINK_STATE_UP || link_state == LINK_STATE_UNKNOWN) { nwamd_object_set_state (NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_ONLINE, NWAM_AUX_STATE_UP); } else { nwamd_object_set_state (NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_DOWN); } } } 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. */ nwamd_unplumb_interface(ncu, 0, AF_INET); nwamd_unplumb_interface(ncu, 0, AF_INET6); /* * 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. */ nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_IF_WAITING_FOR_ADDR); if (ncu->ncu_node.u_if.nwamd_if_ipv4) nwamd_plumb_interface(ncu, 0, AF_INET); if (ncu->ncu_node.u_if.nwamd_if_ipv6) nwamd_plumb_interface(ncu, 0, AF_INET6); /* * Configure addresses. Configure any static addresses * and start DHCP if required. If DHCP is not required, * do a DHCPINFORM to get other networking config * parameters. RTM_NEWADDRs - translated into IF_STATE * events - will then finish the job of bringing us * online. */ nwamd_configure_interface_addresses(ncu); if (ncu->ncu_node.u_if.nwamd_if_dhcp_requested) nwamd_start_dhcp(ncu); else nwamd_dhcp_inform(ncu); } break; case NWAM_AUX_STATE_IF_DHCP_TIMED_OUT: case NWAM_AUX_STATE_IF_WAITING_FOR_ADDR: /* * nothing to do here - RTM_NEWADDRs will trigger IF_STATE * events to move us online. */ break; case NWAM_AUX_STATE_LINK_WIFI_SCANNING: /* launch scan thread */ (void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname)); (void) nwamd_wlan_scan(linkname); /* Create periodic scan event */ nwamd_ncu_create_periodic_scan_event(object); break; case NWAM_AUX_STATE_LINK_WIFI_NEED_SELECTION: /* send "need choice" event */ event = nwamd_event_init_wlan (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_NEED_CHOICE, B_FALSE, link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr, link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr_num); if (event == NULL) break; nwamd_event_enqueue(event); nwamd_set_selected_connected(ncu, B_FALSE, B_FALSE); break; case NWAM_AUX_STATE_LINK_WIFI_NEED_KEY: /* * 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. */ bzero(&key_wlan, sizeof (key_wlan)); (void) strlcpy(key_wlan.nww_essid, link->nwamd_link_wifi_essid, sizeof (key_wlan.nww_essid)); (void) strlcpy(key_wlan.nww_bssid, link->nwamd_link_wifi_bssid, sizeof (key_wlan.nww_bssid)); key_wlan.nww_security_mode = link->nwamd_link_wifi_security_mode; key_wlan.nww_selected = B_TRUE; key_wlan.nww_connected = B_FALSE; key_wlan.nww_have_key = B_FALSE; event = nwamd_event_init_wlan (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_NEED_KEY, B_FALSE, &key_wlan, 1); if (event == NULL) break; nwamd_event_enqueue(event); break; case NWAM_AUX_STATE_LINK_WIFI_CONNECTING: (void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname)); nwamd_wlan_connect(linkname); break; case NWAM_AUX_STATE_UP: case NWAM_AUX_STATE_DOWN: up = (object->nwamd_object_aux_state == NWAM_AUX_STATE_UP); if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) { if (link->nwamd_link_media == DL_WIFI) { /* * Connected/disconnected - send WLAN * connection report. */ link->nwamd_link_wifi_connected = up; nwamd_set_selected_connected(ncu, B_TRUE, up); (void) strlcpy(connected_wlan.nww_essid, link->nwamd_link_wifi_essid, sizeof (connected_wlan.nww_essid)); (void) strlcpy(connected_wlan.nww_bssid, link->nwamd_link_wifi_bssid, sizeof (connected_wlan.nww_bssid)); connected_wlan.nww_security_mode = link->nwamd_link_wifi_security_mode; event = nwamd_event_init_wlan (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_CONNECTION_REPORT, up, &connected_wlan, 1); if (event == NULL) break; nwamd_event_enqueue(event); /* * If disconnected, restart the state machine * for the WiFi link (WiFi is always trying * to connect). * * If connected, start signal strength * monitoring thread. */ if (!up && ncu->ncu_enabled) { nlog(LOG_DEBUG, "nwamd_ncu_state_machine: " "wifi disconnect - start over " "after %dsec interval", WIRELESS_RETRY_INTERVAL); link->nwamd_link_wifi_connected = B_FALSE; /* propogate down event to IP NCU */ nwamd_propogate_link_up_down_to_ip (ncu->ncu_name, B_FALSE); nwamd_object_set_state_timed (NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED, WIRELESS_RETRY_INTERVAL); } else { nlog(LOG_DEBUG, "nwamd_ncu_state_machine: " "wifi connected, start monitoring"); (void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname)); nwamd_wlan_monitor_signal(linkname); } } } /* If not in ONLINE/OFFLINE state yet, change state */ if ((up && object->nwamd_object_state != NWAM_STATE_ONLINE) || (!up && object->nwamd_object_state != NWAM_STATE_OFFLINE)) { nlog(LOG_DEBUG, "nwamd_ncu_state_machine: " "%s is moving %s", object_name, up ? "online" : "offline"); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, up ? NWAM_STATE_ONLINE : NWAM_STATE_OFFLINE, up ? NWAM_AUX_STATE_UP : NWAM_AUX_STATE_DOWN); if (ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE) { if (up) { /* * Moving online, add v4/v6 default * routes (if any). */ nwamd_add_default_routes(ncu); } 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. */ nwamd_object_set_state (NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); } } } else { nlog(LOG_DEBUG, "nwamd_ncu_state_machine: " "%s is %s", object_name, up ? "online" : "offline"); } /* * 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. */ nwamd_create_triggered_condition_check_event(NEXT_FEW_SECONDS); break; case NWAM_AUX_STATE_CONDITIONS_NOT_MET: /* * Link/interface is moving offline. Nothing to do except * for WiFi, where we disconnect. Don't unplumb IP on * a link since it may be a transient change. */ if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) { if (link->nwamd_link_media == DL_WIFI) { (void) dladm_wlan_disconnect(dld_handle, link->nwamd_link_id); link->nwamd_link_wifi_connected = B_FALSE; nwamd_set_selected_connected(ncu, B_FALSE, 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). */ nwamd_unplumb_interface(ncu, 0, AF_INET); nwamd_unplumb_interface(ncu, 0, AF_INET6); } if (object->nwamd_object_state != NWAM_STATE_OFFLINE) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET); } break; case NWAM_AUX_STATE_MANUAL_DISABLE: /* Manual disable, set enabled state appropriately. */ ncu->ncu_enabled = B_FALSE; /* FALLTHROUGH */ case NWAM_AUX_STATE_UNINITIALIZED: case NWAM_AUX_STATE_NOT_FOUND: /* * Link/interface NCU has been disabled/deactivated/removed. * For WiFi links disconnect, and for IP interfaces we unplumb. */ if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) { if (link->nwamd_link_media == DL_WIFI) { (void) dladm_wlan_disconnect(dld_handle, link->nwamd_link_id); link->nwamd_link_wifi_connected = B_FALSE; nwamd_set_selected_connected(ncu, B_FALSE, B_FALSE); } nwamd_dlpi_delete_link(object); } else { /* Unplumb here. */ if (ncu->ncu_node.u_if.nwamd_if_ipv4) { nwamd_unplumb_interface(ncu, 0, AF_INET); } if (ncu->ncu_node.u_if.nwamd_if_ipv6) { nwamd_unplumb_interface(ncu, 0, AF_INET6); } /* trigger location condition checking */ nwamd_create_triggered_condition_check_event(0); } switch (object->nwamd_object_aux_state) { case NWAM_AUX_STATE_MANUAL_DISABLE: /* Change state to DISABLED if manually disabled */ nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_DISABLED, NWAM_AUX_STATE_MANUAL_DISABLE); /* Note that NCU has been disabled */ ncu->ncu_enabled = B_FALSE; break; case NWAM_AUX_STATE_NOT_FOUND: /* Change state to UNINITIALIZED for device removal */ nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_UNINITIALIZED, NWAM_AUX_STATE_NOT_FOUND); break; default: break; } break; default: nlog(LOG_ERR, "nwamd_ncu_state_machine: unexpected state"); break; } nwamd_object_release(object); } static int ncu_create_init_fini_event(nwam_ncu_handle_t ncuh, void *data) { boolean_t *init = data; char *name, *typedname; nwam_error_t err; nwam_value_t typeval = NULL; uint64_t *type; uint_t numvalues; nwamd_event_t ncu_event; if (nwam_ncu_get_name(ncuh, &name) != NWAM_SUCCESS) { nlog(LOG_ERR, "ncu_create_init_fini_event: could not get NCU name"); return (0); } nlog(LOG_DEBUG, "ncu_create_init_fini_event(%s, %p)", name, data); if ((err = nwamd_get_ncu_uint(ncuh, &typeval, &type, &numvalues, NWAM_NCU_PROP_TYPE)) != NWAM_SUCCESS) { nlog(LOG_ERR, "ncu_create_init_fini_event: " "could not get NCU type: %s", nwam_strerror(err)); free(name); nwam_value_free(typeval); return (0); } /* convert name to typedname for event */ if ((err = nwam_ncu_name_to_typed_name(name, *type, &typedname)) != NWAM_SUCCESS) { nlog(LOG_ERR, "ncu_create_init_fini_event: " "NCU name translation failed: %s", nwam_strerror(err)); free(name); return (0); } free(name); nwam_value_free(typeval); ncu_event = nwamd_event_init(*init ? NWAM_EVENT_TYPE_OBJECT_INIT : NWAM_EVENT_TYPE_OBJECT_FINI, NWAM_OBJECT_TYPE_NCU, 0, typedname); if (ncu_event != NULL) nwamd_event_enqueue(ncu_event); free(typedname); 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) { boolean_t init = B_TRUE; (void) pthread_mutex_lock(&active_ncp_mutex); if (active_ncph != NULL) { nlog(LOG_DEBUG, "nwamd_init_ncus: " "(re)intializing NCUs for NCP %s", active_ncp); (void) nwam_ncp_walk_ncus(active_ncph, ncu_create_init_fini_event, &init, NWAM_FLAG_NCU_TYPE_ALL, NULL); } (void) pthread_mutex_unlock(&active_ncp_mutex); } void nwamd_fini_ncus(void) { boolean_t init = B_FALSE; /* We may not have an active NCP on initialization, so skip fini */ (void) pthread_mutex_lock(&active_ncp_mutex); if (active_ncph != NULL) { nlog(LOG_DEBUG, "nwamd_fini_ncus: deinitializing NCUs for %s", active_ncp); (void) nwam_ncp_walk_ncus(active_ncph, ncu_create_init_fini_event, &init, NWAM_FLAG_NCU_TYPE_ALL, 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 populate_common_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data) { nwam_value_t ncu_prop; nwam_error_t err; boolean_t enablevalue; uint_t numvalues; char **parent; if ((err = nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_ENABLED, &ncu_prop)) != NWAM_SUCCESS) { char *name; (void) nwam_ncu_name_to_typed_name(ncu_data->ncu_name, ncu_data->ncu_type, &name); nlog(LOG_ERR, "nwam_ncu_get_prop_value %s ENABLED failed: %s", name, nwam_strerror(err)); free(name); ncu_data->ncu_enabled = B_TRUE; } else { if ((err = nwam_value_get_boolean(ncu_prop, &enablevalue)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwam_value_get_boolean ENABLED failed: " "%s", nwam_strerror(err)); } else { ncu_data->ncu_enabled = enablevalue; } nwam_value_free(ncu_prop); } if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &parent, &numvalues, NWAM_NCU_PROP_PARENT_NCP)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwam_ncu_get_prop_value %s PARENT failed: %s", ncu_data->ncu_name, nwam_strerror(err)); } else { (void) strlcpy(ncu_data->ncu_parent, parent[0], sizeof (ncu_data->ncu_parent)); nwam_value_free(ncu_prop); } } /* * Read in link properties. */ static void populate_link_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data) { nwam_value_t ncu_prop; nwam_error_t err; char **mac_addr; uint64_t *uintval; uint_t numvalues; /* activation-mode */ if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues, NWAM_NCU_PROP_ACTIVATION_MODE)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_link_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_ACTIVATION_MODE, nwam_strerror(err)); } else { ncu_data->ncu_node.u_link.nwamd_link_activation_mode = uintval[0]; nwam_value_free(ncu_prop); } /* priority-group and priority-mode for prioritized activation */ if (ncu_data->ncu_node.u_link.nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_PRIORITIZED) { /* ncus with prioritized activation are always enabled */ ncu_data->ncu_enabled = B_TRUE; if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues, NWAM_NCU_PROP_PRIORITY_MODE)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_link_ncu_properties: " "could not get %s value: %s", NWAM_NCU_PROP_PRIORITY_MODE, nwam_strerror(err)); } else { ncu_data->ncu_node.u_link.nwamd_link_priority_mode = uintval[0]; nwam_value_free(ncu_prop); } if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues, NWAM_NCU_PROP_PRIORITY_GROUP)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_link_ncu_properties: " "could not get %s value: %s", NWAM_NCU_PROP_PRIORITY_GROUP, nwam_strerror(err)); } else { ncu_data->ncu_node.u_link.nwamd_link_priority_group = uintval[0]; nwam_value_free(ncu_prop); } } /* link-mac-addr */ if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &mac_addr, &numvalues, NWAM_NCU_PROP_LINK_MAC_ADDR)) != NWAM_SUCCESS) { nlog(LOG_DEBUG, "populate_link_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_LINK_MAC_ADDR, nwam_strerror(err)); ncu_data->ncu_node.u_link.nwamd_link_mac_addr = NULL; } else { ncu_data->ncu_node.u_link.nwamd_link_mac_addr = strdup(*mac_addr); ncu_data->ncu_node.u_link.nwamd_link_mac_addr_len = strlen(*mac_addr); nwam_value_free(ncu_prop); } /* link-mtu */ if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues, NWAM_NCU_PROP_LINK_MTU)) != NWAM_SUCCESS) { nlog(LOG_DEBUG, "populate_link_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_LINK_MTU, nwam_strerror(err)); ncu_data->ncu_node.u_link.nwamd_link_mtu = 0; } else { ncu_data->ncu_node.u_link.nwamd_link_mtu = uintval[0]; nwam_value_free(ncu_prop); } /* link-autopush */ if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &ncu_data->ncu_node.u_link.nwamd_link_autopush, &ncu_data->ncu_node.u_link.nwamd_link_num_autopush, NWAM_NCU_PROP_LINK_AUTOPUSH)) != NWAM_SUCCESS) { nlog(LOG_DEBUG, "populate_link_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_LINK_AUTOPUSH, nwam_strerror(err)); ncu_data->ncu_node.u_link.nwamd_link_num_autopush = 0; } } static void populate_ip_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data) { nwamd_if_t *nif = &ncu_data->ncu_node.u_if; struct nwamd_if_address **nifa, *nifai, *nifait; char *prefix; boolean_t static_addr = B_FALSE; uint64_t *addrsrcvalue; nwam_value_t ncu_prop; nwam_error_t err; char **addrvalue; uint_t numvalues; uint64_t *ipversion; int i; nif->nwamd_if_ipv4 = B_FALSE; nif->nwamd_if_ipv6 = B_FALSE; nif->nwamd_if_dhcp_requested = B_FALSE; nif->nwamd_if_stateful_requested = B_FALSE; nif->nwamd_if_stateless_requested = B_FALSE; nif->nwamd_if_ipv4_default_route_set = B_FALSE; nif->nwamd_if_ipv6_default_route_set = B_FALSE; /* ip-version */ if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &ipversion, &numvalues, NWAM_NCU_PROP_IP_VERSION)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_IP_VERSION, nwam_strerror(err)); } else { for (i = 0; i < numvalues; i++) { switch (ipversion[i]) { case IPV4_VERSION: nif->nwamd_if_ipv4 = B_TRUE; break; case IPV6_VERSION: nif->nwamd_if_ipv6 = B_TRUE; break; default: nlog(LOG_ERR, "bogus ip version %lld", ipversion[i]); break; } } nwam_value_free(ncu_prop); } /* Free the old list. */ for (nifai = nif->nwamd_if_list; nifai != NULL; nifai = nifait) { nifait = nifai->next; nifai->next = NULL; free(nifai); } nif->nwamd_if_list = NULL; nifa = &(nif->nwamd_if_list); if (!nif->nwamd_if_ipv4) goto skip_ipv4; /* ipv4-addrsrc */ if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &addrsrcvalue, &numvalues, NWAM_NCU_PROP_IPV4_ADDRSRC)) != NWAM_SUCCESS) { nlog(nif->nwamd_if_ipv4 ? LOG_ERR : LOG_DEBUG, "populate_ip_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_IPV4_ADDRSRC, nwam_strerror(err)); } else { for (i = 0; i < numvalues; i++) { switch (addrsrcvalue[i]) { case NWAM_ADDRSRC_DHCP: nif->nwamd_if_dhcp_requested = B_TRUE; break; case NWAM_ADDRSRC_STATIC: static_addr = B_TRUE; break; default: break; } } nwam_value_free(ncu_prop); } if (nif->nwamd_if_dhcp_requested) { if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) { (*nifa)->address.sa_family = AF_INET; (*nifa)->dhcp_if = B_TRUE; nifa = &((*nifa)->next); *nifa = NULL; } } /* ipv4-addr */ if (static_addr) { if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue, &numvalues, NWAM_NCU_PROP_IPV4_ADDR)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "could not get %s value; %s", NWAM_NCU_PROP_IPV4_ADDR, nwam_strerror(err)); } else { struct sockaddr_in *s; for (i = 0; i < numvalues; i++) { if ((*nifa = calloc(sizeof (**nifa), 1)) == NULL) { nlog(LOG_ERR, "couldn't allocate nwamd" "address"); continue; } (*nifa)->address.sa_family = AF_INET; /*LINTED*/ s = (struct sockaddr_in *)&(*nifa)->address; s->sin_family = AF_INET; s->sin_port = 0; prefix = strchr(addrvalue[i], '/'); if (prefix != NULL) { *prefix++ = 0; (*nifa)->prefix = atoi(prefix); } (void) inet_pton(AF_INET, addrvalue[i], &(s->sin_addr)); nifa = &((*nifa)->next); } *nifa = NULL; nwam_value_free(ncu_prop); } } /* get default route, if any */ if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue, &numvalues, NWAM_NCU_PROP_IPV4_DEFAULT_ROUTE)) == NWAM_SUCCESS) { /* Only one default route is allowed. */ nif->nwamd_if_ipv4_default_route.sin_family = AF_INET; (void) inet_pton(AF_INET, addrvalue[0], &(nif->nwamd_if_ipv4_default_route.sin_addr)); nif->nwamd_if_ipv4_default_route_set = B_TRUE; nwam_value_free(ncu_prop); } skip_ipv4: if (!nif->nwamd_if_ipv6) goto skip_ipv6; /* ipv6-addrsrc */ static_addr = B_FALSE; if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &addrsrcvalue, &numvalues, NWAM_NCU_PROP_IPV6_ADDRSRC)) != NWAM_SUCCESS) { nlog(nif->nwamd_if_ipv6 ? LOG_ERR : LOG_DEBUG, "populate_ip_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_IPV6_ADDRSRC, nwam_strerror(err)); } else { for (i = 0; i < numvalues; i++) { switch (addrsrcvalue[i]) { case NWAM_ADDRSRC_DHCP: nif->nwamd_if_stateful_requested = B_TRUE; break; case NWAM_ADDRSRC_AUTOCONF: nif->nwamd_if_stateless_requested = B_TRUE; break; case NWAM_ADDRSRC_STATIC: static_addr = B_TRUE; break; default: break; } } nwam_value_free(ncu_prop); } if (nif->nwamd_if_stateful_requested) { if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) { (*nifa)->address.sa_family = AF_INET6; (*nifa)->dhcp_if = B_TRUE; nifa = &((*nifa)->next); *nifa = NULL; } } if (nif->nwamd_if_stateless_requested) { if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) { (*nifa)->address.sa_family = AF_INET6; (*nifa)->stateless_if = B_TRUE; nifa = &((*nifa)->next); *nifa = NULL; } } /* ipv6-addr */ if (static_addr) { if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue, &numvalues, NWAM_NCU_PROP_IPV6_ADDR)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "could not get %s value; %s", NWAM_NCU_PROP_IPV6_ADDR, nwam_strerror(err)); } else { struct sockaddr_in6 *s; for (i = 0; i < numvalues; i++) { if ((*nifa = calloc(sizeof (**nifa), 1)) == NULL) { nlog(LOG_ERR, "couldn't allocate nwamd" "address"); continue; } (*nifa)->address.sa_family = AF_INET6; /*LINTED*/ s = (struct sockaddr_in6 *)&(*nifa)->address; s->sin6_family = AF_INET6; s->sin6_port = 0; prefix = strchr(addrvalue[i], '/'); if (prefix != NULL) { *prefix++ = 0; (*nifa)->prefix = atoi(prefix); } (void) inet_pton(AF_INET6, addrvalue[i], &(s->sin6_addr)); nifa = &((*nifa)->next); } *nifa = NULL; nwam_value_free(ncu_prop); } } /* get default route, if any */ if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue, &numvalues, NWAM_NCU_PROP_IPV6_DEFAULT_ROUTE)) == NWAM_SUCCESS) { /* Only one default route is allowed. */ nif->nwamd_if_ipv6_default_route.sin6_family = AF_INET6; (void) inet_pton(AF_INET6, addrvalue[0], &(nif->nwamd_if_ipv6_default_route.sin6_addr)); nif->nwamd_if_ipv6_default_route_set = B_TRUE; nwam_value_free(ncu_prop); } skip_ipv6: ; } static nwamd_ncu_t * nwamd_ncu_init(nwam_ncu_type_t ncu_type, const char *name) { nwamd_ncu_t *rv; nlog(LOG_DEBUG, "nwamd_ncu_init(%d, %s)", ncu_type, name); if ((rv = calloc(1, sizeof (*rv))) == NULL) return (NULL); rv->ncu_type = ncu_type; rv->ncu_name = strdup(name); rv->ncu_enabled = B_FALSE; /* Initialize link/interface-specific data */ if (rv->ncu_type == NWAM_NCU_TYPE_LINK) { (void) bzero(&rv->ncu_node.u_link, sizeof (nwamd_link_t)); (void) dladm_name2info(dld_handle, name, &rv->ncu_node.u_link.nwamd_link_id, NULL, NULL, &rv->ncu_node.u_link.nwamd_link_media); (void) pthread_mutex_init( &rv->ncu_node.u_link.nwamd_link_wifi_mutex, NULL); rv->ncu_node.u_link.nwamd_link_wifi_priority = MAXINT; } else { (void) bzero(&rv->ncu_node.u_if, sizeof (nwamd_if_t)); } return (rv); } void nwamd_ncu_free(nwamd_ncu_t *ncu) { if (ncu != NULL) { assert(ncu->ncu_type == NWAM_NCU_TYPE_LINK || ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE); if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) { struct nwamd_link *l = &ncu->ncu_node.u_link; 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]); } else if (ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE) { struct nwamd_if_address *nifa; nifa = ncu->ncu_node.u_if.nwamd_if_list; while (nifa != NULL) { struct nwamd_if_address *n; n = nifa; nifa = nifa->next; free(n); } } free(ncu->ncu_name); free(ncu); } } static int nwamd_ncu_display(nwamd_object_t ncu_obj, void *data) { nwamd_ncu_t *ncu = (nwamd_ncu_t *)ncu_obj->nwamd_object_data; data = data; nlog(LOG_DEBUG, "NCU (%p) %s state %s, %s", (void *)ncu, ncu_obj->nwamd_object_name, nwam_state_to_string(ncu_obj->nwamd_object_state), nwam_aux_state_to_string(ncu_obj->nwamd_object_aux_state)); return (0); } void nwamd_log_ncus(void) { nlog(LOG_DEBUG, "NCP %s", active_ncp); (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, nwamd_ncu_display, NULL); } int nwamd_ncu_action(const char *ncu, const char *parent, nwam_action_t action) { nwamd_event_t ncu_event = nwamd_event_init_object_action (NWAM_OBJECT_TYPE_NCU, ncu, parent, action); if (ncu_event == NULL) return (1); nwamd_event_enqueue(ncu_event); return (0); } static void add_phys_ncu_to_ncp(nwam_ncp_handle_t ncph, const char *name) { dladm_status_t dlrtn; uint32_t media; boolean_t is_wireless; nwam_error_t err; nwam_ncu_handle_t ncuh; uint64_t uintval; if ((dlrtn = dladm_name2info(dld_handle, name, NULL, NULL, NULL, &media)) != DLADM_STATUS_OK) { char errmsg[DLADM_STRSIZE]; nlog(LOG_ERR, "failed to get media type for %s: %s", name, dladm_status2str(dlrtn, errmsg)); return; } is_wireless = (media == DL_WIFI); if ((err = nwam_ncu_create(ncph, name, NWAM_NCU_TYPE_LINK, NWAM_NCU_CLASS_PHYS, &ncuh)) != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to create link ncu for %s: %s", name, nwam_strerror(err)); if (err == NWAM_ENTITY_READ_ONLY) { nwamd_event_t retry_event; /* * Root filesystem may be read-only, retry in * a few seconds. */ nlog(LOG_DEBUG, "Retrying addition of phys ncu for %s", name); retry_event = nwamd_event_init_link_action(name, NWAM_ACTION_ADD); if (retry_event != NULL) { nwamd_event_enqueue_timed(retry_event, NWAMD_READONLY_RETRY_INTERVAL); } } return; } uintval = NWAM_ACTIVATION_MODE_PRIORITIZED; if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1, NWAM_NCU_PROP_ACTIVATION_MODE)) != NWAM_SUCCESS) { goto finish; } uintval = is_wireless ? 1 : 0; if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1, NWAM_NCU_PROP_PRIORITY_GROUP)) != NWAM_SUCCESS) { goto finish; } uintval = is_wireless ? NWAM_PRIORITY_MODE_EXCLUSIVE : NWAM_PRIORITY_MODE_SHARED; if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1, NWAM_NCU_PROP_PRIORITY_MODE)) != NWAM_SUCCESS) { goto finish; } err = nwam_ncu_commit(ncuh, 0); finish: nwam_ncu_free(ncuh); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to create automatic link ncu for %s: %s", name, nwam_strerror(err)); } } static void add_ip_ncu_to_ncp(nwam_ncp_handle_t ncph, const char *name) { nwam_error_t err; nwam_ncu_handle_t ncuh; if ((err = nwam_ncu_create(ncph, name, NWAM_NCU_TYPE_INTERFACE, NWAM_NCU_CLASS_IP, &ncuh)) != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to create ip ncu for %s: %s", name, 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 */ err = nwam_ncu_commit(ncuh, 0); finish: nwam_ncu_free(ncuh); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to create ip ncu for %s: %s", name, nwam_strerror(err)); } } static void remove_ncu_from_ncp(nwam_ncp_handle_t ncph, const char *name, nwam_ncu_type_t type) { nwam_error_t err; nwam_ncu_handle_t ncuh; if ((err = nwam_ncu_read(ncph, name, type, 0, &ncuh)) != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to read automatic ncu %s: %s", name, nwam_strerror(err)); return; } err = nwam_ncu_destroy(ncuh, 0); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to delete automatic ncu %s: %s", name, 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 ncu_action_change_state(nwam_action_t action, nwam_ncu_type_t type, const char *name) { nwamd_object_t ncu_obj = NULL; nwamd_ncu_t *ncu; if ((ncu_obj = nwamd_ncu_object_find(type, name)) == NULL) return; ncu = ncu_obj->nwamd_object_data; /* * 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) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET); } else { if (ncu->ncu_enabled) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_NOT_FOUND); } else { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_UNINITIALIZED, NWAM_AUX_STATE_NOT_FOUND); } } nwamd_object_release(ncu_obj); } /* * Called with hotplug sysevent or when nwam is started and walking the * physical interfaces. Add/remove both link and interface NCUs from the * Automatic NCP. Assumes that both link and interface NCUs don't exist. */ void nwamd_ncu_handle_link_action_event(nwamd_event_t event) { nwam_ncp_handle_t ncph; nwam_ncu_type_t type; nwam_action_t action = event->event_msg->nwe_data.nwe_link_action.nwe_action; nwam_error_t err; char *name; boolean_t automatic_ncp_active = B_FALSE; if (action != NWAM_ACTION_ADD && action != NWAM_ACTION_REMOVE) { nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: " "invalid link action %s", nwam_action_to_string(action)); nwamd_event_do_not_send(event); return; } nlog(LOG_DEBUG, "nwamd_ncu_handle_link_action_event: " "link action '%s' event on %s", nwam_action_to_string(action), event->event_object[0] == 0 ? "n/a" : event->event_object); if ((err = nwam_ncu_typed_name_to_name(event->event_object, &type, &name)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: " "translation from typedname error: %s", nwam_strerror(err)); nwamd_event_do_not_send(event); return; } (void) pthread_mutex_lock(&active_ncp_mutex); if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) == 0 && active_ncph != NULL) { automatic_ncp_active = B_TRUE; } (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. */ if ((err = nwam_ncp_read(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph)) == NWAM_ENTITY_NOT_FOUND) { /* Automatic NCP doesn't exist, create it */ err = nwam_ncp_create(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph); } if (err != NWAM_SUCCESS) goto fail; /* add or remove NCUs from Automatic NCP */ if (action == NWAM_ACTION_ADD) { add_phys_ncu_to_ncp(ncph, name); add_ip_ncu_to_ncp(ncph, name); } 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(). */ remove_ncu_from_ncp(ncph, name, NWAM_NCU_TYPE_INTERFACE); remove_ncu_from_ncp(ncph, name, NWAM_NCU_TYPE_LINK); } nwam_ncp_free(ncph); /* * 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) { ncu_action_change_state(action, NWAM_NCU_TYPE_INTERFACE, name); ncu_action_change_state(action, NWAM_NCU_TYPE_LINK, name); } /* Need NCU check to evaluate state in light of added/removed NCUs */ if (!nwamd_event_enqueued(NWAM_EVENT_TYPE_NCU_CHECK, NWAM_OBJECT_TYPE_NCP, NULL)) { nwamd_create_ncu_check_event(NEXT_FEW_SECONDS); } fail: free(name); if (err != NWAM_SUCCESS) { nwamd_event_t retry_event = nwamd_event_init_link_action(name, action); if (retry_event == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: " "could not create retry event to read/create " "%s NCP", NWAM_NCP_NAME_AUTOMATIC); return; } nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: " "could not read/create %s NCP, retrying in %d seconds", NWAM_NCP_NAME_AUTOMATIC, NWAMD_READONLY_RETRY_INTERVAL); nwamd_event_enqueue_timed(retry_event, NWAMD_READONLY_RETRY_INTERVAL); } } /* * 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 { datalink_id_t linkid; boolean_t under; }; static int ncu_aggr_search(const char *name, void *data) { struct link_aggr_search_data *lasd = data; dladm_aggr_grp_attr_t ginfo; datalink_id_t linkid; int i; if (dladm_name2info(dld_handle, name, &linkid, NULL, NULL, NULL) != DLADM_STATUS_OK) return (DLADM_WALK_CONTINUE); if (dladm_aggr_info(dld_handle, linkid, &ginfo, DLADM_OPT_ACTIVE) != DLADM_STATUS_OK || ginfo.lg_nports == 0) return (DLADM_WALK_CONTINUE); for (i = 0; i < ginfo.lg_nports; i++) { if (lasd->linkid == ginfo.lg_ports[i].lp_linkid) { lasd->under = B_TRUE; return (DLADM_WALK_TERMINATE); } } free(ginfo.lg_ports); return (DLADM_WALK_CONTINUE); } static boolean_t nwamd_link_belongs_to_an_aggr(const char *name) { struct link_aggr_search_data lasd; if (dladm_name2info(dld_handle, name, &lasd.linkid, NULL, NULL, NULL) != DLADM_STATUS_OK) return (B_FALSE); lasd.under = B_FALSE; (void) dladm_walk(ncu_aggr_search, dld_handle, &lasd, DATALINK_CLASS_AGGR, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE); return (lasd.under); } /* * If NCU doesn't exist for interface with given name, enqueue a ADD * LINK_ACTION event. */ static int ncu_create_link_action_event(const char *name, void *data) { nwam_ncp_handle_t ncph = data; nwam_ncu_handle_t ncuh; nwamd_event_t link_event; /* Do not generate an event if this is a VirtualBox interface. */ if (strncmp(name, VBOX_IFACE_PREFIX, strlen(VBOX_IFACE_PREFIX)) == 0) return (DLADM_WALK_CONTINUE); /* Do not generate an event if this link belongs to another zone. */ if (!nwamd_link_belongs_to_this_zone(name)) 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. */ if (ncph != NULL && nwam_ncu_read(ncph, name, NWAM_NCU_TYPE_LINK, 0, &ncuh) == NWAM_SUCCESS) { nwam_ncu_free(ncuh); return (DLADM_WALK_CONTINUE); } nlog(LOG_DEBUG, "ncu_create_link_action_event: adding ncus for %s", name); link_event = nwamd_event_init_link_action(name, NWAM_ACTION_ADD); if (link_event != NULL) nwamd_event_enqueue(link_event); return (DLADM_WALK_CONTINUE); } /* * Check if interface exists for this NCU. If not, enqueue a REMOVE * LINK_ACTION event. */ /* ARGSUSED */ static int nwamd_destroy_ncu(nwam_ncu_handle_t ncuh, void *data) { char *name; uint32_t flags; nwamd_event_t link_event; if (nwam_ncu_get_name(ncuh, &name) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_destroy_ncu: could not get NCU name"); return (0); } /* Interfaces that exist return DLADM_OPT_ACTIVE flag */ if ((dladm_name2info(dld_handle, name, NULL, &flags, NULL, NULL) == DLADM_STATUS_OK && (flags & DLADM_OPT_ACTIVE)) && !nwamd_link_belongs_to_an_aggr(name)) { free(name); return (0); } nlog(LOG_DEBUG, "nwamd_destroy_ncu: destroying ncus for %s", name); link_event = nwamd_event_init_link_action(name, NWAM_ACTION_REMOVE); if (link_event != NULL) nwamd_event_enqueue(link_event); free(name); 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 nwamd_walk_physical_configuration(void) { nwam_ncp_handle_t ncph; (void) pthread_mutex_lock(&active_ncp_mutex); if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) == 0 && active_ncph != NULL) { ncph = active_ncph; } else { if (nwam_ncp_read(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph) != NWAM_SUCCESS) { ncph = NULL; } } /* destroy NCUs for interfaces that don't exist */ if (ncph != NULL) { (void) nwam_ncp_walk_ncus(ncph, nwamd_destroy_ncu, NULL, NWAM_FLAG_NCU_TYPE_LINK, NULL); } /* create NCUs for interfaces without NCUs */ (void) dladm_walk(ncu_create_link_action_event, dld_handle, ncph, DATALINK_CLASS_PHYS, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE); if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) != 0 || active_ncph == NULL) { nwam_ncp_free(ncph); } (void) pthread_mutex_unlock(&active_ncp_mutex); } /* * Handle NCU initialization/refresh event. */ void nwamd_ncu_handle_init_event(nwamd_event_t event) { nwamd_object_t object = NULL; nwam_ncu_handle_t ncuh; nwamd_ncu_t *ncu = NULL; nwam_error_t err; nwam_ncu_type_t type; char *name; uint32_t flags; boolean_t new = B_TRUE; nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event(%s)", event->event_object); /* Get base linkname rather than interface:linkname or link:linkname */ err = nwam_ncu_typed_name_to_name(event->event_object, &type, &name); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_ncu_handle_init_event: " "nwam_ncu_typed_name_to_name returned %s", nwam_strerror(err)); nwamd_event_do_not_send(event); return; } (void) pthread_mutex_lock(&active_ncp_mutex); if (active_ncph == NULL) { nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event: active NCP handle NULL"); nwamd_event_do_not_send(event); free(name); (void) pthread_mutex_unlock(&active_ncp_mutex); return; } err = nwam_ncu_read(active_ncph, event->event_object, type, 0, &ncuh); (void) pthread_mutex_unlock(&active_ncp_mutex); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_ncu_handle_init_event: " "could not read object '%s': %s", event->event_object, nwam_strerror(err)); free(name); nwamd_event_do_not_send(event); return; } if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object)) != NULL) new = B_FALSE; /* * 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) { ncu = nwamd_ncu_init(type, name); } else { ncu = object->nwamd_object_data; nwam_ncu_free(object->nwamd_object_handle); } populate_common_ncu_properties(ncuh, ncu); populate_link_ncu_properties(ncuh, ncu); break; case NWAM_NCU_TYPE_INTERFACE: if (!new) { nwam_ncu_free(object->nwamd_object_handle); nwamd_ncu_free(object->nwamd_object_data); } ncu = nwamd_ncu_init(type, name); populate_common_ncu_properties(ncuh, ncu); populate_ip_ncu_properties(ncuh, ncu); break; default: nlog(LOG_ERR, "unknown ncu type %d", type); free(name); nwam_ncu_free(ncuh); nwamd_event_do_not_send(event); nwamd_object_release(object); return; } if (new) { nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event: didn't find " "ncu so create it %s", name); object = nwamd_object_init(NWAM_OBJECT_TYPE_NCU, event->event_object, ncuh, ncu); } else { nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event: refreshing " "ncu %s", name); object->nwamd_object_data = ncu; object->nwamd_object_handle = ncuh; } /* * 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. */ if (dladm_name2info(dld_handle, name, NULL, &flags, NULL, NULL) != DLADM_STATUS_OK || !(flags & DLADM_OPT_ACTIVE)) { nlog(LOG_DEBUG, "nwam_ncu_handle_init_event: " "interface for NCU %s doesn't exist", event->event_object); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object->nwamd_object_name, NWAM_STATE_UNINITIALIZED, NWAM_AUX_STATE_NOT_FOUND); free(name); nwamd_object_release(object); return; } /* * If NCU is being initialized (rather than refreshed), the * object_state is INITIALIZED (from nwamd_object_init()). */ if (object->nwamd_object_state == NWAM_STATE_INITIALIZED) { /* * 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) { object->nwamd_object_state = NWAM_STATE_DISABLED; object->nwamd_object_aux_state = NWAM_AUX_STATE_MANUAL_DISABLE; } else { object->nwamd_object_state = NWAM_STATE_OFFLINE; object->nwamd_object_aux_state = NWAM_AUX_STATE_CONDITIONS_NOT_MET; } } else { nwamd_link_t *link = &ncu->ncu_node.u_link; /* * 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) { if (object->nwamd_object_state != NWAM_STATE_DISABLED) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object->nwamd_object_name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_MANUAL_DISABLE); } goto done; } else { if (object->nwamd_object_state == NWAM_STATE_DISABLED) { int64_t c; /* * Try to activate the NCU if manual or * prioritized (when priority <= current). */ (void) pthread_mutex_lock(&active_ncp_mutex); c = current_ncu_priority_group; (void) pthread_mutex_unlock(&active_ncp_mutex); if (link->nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_MANUAL || (link->nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_PRIORITIZED && link->nwamd_link_priority_mode <= c)) { nwamd_object_set_state (NWAM_OBJECT_TYPE_NCU, object->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); } else { nwamd_object_set_state (NWAM_OBJECT_TYPE_NCU, object->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); } goto done; } } switch (type) { case NWAM_NCU_TYPE_LINK: if (ncu->ncu_node.u_link.nwamd_link_media == DL_WIFI) { /* * Do rescan. If the current state and the * active priority-group do not allow wireless * network selection, then it won't happen. */ (void) nwamd_wlan_scan(ncu->ncu_name); } 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). */ if (object->nwamd_object_state != NWAM_STATE_OFFLINE) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object->nwamd_object_name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_DOWN); } else { object->nwamd_object_state = NWAM_STATE_OFFLINE; object->nwamd_object_aux_state = NWAM_AUX_STATE_CONDITIONS_NOT_MET; } break; } } done: if (type == NWAM_NCU_TYPE_LINK && !nwamd_event_enqueued(NWAM_EVENT_TYPE_NCU_CHECK, NWAM_OBJECT_TYPE_NCP, NULL)) { nwamd_create_ncu_check_event(NEXT_FEW_SECONDS); } free(name); nwamd_object_release(object); } void nwamd_ncu_handle_fini_event(nwamd_event_t event) { nwamd_object_t object; nwamd_event_t state_event; nlog(LOG_DEBUG, "nwamd_ncu_handle_fini_event(%s)", event->event_object); /* * Simulate a state event so that the state machine can correctly * disable the NCU. Then free up allocated objects. */ state_event = nwamd_event_init_object_state(NWAM_OBJECT_TYPE_NCU, event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_UNINITIALIZED); if (state_event == NULL) { nwamd_event_do_not_send(event); return; } nwamd_ncu_handle_state_event(state_event); nwamd_event_fini(state_event); if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object)) == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_fini_event: " "ncu %s not found", event->event_object); nwamd_event_do_not_send(event); return; } nwamd_object_release_and_destroy(object); } void nwamd_ncu_handle_action_event(nwamd_event_t event) { nwamd_object_t object; (void) pthread_mutex_lock(&active_ncp_mutex); if (strcmp(event->event_msg->nwe_data.nwe_object_action.nwe_parent, active_ncp) != 0) { nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: action for " "inactive NCP %s, nothing to do", event->event_msg->nwe_data.nwe_object_action.nwe_parent); (void) pthread_mutex_unlock(&active_ncp_mutex); return; } (void) pthread_mutex_unlock(&active_ncp_mutex); switch (event->event_msg->nwe_data.nwe_object_action.nwe_action) { case NWAM_ACTION_ENABLE: object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object); if (object == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_action_event: " "could not find ncu %s", event->event_object); nwamd_event_do_not_send(event); return; } if (object->nwamd_object_state == NWAM_STATE_ONLINE) { nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: " "ncu %s already online, nothing to do", event->event_object); nwamd_object_release(object); return; } nwamd_object_release(object); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); break; case NWAM_ACTION_DISABLE: object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object); if (object == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_action_event: " "could not find ncu %s", event->event_object); nwamd_event_do_not_send(event); return; } if (object->nwamd_object_state == NWAM_STATE_DISABLED) { nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: " "ncu %s already disabled, nothing to do", event->event_object); nwamd_object_release(object); return; } nwamd_object_release(object); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_MANUAL_DISABLE); break; case NWAM_ACTION_ADD: case NWAM_ACTION_REFRESH: nwamd_ncu_handle_init_event(event); break; case NWAM_ACTION_DESTROY: nwamd_ncu_handle_fini_event(event); break; default: nlog(LOG_INFO, "nwam_ncu_handle_action_event: " "unexpected action"); nwamd_event_do_not_send(event); break; } } void nwamd_ncu_handle_state_event(nwamd_event_t event) { nwamd_object_t object; nwam_state_t old_state, new_state; nwam_aux_state_t new_aux_state; nwamd_ncu_t *ncu; boolean_t is_link, enabled, prioritized = B_FALSE; char linkname[NWAM_MAX_NAME_LEN]; nwam_event_t m = event->event_msg; if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object)) == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_state_event %lld: " "state event for nonexistent NCU %s", event->event_id, event->event_object); nwamd_event_do_not_send(event); return; } ncu = object->nwamd_object_data; old_state = object->nwamd_object_state; new_state = event->event_msg->nwe_data.nwe_object_state.nwe_state; new_aux_state = event->event_msg->nwe_data.nwe_object_state.nwe_aux_state; /* * 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. */ (void) strlcpy(m->nwe_data.nwe_object_state.nwe_parent, ncu->ncu_parent, sizeof (m->nwe_data.nwe_object_state.nwe_parent)); /* * 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) && object->nwamd_object_state == NWAM_STATE_ONLINE) { nlog(LOG_INFO, "nwamd_ncu_handle_state_event: " "NCU %s already online, not going to '%s' state", object->nwamd_object_name, nwam_aux_state_to_string(new_aux_state)); nwamd_event_do_not_send(event); nwamd_object_release(object); return; } if (new_state == object->nwamd_object_state && new_aux_state == object->nwamd_object_aux_state) { nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: " "NCU %s already in state (%s, %s)", object->nwamd_object_name, nwam_state_to_string(new_state), nwam_aux_state_to_string(new_aux_state)); nwamd_object_release(object); return; } if (old_state == NWAM_STATE_MAINTENANCE && (new_state == NWAM_STATE_ONLINE || (new_state == NWAM_STATE_OFFLINE_TO_ONLINE && new_aux_state != NWAM_AUX_STATE_INITIALIZED))) { nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: " "NCU %s cannot transition from state %s to state (%s, %s)", object->nwamd_object_name, nwam_state_to_string(old_state), nwam_state_to_string(new_state), nwam_aux_state_to_string(new_aux_state)); nwamd_event_do_not_send(event); nwamd_object_release(object); return; } object->nwamd_object_state = new_state; object->nwamd_object_aux_state = new_aux_state; nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: changing state for NCU " "%s to (%s, %s)", object->nwamd_object_name, nwam_state_to_string(object->nwamd_object_state), nwam_aux_state_to_string(object->nwamd_object_aux_state)); is_link = (ncu->ncu_type == NWAM_NCU_TYPE_LINK); if (is_link) (void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname)); prioritized = (ncu->ncu_type == NWAM_NCU_TYPE_LINK && ncu->ncu_node.u_link.nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_PRIORITIZED); enabled = ncu->ncu_enabled; nwamd_object_release(object); /* * State machine for NCUs */ switch (new_state) { case NWAM_STATE_OFFLINE_TO_ONLINE: if (enabled) { nwamd_ncu_state_machine(event->event_object); } else { nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: " "cannot move disabled NCU %s online", event->event_object); nwamd_event_do_not_send(event); } break; case NWAM_STATE_ONLINE_TO_OFFLINE: nwamd_ncu_state_machine(event->event_object); 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. */ nwamd_ncu_state_machine(event->event_object); break; case NWAM_STATE_OFFLINE: /* Reassess priority group now member is offline */ if (prioritized) { nwamd_create_ncu_check_event(0); } 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_aux_state != NWAM_AUX_STATE_UNINITIALIZED && new_aux_state != NWAM_AUX_STATE_NOT_FOUND) || 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 * that an NCP switch/device removal is in progress. */ nwamd_propogate_link_up_down_to_ip(linkname, B_FALSE); } if (new_state == NWAM_STATE_ONLINE) { /* gone online, propogate up event to IP NCU */ nwamd_propogate_link_up_down_to_ip(linkname, B_TRUE); } } else { /* If IP NCU is online, reasses priority group */ if (new_state == NWAM_STATE_ONLINE) nwamd_create_ncu_check_event(0); } }