root/usr/src/cmd/cmd-inet/lib/nwamd/loc.c
/*
 * 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 <arpa/inet.h>
#include <errno.h>
#include <inet/ip.h>
#include <libdladm.h>
#include <libdllink.h>
#include <libdlwlan.h>
#include <libscf.h>
#include <limits.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <libnwam.h>
#include "conditions.h"
#include "events.h"
#include "objects.h"
#include "util.h"

/*
 * loc.c - contains routines which handle location abstraction.
 */

pthread_mutex_t active_loc_mutex = PTHREAD_MUTEX_INITIALIZER;
char active_loc[NWAM_MAX_NAME_LEN];

static int
loc_create_init_fini_event(nwam_loc_handle_t loch, void *data)
{
        boolean_t *init = data;
        char *name;
        nwamd_event_t event;

        if (nwam_loc_get_name(loch, &name) != NWAM_SUCCESS) {
                nlog(LOG_ERR, "loc_init_fini: could not get loc name");
                return (0);
        }

        event = nwamd_event_init(*init ?
            NWAM_EVENT_TYPE_OBJECT_INIT : NWAM_EVENT_TYPE_OBJECT_FINI,
            NWAM_OBJECT_TYPE_LOC, 0, name);
        if (event != NULL)
                nwamd_event_enqueue(event);
        free(name);

        return (0);
}

/*
 * Walk all locs, creating init events for each.
 */
void
nwamd_init_locs(void)
{
        boolean_t init = B_TRUE;

        /* Unset active location */
        (void) pthread_mutex_lock(&active_loc_mutex);
        active_loc[0] = '\0';
        (void) pthread_mutex_unlock(&active_loc_mutex);
        (void) nwam_walk_locs(loc_create_init_fini_event, &init, 0, NULL);
}

/*
 * Walk all locs, creating fini events for each.
 */
void
nwamd_fini_locs(void)
{
        boolean_t init = B_FALSE;

        (void) nwam_walk_locs(loc_create_init_fini_event, &init, 0, NULL);
}

static boolean_t
loc_is_enabled(nwam_loc_handle_t loch)
{
        nwam_value_t enabledval;
        boolean_t enabled = B_FALSE;

        if (nwam_loc_get_prop_value(loch, NWAM_LOC_PROP_ENABLED,
            &enabledval) != NWAM_SUCCESS) {
                nlog(LOG_ERR, "loc_is_enabled: could not retrieve "
                    "enabled value");
                return (B_FALSE);
        }
        if (nwam_value_get_boolean(enabledval, &enabled)
            != NWAM_SUCCESS) {
                nlog(LOG_ERR, "loc_is_enabled: could not retrieve "
                    "enabled value");
                nwam_value_free(enabledval);
                return (B_FALSE);
        }
        nwam_value_free(enabledval);
        return (enabled);
}

static int64_t
loc_get_activation_mode(nwam_loc_handle_t loch)
{
        nwam_error_t err;
        uint64_t activation;
        nwam_value_t activationval;

        if (nwam_loc_get_prop_value(loch, NWAM_LOC_PROP_ACTIVATION_MODE,
            &activationval)  != NWAM_SUCCESS) {
                nlog(LOG_ERR, "loc_get_activation_mode: could not retrieve "
                    "activation mode value");
                return (-1);
        }
        err = nwam_value_get_uint64(activationval, &activation);
        nwam_value_free(activationval);
        if (err != NWAM_SUCCESS) {
                nlog(LOG_ERR, "loc_get_activation_mode: could not retrieve "
                    "activation mode value");
                return (-1);
        }

        return ((int64_t)activation);
}

