root/usr/src/cmd/cmd-inet/lib/nwamd/llp.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) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * This file is here for legacy support.
 */

#include <atomic.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <libdllink.h>
#include <libscf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#include <libnwam.h>
#include "known_wlans.h"
#include "llp.h"
#include "ncu.h"
#include "util.h"

/*
 * This file formerly contained the routines that manipulate Link Layer
 * Profiles (aka LLPs) and various support functions.  Now only code
 * necessary for parsing the legacy /etc/nwam/llp file on upgrade is included,
 * since this legacy configuration needs to be translated into the User NCP.
 */

#define OUR_OLD_DHCP_WAIT_TIME_PROP_NAME        "dhcp_wait_time"
#define OUR_OLD_USE_NET_SVC_PROP_NAME           "use_net_svc"
#define OUR_OLD_IDLE_TIME_PROP_NAME             "idle_time"

static struct qelem llp_list;

/*
 * Global variable to hold the highest priority.  Need to use the atomic
 * integer arithmetic functions to update it.
 */
static uint32_t llp_highest_pri;

/* Specifies if static address has been configured in /etc/nwam/llp */
static boolean_t static_configured = B_FALSE;

static enum interface_type
find_if_type(const char *name)
{
        uint32_t media;
        enum interface_type type;

        if (name == NULL) {
                nlog(LOG_DEBUG, "find_if_type: no ifname; "
                    "returning IF_UNKNOWN");
                return (IF_UNKNOWN);
        }

        type = IF_WIRED;
        if (dladm_name2info(dld_handle, name, NULL, NULL, NULL, &media) !=
            DLADM_STATUS_OK) {
                if (strncmp(name, "ip.tun", 6) == 0 ||
                    strncmp(name, "ip6.tun", 7) == 0 ||
                    strncmp(name, "ip.6to4tun", 10) == 0)
                        /*
                         * We'll need to update our tunnel detection once
                         * the clearview/tun project is integrated; tunnel
                         * names won't necessarily be ip.tunN.
                         */
                        type = IF_TUN;
        } else if (media == DL_WIFI) {
                type = IF_WIRELESS;
        }

        return (type);
}

static void
llp_list_free(void)
{
        llp_t *llp;

        while (llp_list.q_forw != &llp_list) {
                llp = (llp_t *)llp_list.q_forw;
                remque(&llp->llp_links);
                free(llp->llp_ipv6addrstr);
                free(llp->llp_ipv4addrstr);
                free(llp);
        }
}

static void
initialize_llp(void)
{
        llp_list.q_forw = llp_list.q_back = &llp_list;
}

static llp_t *
llp_lookup(const char *link)
{
        llp_t *llp;

        if (link == NULL)
                return (NULL);

        for (llp = (llp_t *)llp_list.q_forw; llp != (llp_t *)&llp_list;
            llp = (llp_t *)llp->llp_links.q_forw) {
                if (strcmp(link, llp->llp_lname) == 0)
                        break;
        }
        if (llp == (llp_t *)&llp_list)
                llp = NULL;
        return (llp);
}

/*
 * Create the named LLP with default settings.  Called only in main thread.
 */
static llp_t *
llp_add(const char *name)
{
        llp_t *llp;

        if ((llp = calloc(1, sizeof (llp_t))) == NULL) {
                nlog(LOG_ERR, "llp_add: cannot allocate LLP: %m");
                return (NULL);
        }

        if (strlcpy(llp->llp_lname, name, sizeof (llp->llp_lname)) >=
            sizeof (llp->llp_lname)) {
                nlog(LOG_ERR, "llp_add: linkname '%s' too long; ignoring entry",
                    name);
                free(llp);
                return (NULL);
        }

        llp->llp_fileorder = llp->llp_pri =
            atomic_add_32_nv(&llp_highest_pri, 1);
        llp->llp_ipv4src = IPV4SRC_DHCP;
        llp->llp_type = find_if_type(llp->llp_lname);
        llp->llp_ipv6onlink = B_TRUE;

        /*
         * should be a no-op, but for now, make sure we only
         * create llps for wired and wireless interfaces.
         */
        if (llp->llp_type != IF_WIRED && llp->llp_type != IF_WIRELESS) {
                nlog(LOG_ERR, "llp_add: wrong type of interface for %s", name);
                free(llp);
                return (NULL);
        }
        insque(&llp->llp_links, llp_list.q_back);

        nlog(LOG_DEBUG, "llp_add: "
            "created llp for link %s, priority %d", llp->llp_lname,
            llp->llp_pri);
        return (llp);
}

