root/usr/src/uts/common/inet/ip/ipsec_loader.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/stream.h>
#include <sys/sysmacros.h>
#include <sys/callb.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/proc.h>
#include <sys/modctl.h>
#include <sys/disp.h>
#include <inet/ip.h>
#include <inet/ipsec_impl.h>
#include <inet/optcom.h>
#include <inet/keysock.h>

/*
 * Loader commands for ipsec_loader_sig
 */
#define IPSEC_LOADER_EXITNOW    -1
#define IPSEC_LOADER_LOADNOW    1

/*
 * NOTE:  This function is entered w/o holding any STREAMS perimeters.
 */
static void
ipsec_loader(void *arg)
{
        callb_cpr_t cprinfo;
        boolean_t ipsec_failure = B_FALSE;
        ipsec_stack_t *ipss = (ipsec_stack_t *)arg;

        CALLB_CPR_INIT(&cprinfo, &ipss->ipsec_loader_lock, callb_generic_cpr,
            "ipsec_loader");
        mutex_enter(&ipss->ipsec_loader_lock);
        for (;;) {

                /*
                 * Wait for someone to tell me to continue.
                 */
                while (ipss->ipsec_loader_sig == IPSEC_LOADER_WAIT) {
                        CALLB_CPR_SAFE_BEGIN(&cprinfo);
                        cv_wait(&ipss->ipsec_loader_sig_cv,
                            &ipss->ipsec_loader_lock);
                        CALLB_CPR_SAFE_END(&cprinfo, &ipss->ipsec_loader_lock);
                }

                /* IPSEC_LOADER_EXITNOW implies signal by _fini(). */
                if (ipss->ipsec_loader_sig == IPSEC_LOADER_EXITNOW) {
                        /*
                         * Let user patch ipsec_loader_tid to
                         * 0 to try again.
                         */
                        ipss->ipsec_loader_state = IPSEC_LOADER_FAILED;
                        ipss->ipsec_loader_sig = IPSEC_LOADER_WAIT;

                        /* ipsec_loader_lock is held at this point! */
                        ASSERT(MUTEX_HELD(&ipss->ipsec_loader_lock));
                        CALLB_CPR_EXIT(&cprinfo);
                        ASSERT(MUTEX_NOT_HELD(&ipss->ipsec_loader_lock));
                        thread_exit();
                }
                mutex_exit(&ipss->ipsec_loader_lock);

                /*
                 * Load IPsec, which is done by modloading keysock and calling
                 * keysock_plumb_ipsec().
                 */

                /* Pardon my hardcoding... */
                if (modload("drv", "keysock") == -1) {
                        cmn_err(CE_WARN, "IP: Cannot load keysock.");
                        /*
                         * Only this function can set ipsec_failure.  If the
                         * damage can be repaired, use adb to set this to
                         * B_FALSE and try again.
                         */
                        ipsec_failure = B_TRUE;
                } else if (keysock_plumb_ipsec(ipss->ipsec_netstack) != 0) {
                        cmn_err(CE_WARN, "IP: Cannot plumb IPsec.");
                        /*
                         * Only this function can set ipsec_failure.  If the
                         * damage can be repaired, use adb to set this to
                         * B_FALSE and try again.
                         */
                        ipsec_failure = B_TRUE;
                } else {
                        ipsec_failure = B_FALSE;
                }

                mutex_enter(&ipss->ipsec_loader_lock);
                if (ipsec_failure) {
                        if (ipss->ipsec_loader_sig == IPSEC_LOADER_LOADNOW)
                                ipss->ipsec_loader_sig = IPSEC_LOADER_WAIT;
                        ipss->ipsec_loader_state = IPSEC_LOADER_FAILED;
                } else {
                        ipss->ipsec_loader_state = IPSEC_LOADER_SUCCEEDED;
                }
                mutex_exit(&ipss->ipsec_loader_lock);

                mutex_enter(&ipss->ipsec_loader_lock);
                if (!ipsec_failure) {
                        CALLB_CPR_EXIT(&cprinfo);
                        ASSERT(MUTEX_NOT_HELD(&ipss->ipsec_loader_lock));
                        ipsec_register_prov_update();
                        thread_exit();
                }
        }
}