/* Enables the location. */
static void
nwamd_loc_activate(const char *object_name)
{
        char *enabled;

        nlog(LOG_DEBUG, "nwamd_loc_activate: activating loc %s",
            object_name);

        /*
         * Find currently enabled location and change its state to disabled
         * if it is a manual location, or offline (if it is not).
         * Only manual locations reach disabled, since conditional and
         * system locations which are manually disabled simply revert to
         * their conditions for activation.
         */
        if ((enabled = malloc(NWAM_MAX_NAME_LEN)) != NULL &&
            nwamd_lookup_string_property(NET_LOC_FMRI, NET_LOC_PG,
            NET_LOC_SELECTED_PROP, enabled, NWAM_MAX_NAME_LEN) == 0) {
                /* Only change state if current != new */
                if (strcmp(enabled, object_name) != 0) {
                        boolean_t do_disable = B_FALSE;
                        nwamd_object_t eobj = nwamd_object_find
                            (NWAM_OBJECT_TYPE_LOC, enabled);
                        if (eobj == NULL) {
                                nlog(LOG_INFO, "nwamd_loc_activate: could not "
                                    "find old location %s", enabled);
                                goto skip_disable;
                        }
                        /*
                         * Disable if the old location was manual, since the
                         * only way a manual location can deactivate is if
                         * it is disabled.
                         */
                        do_disable =
                            (loc_get_activation_mode(eobj->nwamd_object_handle)
                            == (int64_t)NWAM_ACTIVATION_MODE_MANUAL);
                        nwamd_object_release(eobj);

                        if (do_disable) {
                                nlog(LOG_DEBUG, "nwamd_loc_activate: "
                                    "disable needed for old location %s",
                                    enabled);
                                nwamd_object_set_state
                                    (NWAM_OBJECT_TYPE_LOC, enabled,
                                    NWAM_STATE_DISABLED,
                                    NWAM_AUX_STATE_MANUAL_DISABLE);
                        } else {
                                nlog(LOG_DEBUG, "nwamd_loc_activate: "
                                    "offline needed for old location %s",
                                    enabled);
                                nwamd_object_set_state
                                    (NWAM_OBJECT_TYPE_LOC, enabled,
                                    NWAM_STATE_OFFLINE,
                                    NWAM_AUX_STATE_CONDITIONS_NOT_MET);
                        }
                }
        }
skip_disable:
        free(enabled);

        if (nwamd_set_string_property(NET_LOC_FMRI, NET_LOC_PG,
            NET_LOC_SELECTED_PROP, object_name) == 0) {
                char *state = smf_get_state(NET_LOC_FMRI);
                nlog(LOG_INFO, "nwamd_loc_activate: set %s/%s to %s; "
                    "service is in %s state", NET_LOC_PG, NET_LOC_SELECTED_PROP,
                    object_name, state == NULL ? "unknown" : state);
                free(state);
                (void) smf_restore_instance(NET_LOC_FMRI);
                if (smf_refresh_instance(NET_LOC_FMRI) == 0) {
                        (void) pthread_mutex_lock(&active_loc_mutex);
                        (void) strlcpy(active_loc, object_name,
                            sizeof (active_loc));
                        (void) pthread_mutex_unlock(&active_loc_mutex);
                        nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC,
                            object_name,
                            NWAM_STATE_ONLINE, NWAM_AUX_STATE_ACTIVE);
                } else {
                        nlog(LOG_ERR, "nwamd_loc_activate: "
                            "%s could not be refreshed", NET_LOC_FMRI);
                        nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC,
                            object_name,
                            NWAM_STATE_MAINTENANCE,
                            NWAM_AUX_STATE_METHOD_FAILED);
                }
        }
}

struct nwamd_loc_check_walk_arg {
        nwamd_object_t winning_object;
        uint64_t winning_rating;
};

/*
 * Determine which location should be activated.
 */