static int
parse_llp_config(void)
{
        static const char STATICSTR[] = "static";
        static const char DHCP[] = "dhcp";
        static const char IPV6[] = "ipv6";
        static const char NOIPV6[] = "noipv6";
        static const char PRIORITY[] = "priority";
        FILE *fp;
        char line[LINE_MAX];
        char *cp, *lasts, *lstr, *srcstr, *addrstr;
        int lnum;
        llp_t *llp;

        initialize_llp();

        fp = fopen(LLPFILE, "r+");
        if (fp == NULL) {
                if (errno == ENOENT)
                        return (errno);
                nlog(LOG_ERR, "parse_llp_config: "
                    "open legacy LLP config file: %m");
                return (-1);
        }

        for (lnum = 1; fgets(line, sizeof (line), fp) != NULL; lnum++) {
                if (line[strlen(line) - 1] == '\n')
                        line[strlen(line) - 1] = '\0';

                cp = line;
                while (isspace(*cp))
                        cp++;

                if (*cp == '#' || *cp == '\0')
                        continue;

                nlog(LOG_DEBUG, "parse_llp_config: "
                    "parsing legacy LLP conf file line %d...", lnum);

                if (((lstr = strtok_r(cp, " \t", &lasts)) == NULL) ||
                    ((srcstr = strtok_r(NULL, " \t", &lasts)) == NULL)) {
                        nlog(LOG_ERR, "parse_llp_config: line %d: "
                            "not enough tokens; ignoring entry", lnum);
                        continue;
                }

                if ((llp = llp_lookup(lstr)) == NULL &&
                    (llp = llp_add(lstr)) == NULL) {
                        nlog(LOG_ERR, "parse_llp_config: line %d: "
                            "cannot add entry", lnum);
                        continue;
                }

                if (strcasecmp(srcstr, STATICSTR) == 0) {
                        if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL ||
                            atoi(addrstr) == 0) { /* crude check for number */
                                nlog(LOG_ERR, "parse_llp_config: line %d: "
                                    "missing ipaddr for static config", lnum);
                        } else if ((addrstr = strdup(addrstr)) == NULL) {
                                nlog(LOG_ERR, "parse_llp_config: line %d: "
                                    "cannot save address", lnum);
                        } else {
                                free(llp->llp_ipv4addrstr);
                                llp->llp_ipv4src = IPV4SRC_STATIC;
                                llp->llp_ipv4addrstr = addrstr;
                        }

                } else if (strcasecmp(srcstr, DHCP) == 0) {
                        llp->llp_ipv4src = IPV4SRC_DHCP;

                } else if (strcasecmp(srcstr, IPV6) == 0) {
                        llp->llp_ipv6onlink = B_TRUE;
                        if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL) {
                                (void) 0;
                        } else if ((addrstr = strdup(addrstr)) == NULL) {
                                nlog(LOG_ERR, "parse_llp_config: line %d: "
                                    "cannot save address", lnum);
                        } else {
                                free(llp->llp_ipv6addrstr);
                                llp->llp_ipv6addrstr = addrstr;
                        }

                } else if (strcasecmp(srcstr, NOIPV6) == 0) {
                        llp->llp_ipv6onlink = B_FALSE;

                } else if (strcasecmp(srcstr, PRIORITY) == 0) {
                        if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL) {
                                nlog(LOG_ERR,
                                    "parse_llp_config: line %d: "
                                    "missing priority value", lnum);
                        } else {
                                llp->llp_pri = atoi(addrstr);
                        }

                } else {
                        nlog(LOG_ERR, "parse_llp_config: line %d: "
                            "unrecognized field '%s'", lnum, srcstr);
                }
        }

        (void) fclose(fp);
        return (0);
}

/*
 * Translate legacy LLP config into the user NCP.
 */