/*
 * Called from ip_ddi_init() to initialize ipsec loader thread.
 */
void
ipsec_loader_init(ipsec_stack_t *ipss)
{
        mutex_init(&ipss->ipsec_loader_lock, NULL, MUTEX_DEFAULT, NULL);
        cv_init(&ipss->ipsec_loader_sig_cv, NULL, CV_DEFAULT, NULL);
}

/*
 * Called from ip_ddi_destroy() to take down ipsec loader thread.
 */
void
ipsec_loader_destroy(ipsec_stack_t *ipss)
{
        kt_did_t tid;

        mutex_enter(&ipss->ipsec_loader_lock);
        tid = ipss->ipsec_loader_tid;
        if (tid != 0) {
                ipss->ipsec_loader_sig = IPSEC_LOADER_EXITNOW;
                cv_signal(&ipss->ipsec_loader_sig_cv);
                ipss->ipsec_loader_tid = 0;
        }
        mutex_exit(&ipss->ipsec_loader_lock);

        /*
         * Wait for ipsec_loader() to finish before we destroy
         * cvs and mutexes.
         */
        if (tid != 0)
                thread_join(tid);

        mutex_destroy(&ipss->ipsec_loader_lock);
        cv_destroy(&ipss->ipsec_loader_sig_cv);
}

void
ipsec_loader_start(ipsec_stack_t *ipss)
{
        kthread_t *tp;

        mutex_enter(&ipss->ipsec_loader_lock);

        if (ipss->ipsec_loader_tid == 0) {
                tp = thread_create(NULL, 0, ipsec_loader, ipss, 0, &p0,
                    TS_RUN, MAXCLSYSPRI);
                ipss->ipsec_loader_tid = tp->t_did;
        }
        /* Else we lost the race, oh well. */
        mutex_exit(&ipss->ipsec_loader_lock);
}

void
ipsec_loader_loadnow(ipsec_stack_t *ipss)
{
        /*
         * It is possible that an algorithm update message was
         * received before IPsec is loaded. Such messages are
         * saved in spdsock for later processing. Since IPsec
         * loading can be initiated by interfaces different
         * than spdsock, we must trigger the processing of
         * update messages from the ipsec loader.
         */
        spdsock_update_pending_algs(ipss->ipsec_netstack);

        mutex_enter(&ipss->ipsec_loader_lock);
        if ((ipss->ipsec_loader_state == IPSEC_LOADER_WAIT) &&
            (ipss->ipsec_loader_sig == IPSEC_LOADER_WAIT)) {
                ipss->ipsec_loader_sig = IPSEC_LOADER_LOADNOW;
                cv_signal(&ipss->ipsec_loader_sig_cv);
        }
        mutex_exit(&ipss->ipsec_loader_lock);
}

/*
 * Dummy callback routine (placeholder) to avoid keysock plumbing
 * races.  Used in conjunction with qtimeout() and qwait() to wait
 * until ipsec has loaded -- the qwait() in ipsec_loader_loadwait will
 * wake up once this routine returns.
 */

/* ARGSUSED */
static void
loader_nop(void *ignoreme)
{
}

/*
 * Called from keysock driver open to delay until ipsec is done loading.
 * Returns B_TRUE if it worked, B_FALSE if it didn't.
 */
boolean_t
ipsec_loader_wait(queue_t *q, ipsec_stack_t *ipss)
{
        /*
         * 30ms delay per loop is arbitrary; it takes ~300ms to
         * load and plumb ipsec on an ultra-1.
         */

        while (ipss->ipsec_loader_state == IPSEC_LOADER_WAIT) {
                (void) qtimeout(q, loader_nop, 0, drv_usectohz(30000));
                qwait(q);
        }

        return (ipss->ipsec_loader_state == IPSEC_LOADER_SUCCEEDED);
}

/*
 * Just check to see if IPsec is loaded (or not).
 */
boolean_t
ipsec_loaded(ipsec_stack_t *ipss)
{
        return (ipss->ipsec_loader_state == IPSEC_LOADER_SUCCEEDED);
}

/*
 * Check to see if IPsec loading failed.
 */
boolean_t
ipsec_failed(ipsec_stack_t *ipss)
{
        return (ipss->ipsec_loader_state == IPSEC_LOADER_FAILED);
}