static int
nwamd_loc_check(nwamd_object_t object, void *data)
{
        struct nwamd_loc_check_walk_arg *wa = data;
        nwam_loc_handle_t loch = object->nwamd_object_handle;
        nwam_value_t conditionval;
        int64_t lactivation;
        uint64_t rating, activation;
        boolean_t satisfied;
        char **conditions;
        uint_t nelem;

        lactivation = loc_get_activation_mode(object->nwamd_object_handle);

        if (lactivation == -1)
                return (0);

        activation = (uint64_t)lactivation;
        switch (activation) {
        case NWAM_ACTIVATION_MODE_MANUAL:
                if (loc_is_enabled(loch)) {
                        /* Manually enabled locations should always win out. */
                        nlog(LOG_DEBUG, "nwamd_loc_check: %s is enabled",
                            object->nwamd_object_name);
                        wa->winning_object = object;
                        wa->winning_rating = UINT64_MAX;
                } else {
                        nlog(LOG_DEBUG, "nwamd_loc_check: %s is disabled",
                            object->nwamd_object_name);
                        if (object->nwamd_object_state != NWAM_STATE_DISABLED) {
                                nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC,
                                    object->nwamd_object_name,
                                    NWAM_STATE_DISABLED,
                                    NWAM_AUX_STATE_MANUAL_DISABLE);
                        }
                }

                return (0);

        case NWAM_ACTIVATION_MODE_CONDITIONAL_ANY:
        case NWAM_ACTIVATION_MODE_CONDITIONAL_ALL:
                if (loc_is_enabled(loch)) {
                        /* Manually enabled locations should always win out. */
                        nlog(LOG_DEBUG, "nwamd_loc_check: %s is enabled",
                            object->nwamd_object_name);
                        wa->winning_object = object;
                        wa->winning_rating = UINT64_MAX;
                }

                if (nwam_loc_get_prop_value(loch,
                    NWAM_LOC_PROP_CONDITIONS, &conditionval) != NWAM_SUCCESS) {
                        nlog(LOG_ERR, "nwamd_loc_check: could not retrieve "
                            "condition value");
                        return (0);
                }
                if (nwam_value_get_string_array(conditionval,
                    &conditions, &nelem) != NWAM_SUCCESS) {
                        nlog(LOG_ERR, "nwamd_loc_check: could not retrieve "
                            "condition value");
                        nwam_value_free(conditionval);
                        return (0);
                }
                satisfied = nwamd_check_conditions(activation, conditions,
                    nelem);

                if (satisfied) {
                        rating = nwamd_rate_conditions(activation,
                            conditions, nelem);
                        if (rating > wa->winning_rating) {
                                wa->winning_object = object;
                                wa->winning_rating = rating;
                        }
                }
                nwam_value_free(conditionval);
                return (0);

        case NWAM_ACTIVATION_MODE_SYSTEM:
                if (loc_is_enabled(loch)) {
                        /* Manually enabled locations should always win out. */
                        nlog(LOG_DEBUG, "nwamd_loc_check: %s is enabled",
                            object->nwamd_object_name);
                        wa->winning_object = object;
                        wa->winning_rating = UINT64_MAX;
                }

                /* Either NoNet, Automatic or Legacy location, so skip. */

                return (0);
        default:
                return (0);
        }
        /*NOTREACHED*/
        return (0);
}

static int
nwamd_ncu_online_check(nwamd_object_t object, void *data)
{
        boolean_t *online = data;
        nwamd_ncu_t *ncu_data = object->nwamd_object_data;

        if (ncu_data->ncu_type != NWAM_NCU_TYPE_INTERFACE)
                return (0);

        if (object->nwamd_object_state == NWAM_STATE_ONLINE) {
                /* An online IP NCU found, stop walk */
                *online = B_TRUE;
                return (1);
        }
        return (0);
}