static int
upgrade_llp_config(void)
{
        llp_t *wp;
        nwam_ncp_handle_t user_ncp;
        nwam_ncu_handle_t phys_ncu = NULL, ip_ncu = NULL;
        nwam_error_t err;
        uint64_t uintval;
        char *strval;
        const char *prop;

        switch (parse_llp_config()) {
        case -1:
                return (0);
        case ENOENT:
                return (ENOENT);
        default:
                break;
        }

        err = nwam_ncp_create(NWAM_NCP_NAME_USER, 0, &user_ncp);
        switch (err) {
        case NWAM_SUCCESS:
                break;
        case NWAM_ERROR_BIND:
        case NWAM_ERROR_INTERNAL:
                nlog(LOG_ERR, "upgrade_llp_config: "
                    "could not create User NCP: %s", nwam_strerror(err));
                llp_list_free();
                return (EAGAIN);
        default:
                nlog(LOG_ERR, "upgrade_llp_config: error creating User NCP: %s",
                    nwam_strerror(err));
                llp_list_free();
                return (0);
        }

        nlog(LOG_DEBUG, "upgrade_llp_config: walking llp list");

        for (wp = (llp_t *)llp_list.q_forw; wp != (llp_t *)&llp_list;
            wp = (llp_t *)wp->llp_links.q_forw) {

                nlog(LOG_DEBUG, "upgrade_llp_config: "
                    "upgrading llp %s", wp->llp_lname);

                if (nwam_ncu_create(user_ncp, wp->llp_lname,
                    NWAM_NCU_TYPE_INTERFACE, NWAM_NCU_CLASS_IP, &ip_ncu)
                    != NWAM_SUCCESS ||
                    nwam_ncu_create(user_ncp, wp->llp_lname, NWAM_NCU_TYPE_LINK,
                    NWAM_NCU_CLASS_PHYS, &phys_ncu) != NWAM_SUCCESS) {
                        nlog(LOG_ERR, "upgrade_llp_config: llp %s: "
                            "could not create NCUs: %s", wp->llp_lname,
                            nwam_strerror(err));
                        break;
                }

                /* Link NCU properties */
                prop = NWAM_NCU_PROP_ACTIVATION_MODE;
                uintval = NWAM_ACTIVATION_MODE_PRIORITIZED;
                if ((err = nwamd_set_ncu_uint(phys_ncu, &uintval, 1, prop))
                    != NWAM_SUCCESS)
                        break;

                prop = NWAM_NCU_PROP_PRIORITY_MODE;
                uintval = NWAM_PRIORITY_MODE_EXCLUSIVE;
                if ((err = nwamd_set_ncu_uint(phys_ncu, &uintval, 1, prop))
                    != NWAM_SUCCESS)
                        break;

                prop = NWAM_NCU_PROP_PRIORITY_GROUP;
                uintval = wp->llp_pri;
                if ((err = nwamd_set_ncu_uint(phys_ncu, &uintval, 1, prop))
                    != NWAM_SUCCESS)
                        break;

                /* IP NCU properties */
                if (wp->llp_ipv4addrstr != NULL) {
                        /* Set v4 address and specify static addrsrc */
                        prop = NWAM_NCU_PROP_IPV4_ADDRSRC;
                        uintval = NWAM_ADDRSRC_STATIC;
                        if ((err = nwamd_set_ncu_uint(ip_ncu, &uintval, 1,
                            prop)) != NWAM_SUCCESS)
                                break;

                        prop = NWAM_NCU_PROP_IPV4_ADDR;
                        strval = wp->llp_ipv4addrstr;
                        if ((err = nwamd_set_ncu_string(ip_ncu, &strval, 1,
                            prop)) != NWAM_SUCCESS)
                                break;

                        static_configured = B_TRUE;
                }

                if (wp->llp_ipv6addrstr != NULL) {
                        /* Set v6 address and specify static addrsrc */
                        prop = NWAM_NCU_PROP_IPV6_ADDRSRC;
                        uintval = NWAM_ADDRSRC_STATIC;
                        if ((err = nwamd_set_ncu_uint(ip_ncu, &uintval, 1,
                            prop)) != NWAM_SUCCESS)
                                break;

                        prop = NWAM_NCU_PROP_IPV6_ADDR;
                        strval = wp->llp_ipv6addrstr;
                        if ((err = nwamd_set_ncu_string(ip_ncu, &strval, 1,
                            prop)) != NWAM_SUCCESS)
                                break;

                        static_configured = B_TRUE;
                }

                if (!wp->llp_ipv6onlink) {
                        prop = NWAM_NCU_PROP_IP_VERSION;
                        uintval = IPV4_VERSION;
                        if ((err = nwamd_set_ncu_uint(ip_ncu, &uintval, 1,
                            prop)) != NWAM_SUCCESS)
                                break;
                }

                if ((err = nwam_ncu_commit(ip_ncu, 0)) != NWAM_SUCCESS ||
                    (err = nwam_ncu_commit(phys_ncu, 0)) != NWAM_SUCCESS) {
                        nlog(LOG_ERR, "upgrade_llp_config: llp %s: "
                            "could not commit NCUs: %s", wp->llp_lname,
                            nwam_strerror(err));
                        /* Schedule a retry - root filesystem may be readonly */
                        llp_list_free();
                        nwam_ncu_free(ip_ncu);
                        nwam_ncu_free(phys_ncu);
                        (void) nwam_ncp_destroy(user_ncp, 0);
                        return (EAGAIN);
                }
        }

        if (err != NWAM_SUCCESS) {
                nlog(LOG_ERR, "upgrade_llp_config: llp %s: "
                    "could not set value for property %s: %s", wp->llp_lname,
                    prop, nwam_strerror(err));
        }
        llp_list_free();
        nwam_ncu_free(ip_ncu);
        nwam_ncu_free(phys_ncu);
        nwam_ncp_free(user_ncp);
        return (0);
}