void
nwamd_loc_check_conditions(void)
{
        struct nwamd_loc_check_walk_arg wa = { NULL, 0 };
        const char *winning_loc;
        boolean_t ncu_online = B_FALSE;
        boolean_t is_active;

        /*
         * Walk the NCUs to find out if at least one IP NCU is online.  If so,
         * check the activation-mode and conditions.  If not, enable the NoNet
         * location.
         */
        (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, nwamd_ncu_online_check,
            &ncu_online);

        if (!ncu_online) {
                winning_loc = NWAM_LOC_NAME_NO_NET;
        } else {
                (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_LOC, nwamd_loc_check,
                    &wa);
                if (wa.winning_object != NULL)
                        winning_loc = wa.winning_object->nwamd_object_name;
                else
                        winning_loc = NWAM_LOC_NAME_AUTOMATIC;
        }
        nlog(LOG_DEBUG, "nwamd_loc_check_conditions: winning loc is %s",
            winning_loc);

        /* If the winning location is already active, do nothing */
        (void) pthread_mutex_lock(&active_loc_mutex);
        is_active = (strcmp(active_loc, winning_loc) == 0);
        (void) pthread_mutex_unlock(&active_loc_mutex);
        if (is_active)
                return;

        nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC, winning_loc,
            NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_METHOD_RUNNING);
}

int
nwamd_loc_action(const char *loc, nwam_action_t action)
{
        nwamd_event_t event = nwamd_event_init_object_action
            (NWAM_OBJECT_TYPE_LOC, loc, NULL, action);
        if (event == NULL)
                return (1);
        nwamd_event_enqueue(event);
        return (0);
}

/*
 * Event handling functions.
 */

/* Handle loc initialization/refresh event */
void
nwamd_loc_handle_init_event(nwamd_event_t event)
{
        nwamd_object_t object;
        nwam_loc_handle_t loch;
        nwam_error_t err;
        boolean_t new_enabled, old_enabled = B_FALSE;
        nwam_state_t state;

        if ((err = nwam_loc_read(event->event_object, 0, &loch))
            != NWAM_SUCCESS) {
                nlog(LOG_ERR, "nwamd_loc_handle_init_event: could not "
                    "read object '%s': %s", event->event_object,
                    nwam_strerror(err));
                nwamd_event_do_not_send(event);
                return;
        }
        if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_LOC,
            event->event_object)) != NULL) {
                old_enabled = loc_is_enabled(object->nwamd_object_handle);
                nwam_loc_free(object->nwamd_object_handle);
                object->nwamd_object_handle = loch;
        } else {
                object = nwamd_object_init(NWAM_OBJECT_TYPE_LOC,
                    event->event_object, loch, NULL);
                object->nwamd_object_state = NWAM_STATE_OFFLINE;
                object->nwamd_object_aux_state =
                    NWAM_AUX_STATE_CONDITIONS_NOT_MET;
        }
        new_enabled = loc_is_enabled(loch);
        state = object->nwamd_object_state;
        nwamd_object_release(object);

        /*
         * If this location is ONLINE and the value of the "enabled" property
         * has not changed, then this location is getting refreshed because it
         * was committed with changes.  Change states to re-activate itself.
         * If the "enabled" property has changed, then this location is
         * getting refreshed as part of a enable/disable action and there is
         * no need to change states here.
         */
        if (state == NWAM_STATE_ONLINE && old_enabled == new_enabled) {
                nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC,
                    event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE,
                    NWAM_AUX_STATE_METHOD_RUNNING);
        }
}

/* Handle loc finish event */
void
nwamd_loc_handle_fini_event(nwamd_event_t event)
{
        nwamd_object_t object;

        nlog(LOG_DEBUG, "nwamd_loc_handle_fini_event(%s)",
            event->event_object);

        /* Don't disable the location, as this can enable the Automatic loc */
        if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_LOC,
            event->event_object)) == NULL) {
                nlog(LOG_INFO, "nwamd_loc_handle_fini_event: "
                    "loc %s not found", event->event_object);
                nwamd_event_do_not_send(event);
                return;
        }
        nwamd_object_release_and_destroy(object);
}