/*
 * Upgrade legacy llp and known_wifi_nets files. Note - it is possible that
 * the root filesystem is not writable at this point, so we need to schedule
 * a retry of the upgrade operation in the event that committing the new
 * config fails.
 */
/* ARGSUSED0 */
void
nwamd_handle_upgrade(nwamd_event_t event)
{
        nwamd_event_t upgrade_event;
        uint64_t dhcp_wait_time, idle_time;
        boolean_t use_net_svc;

        switch (upgrade_llp_config()) {
        case -1:
        case ENOENT:
                /* Nothing readable to upgrade */
                break;
        case EAGAIN:
                /*
                 * Schedule retry in NWAMD_READONLY_RETRY_INTERVAL seconds
                 * as root fs may be readonly.
                 *
                 * The upgrade event is of type NCU, but has no associated
                 * object (we use the event type to map to the appropriate
                 * event/method mappings, so to find the NCU upgrade event
                 * method we specify type NCU while not specifying an
                 * object since all NCUs have to be upgraded.
                 */
                upgrade_event = nwamd_event_init(NWAM_EVENT_TYPE_UPGRADE,
                    NWAM_OBJECT_TYPE_NCP, 0, NULL);
                if (upgrade_event == NULL) {
                        nlog(LOG_ERR, "nwamd_handle_upgrade: "
                            "could not create retry event to upgrade "
                            "%s configuration", LLPFILE);
                        return;
                }
                nwamd_event_enqueue_timed(upgrade_event,
                    NWAMD_READONLY_RETRY_INTERVAL);
                return;
        default:
                break;
        }

        /*
         * If static_configured is set, then at least one static address is
         * configured in /etc/nwam/llp.  Enable the User NCP in this case.
         */
        if (static_configured) {
                nlog(LOG_DEBUG, "nwamd_handle_upgrade: "
                    "static address configured, enabling User NCP");
                (void) pthread_mutex_lock(&active_ncp_mutex);
                (void) strlcpy(active_ncp, NWAM_NCP_NAME_USER,
                    NWAM_MAX_NAME_LEN);
                (void) pthread_mutex_unlock(&active_ncp_mutex);
        }

        /* upgrade /etc/nwam/known_wifi_nets */
        upgrade_known_wifi_nets_config();

        /*
         * SMF property nwamd/dhcp_wait_time in Phase 0/0.5 has been
         * replaced by nwamd/ncu_wait_time property.  If the dhcp_wait_time
         * property exists (which means it has been changed by the user),
         * set its value to ncu_wait_time and remove the property.
         */
        if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
            OUR_OLD_DHCP_WAIT_TIME_PROP_NAME, &dhcp_wait_time) == 0) {
                (void) nwamd_set_count_property(OUR_FMRI, OUR_PG,
                    OUR_NCU_WAIT_TIME_PROP_NAME, dhcp_wait_time);
                (void) nwamd_delete_scf_property(OUR_FMRI, OUR_PG,
                    OUR_OLD_DHCP_WAIT_TIME_PROP_NAME);
                nlog(LOG_DEBUG, "nwamd_handle_upgrade: "
                    "converted '%s' to '%s' with value of %lld",
                    OUR_OLD_DHCP_WAIT_TIME_PROP_NAME,
                    OUR_NCU_WAIT_TIME_PROP_NAME, dhcp_wait_time);
        }

        /*
         * If the user has changed Phase 0/0.5 properties that don't exist in
         * Phase 1, manifest-import reports a warning; but those properties are
         * not removed.  nwamd/use_net_svc and nwamd/idle_time are two
         * properties that don't exist in Phase 1.  If they exist, remove them.
         */
        if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG,
            OUR_OLD_IDLE_TIME_PROP_NAME, &idle_time) == 0) {
                (void) nwamd_delete_scf_property(OUR_FMRI, OUR_PG,
                    OUR_OLD_IDLE_TIME_PROP_NAME);
        }
        if (nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG,
            OUR_OLD_USE_NET_SVC_PROP_NAME, &use_net_svc) == 0) {
                (void) nwamd_delete_scf_property(OUR_FMRI, OUR_PG,
                    OUR_OLD_USE_NET_SVC_PROP_NAME);
        }

        nlog(LOG_DEBUG, "nwamd_handle_upgrade: "
            "creating version property, setting to 1\n");
        (void) nwamd_set_count_property(OUR_FMRI, OUR_PG,
            OUR_VERSION_PROP_NAME, 1U);
        (void) smf_refresh_instance(OUR_FMRI);
}