void
nwamd_loc_handle_action_event(nwamd_event_t event)
{
        nwamd_object_t object;

        switch (event->event_msg->nwe_data.nwe_object_action.nwe_action) {
        case NWAM_ACTION_ENABLE:
                object = nwamd_object_find(NWAM_OBJECT_TYPE_LOC,
                    event->event_object);
                if (object == NULL) {
                        nlog(LOG_ERR, "nwamd_loc_handle_action_event: "
                            "could not find location %s", event->event_object);
                        nwamd_event_do_not_send(event);
                        return;
                }
                if (object->nwamd_object_state == NWAM_STATE_ONLINE) {
                        nlog(LOG_DEBUG, "nwamd_loc_handle_action_event: "
                            "location %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_LOC,
                    event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE,
                    NWAM_AUX_STATE_METHOD_RUNNING);
                break;
        case NWAM_ACTION_DISABLE:
                object = nwamd_object_find(NWAM_OBJECT_TYPE_LOC,
                    event->event_object);
                if (object == NULL) {
                        nlog(LOG_ERR, "nwamd_loc_handle_action_event: "
                            "could not find location %s", event->event_object);
                        nwamd_event_do_not_send(event);
                        return;
                }
                if (object->nwamd_object_state == NWAM_STATE_DISABLED) {
                        nlog(LOG_DEBUG, "nwamd_loc_handle_action_event: "
                            "location %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_LOC,
                    event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE,
                    NWAM_AUX_STATE_MANUAL_DISABLE);
                break;
        case NWAM_ACTION_ADD:
        case NWAM_ACTION_REFRESH:
                nwamd_loc_handle_init_event(event);
                break;
        case NWAM_ACTION_DESTROY:
                nwamd_loc_handle_fini_event(event);
                break;
        default:
                nlog(LOG_INFO, "nwam_loc_handle_action_event: "
                    "unexpected action");
                break;
        }
}

void
nwamd_loc_handle_state_event(nwamd_event_t event)
{
        nwamd_object_t object;
        nwam_state_t new_state;
        nwam_aux_state_t new_aux_state;

        if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_LOC,
            event->event_object)) == NULL) {
                nlog(LOG_INFO, "nwamd_loc_handle_state_event: "
                    "state event for nonexistent loc %s", event->event_object);
                nwamd_event_do_not_send(event);
                return;
        }
        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;

        if (new_state == object->nwamd_object_state &&
            new_aux_state == object->nwamd_object_aux_state) {
                nlog(LOG_DEBUG, "nwamd_loc_handle_state_event: "
                    "loc %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;
        }

        object->nwamd_object_state = new_state;
        object->nwamd_object_aux_state = new_aux_state;

        nlog(LOG_DEBUG, "nwamd_loc_handle_state_event: changing state for loc "
            "%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));

        nwamd_object_release(object);

        /*
         * State machine for location.
         */
        switch (new_state) {
        case NWAM_STATE_OFFLINE_TO_ONLINE:
                nwamd_loc_activate(event->event_object);
                break;
        case NWAM_STATE_ONLINE_TO_OFFLINE:
                /*
                 * Don't need to deactivate current location - condition check
                 * will activate another.  If the currently active location is
                 * being deactivated, then it is being manually deactivated;
                 * so also clear active_loc so condition checking is not
                 * confused.
                 */
                (void) pthread_mutex_lock(&active_loc_mutex);
                if (strcmp(event->event_object, active_loc) == 0)
                        active_loc[0] = '\0';
                (void) pthread_mutex_unlock(&active_loc_mutex);
                nwamd_loc_check_conditions();
                break;
        case NWAM_STATE_DISABLED:
        case NWAM_STATE_OFFLINE:
        case NWAM_STATE_UNINITIALIZED:
        case NWAM_STATE_MAINTENANCE:
        case NWAM_STATE_DEGRADED:
        default:
                /* do nothing */
                break;
        }
}