root/usr/src/uts/common/io/scsi/adapters/iscsi/isns_client.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.
 *
 * iSNS Client
 */

#include "iscsi.h"              /* For ISCSI_MAX_IOVEC */
#include "isns_protocol.h"
#include "isns_client.h"
#include "persistent.h"

#ifdef _KERNEL
#include <sys/sunddi.h>
#else
#include <stdlib.h>
#endif
#include <netinet/tcp.h>
#include <sys/types.h>

/* For local use */
#define ISNS_MAX_IOVEC          5
#define MAX_XID                 (2^16)
#define MAX_RCV_RSP_COUNT       10      /* Maximum number of unmatched xid */
#define ISNS_RCV_TIMEOUT        5
#define ISNS_RCV_RETRY_MAX      2
#define IPV4_RSVD_BYTES         10

typedef struct isns_reg_arg {
        iscsi_addr_t *isns_server_addr;
        uint8_t *node_name;
        size_t node_name_len;
        uint8_t *node_alias;
        size_t node_alias_len;
        uint32_t node_type;
        uint8_t *lhba_handle;
} isns_reg_arg_t;

typedef struct isns_async_thread_arg {
        uint8_t *lhba_handle;
        void *listening_so;
} isns_async_thread_arg_t;

/* One global queue to serve all LHBA instances. */
static ddi_taskq_t *reg_query_taskq;
static kmutex_t reg_query_taskq_mutex;

/* One global queue to serve all LHBA instances. */
static ddi_taskq_t *scn_taskq;
static kmutex_t scn_taskq_mutex;

/* One globally maintained transaction ID. */
static uint16_t xid = 0;

/*
 * One SCN callback registration per LHBA instance. For now, since we
 * support only one instance, we create one place holder for the
 * callback.
 */
void (*scn_callback_p)(void *);

/*
 * One thread, port, local address, and listening socket per LHBA instance.
 * For now, since we support only one instance, we create one set of place
 * holder for these data.
 */
static boolean_t esi_scn_thr_to_shutdown = B_FALSE;
static iscsi_thread_t *esi_scn_thr_id = NULL;
static void *instance_listening_so = NULL;
/*
 * This mutex protects all the per LHBA instance variables, i.e.,
 * esi_scn_thr_to_shutdown, esi_scn_thr_id, and instance_listening_so.
 */
static kmutex_t esi_scn_thr_mutex;

/* iSNS related helpers */
/* Return status */
#define ISNS_OK                         0
#define ISNS_BAD_SVR_ADDR               1
#define ISNS_INTERNAL_ERR               2
#define ISNS_CANNOT_FIND_LOCAL_ADDR     3
static int discover_isns_server(uint8_t *lhba_handle,
    iscsi_addr_list_t **isns_server_addrs);
static int create_esi_scn_thr(uint8_t *lhba_handle,
    iscsi_addr_t *isns_server_addr);
static void esi_scn_thr_cleanup(void);
static void register_isns_client(void *arg);
static isns_status_t do_isns_dev_attr_reg(iscsi_addr_t *isns_server_addr,
    uint8_t *node_name, uint8_t *node_alias, uint32_t node_type);
static isns_status_t do_isns_dev_dereg(iscsi_addr_t *isns_server_addr,
    uint8_t *node_name);

/*
 * Make query to all iSNS servers visible to the specified LHBA.
 * The query could be made for all target nodes or for a specific target
 * node.
 */
static isns_status_t do_isns_query(boolean_t is_query_all_nodes_b,
    uint8_t *lhba_handle, uint8_t *target_node_name,
    uint8_t *source_node_name, uint8_t *source_node_alias,
    uint32_t source_node_type, isns_portal_group_list_t **pg_list);

/*
 * Create DevAttrQuery message requesting portal group information for all
 * target nodes. Send it to the specified iSNS server. Parse the
 * DevAttrQueryRsp PDU and translate the results into a portal group list
 * object.
 */
static isns_status_t do_isns_dev_attr_query_all_nodes(
    iscsi_addr_t *isns_server_addr, uint8_t *node_name,
    uint8_t *node_alias, isns_portal_group_list_t **pg_list);

/*
 * Create DevAttrQuery message requesting portal group information for the
 * specified target node. Send it to the specified iSNS server. Parse the
 * DevAttrQueryRsp PDU and translate the results into a portal group list
 * object.
 */
static isns_status_t do_isns_dev_attr_query_one_node(
    iscsi_addr_t *isns_server_addr, uint8_t *target_node_name,
    uint8_t *source_node_name, uint8_t *source_node_alias,
    uint32_t source_node_type, isns_portal_group_list_t **pg_list);

static void isns_service_esi_scn(iscsi_thread_t *thread, void* arg);
static void (*scn_callback_lookup(uint8_t *lhba_handle))(void *);

/* Transport related helpers */
static void *isns_open(iscsi_addr_t *isns_server_addr);
static ssize_t isns_send_pdu(void *socket, isns_pdu_t *pdu);
static size_t isns_rcv_pdu(void *so, isns_pdu_t **pdu, size_t *pdu_size);
static boolean_t find_listening_addr(iscsi_addr_t *local_addr,
    void *listening_so);
static boolean_t find_local_portal(iscsi_addr_t *isns_server_addr,
    iscsi_addr_t **local_addr, void **listening_so);

/* iSNS protocol related helpers */
static size_t isns_create_pdu_header(uint16_t func_id,
    uint16_t flags, isns_pdu_t **pdu);
static int isns_add_attr(isns_pdu_t *pdu,
    size_t max_pdu_size, uint32_t attr_id, uint32_t attr_len,
    void *attr_data, uint32_t attr_numeric_data);
static uint16_t create_xid(void);
static size_t isns_create_dev_attr_reg_pdu(
    uint8_t *node_name, uint8_t *node_alias, uint32_t node_type,
    uint16_t *xid, isns_pdu_t **out_pdu);
static size_t isns_create_dev_dereg_pdu(uint8_t *node_name,
    uint16_t *xid_p, isns_pdu_t **out_pdu);
static size_t isns_create_dev_attr_qry_target_nodes_pdu(
    uint8_t *node_name, uint8_t *node_alias, uint16_t *xid,
    isns_pdu_t **out_pdu);
static size_t isns_create_dev_attr_qry_one_pg_pdu(
    uint8_t *target_node_name, uint8_t *source_node_name,
    uint16_t *xid, isns_pdu_t **out_pdu);
static size_t isns_create_esi_rsp_pdu(uint32_t rsp_status_code,
    isns_pdu_t *pdu, uint16_t *xid, isns_pdu_t **out_pdu);
static size_t isns_create_scn_reg_pdu(uint8_t *node_name,
    uint8_t *node_alias, uint16_t *xid, isns_pdu_t **out_pdu);
static size_t isns_create_scn_dereg_pdu(uint8_t *node_name,
    uint16_t *xid_p, isns_pdu_t **out_pdu);
static size_t isns_create_scn_rsp_pdu(uint32_t rsp_status_code,
    isns_pdu_t *pdu, uint16_t *xid, isns_pdu_t **out_pdu);
static uint32_t isns_process_dev_attr_reg_rsp(isns_pdu_t *resp_pdu_p);
static uint32_t isns_process_dev_attr_dereg_rsp(isns_pdu_t *resp_pdu_p);

/*
 * Process and parse a DevAttrQryRsp message. The routine creates a list
 * of Portal Group objects if the message is parasable without any issue.
 * If the parsing is not successful, the pg_list will be set to NULL.
 */
static uint32_t isns_process_dev_attr_qry_target_nodes_pdu(
    iscsi_addr_t *isns_server_addr, uint16_t payload_funcId,
    isns_resp_t *resp_p, size_t resp_len,
    isns_portal_group_list_t **pg_list);
static uint32_t isns_process_scn_reg_rsp(isns_pdu_t *resp_pdu_p);
static uint32_t isns_process_scn_dereg_rsp(isns_pdu_t *resp_pdu_p);
static uint32_t isns_process_esi(isns_pdu_t *esi_pdu_p);
static uint32_t isns_process_scn(isns_pdu_t *scn_pdu_p, uint8_t *lhba_handle);

void
isns_client_init()
{
        mutex_init(&reg_query_taskq_mutex, NULL, MUTEX_DRIVER, NULL);
        mutex_enter(&reg_query_taskq_mutex);
        reg_query_taskq = ddi_taskq_create(NULL, "isns_reg_query_taskq",
            1, TASKQ_DEFAULTPRI, 0);
        mutex_exit(&reg_query_taskq_mutex);

        mutex_init(&scn_taskq_mutex, NULL, MUTEX_DRIVER, NULL);
        mutex_enter(&scn_taskq_mutex);
        scn_taskq = ddi_taskq_create(NULL, "isns_scn_taskq",
            1, TASKQ_DEFAULTPRI, 0);
        mutex_exit(&scn_taskq_mutex);

        mutex_init(&esi_scn_thr_mutex, NULL, MUTEX_DRIVER, NULL);

        /* MISC initializations. */
        scn_callback_p = NULL;
        esi_scn_thr_id = NULL;
        instance_listening_so = NULL;
        esi_scn_thr_to_shutdown = B_FALSE;
        xid = 0;
}

void
isns_client_cleanup()
{
        ddi_taskq_t *tmp_taskq_p;

        mutex_enter(&scn_taskq_mutex);
        tmp_taskq_p = scn_taskq;
        scn_taskq = NULL;
        mutex_exit(&scn_taskq_mutex);
        ddi_taskq_destroy(tmp_taskq_p);

        mutex_enter(&reg_query_taskq_mutex);
        tmp_taskq_p = reg_query_taskq;
        reg_query_taskq = NULL;
        mutex_exit(&reg_query_taskq_mutex);
        ddi_taskq_destroy(tmp_taskq_p);

        mutex_destroy(&reg_query_taskq_mutex);
        mutex_destroy(&scn_taskq_mutex);

        esi_scn_thr_cleanup();

        mutex_destroy(&esi_scn_thr_mutex);
}

isns_status_t
isns_reg(uint8_t *lhba_handle,
        uint8_t *node_name,
        size_t node_name_len,
        uint8_t *node_alias,
        size_t node_alias_len,
        uint32_t node_type,
        void (*scn_callback)(void *))
{
        int i;
        int list_space;
        iscsi_addr_list_t *isns_server_addr_list;
        isns_reg_arg_t *reg_args_p;

        /* Look up the iSNS Server address(es) based on the specified ISID */
        if (discover_isns_server(lhba_handle, &isns_server_addr_list) !=
            ISNS_OK) {
                return (isns_no_svr_found);
        }

        /* No iSNS server discovered - no registration needed. */
        if (isns_server_addr_list->al_out_cnt == 0) {
                list_space = sizeof (iscsi_addr_list_t);
                kmem_free(isns_server_addr_list, list_space);
                isns_server_addr_list = NULL;
                return (isns_no_svr_found);
        }

        /* Check and create ESI/SCN threads and populate local address */
        for (i = 0; i < isns_server_addr_list->al_out_cnt; i++) {
                if (create_esi_scn_thr(lhba_handle,
                    &(isns_server_addr_list->al_addrs[i])) == ISNS_OK) {
                        break;
                }
        }
        if (i == isns_server_addr_list->al_out_cnt) {
                /*
                 * Problem creating ESI/SCN thread
                 * Free the server list
                 */
                list_space = sizeof (iscsi_addr_list_t);
                if (isns_server_addr_list->al_out_cnt > 0) {
                        list_space += (sizeof (iscsi_addr_t) *
                            (isns_server_addr_list->al_out_cnt - 1));
                }
                kmem_free(isns_server_addr_list, list_space);
                isns_server_addr_list = NULL;
                return (isns_internal_err);
        }

        /* Register against all iSNS servers discovered. */
        for (i = 0; i < isns_server_addr_list->al_out_cnt; i++) {
                reg_args_p = kmem_zalloc(sizeof (isns_reg_arg_t), KM_SLEEP);
                reg_args_p->isns_server_addr =
                    kmem_zalloc(sizeof (iscsi_addr_t), KM_SLEEP);
                bcopy(&isns_server_addr_list->al_addrs[i],
                    reg_args_p->isns_server_addr, sizeof (iscsi_addr_t));
                reg_args_p->node_name = kmem_zalloc(node_name_len, KM_SLEEP);
                bcopy(node_name, reg_args_p->node_name, node_name_len);
                reg_args_p->node_name_len = node_name_len;
                reg_args_p->node_alias = kmem_zalloc(node_alias_len, KM_SLEEP);
                bcopy(node_alias, reg_args_p->node_alias, node_alias_len);
                reg_args_p->node_alias_len = node_alias_len;
                reg_args_p->node_type = node_type;

                /* Dispatch the registration request */
                register_isns_client(reg_args_p);
        }

        /* Free the server list */
        list_space = sizeof (iscsi_addr_list_t);
        if (isns_server_addr_list->al_out_cnt > 0) {
                list_space += (sizeof (iscsi_addr_t) *
                    (isns_server_addr_list->al_out_cnt - 1));
        }
        kmem_free(isns_server_addr_list, list_space);
        isns_server_addr_list = NULL;

        /* Register the scn_callback. */
        scn_callback_p = scn_callback;

        return (isns_ok);
}

isns_status_t
isns_reg_one_server(entry_t *isns_server,
        uint8_t *lhba_handle,
        uint8_t *node_name,
        size_t node_name_len,
        uint8_t *node_alias,
        size_t node_alias_len,
        uint32_t node_type,
        void (*scn_callback)(void *))
{
        int status;
        iscsi_addr_t *ap;
        isns_reg_arg_t *reg_args_p;

        ap = (iscsi_addr_t *)kmem_zalloc(sizeof (iscsi_addr_t), KM_SLEEP);
        ap->a_port = isns_server->e_port;
        ap->a_addr.i_insize = isns_server->e_insize;
        if (isns_server->e_insize == sizeof (struct in_addr)) {
                ap->a_addr.i_addr.in4.s_addr = (isns_server->e_u.u_in4.s_addr);
        } else if (isns_server->e_insize == sizeof (struct in6_addr)) {
                bcopy(&(isns_server->e_u.u_in6.s6_addr),
                    ap->a_addr.i_addr.in6.s6_addr,
                    sizeof (struct in6_addr));
        } else {
                kmem_free(ap, sizeof (iscsi_addr_t));
                return (isns_op_failed);
        }

        /* Check and create ESI/SCN threads and populate local address */
        if ((status = create_esi_scn_thr(lhba_handle, ap))
            != ISNS_OK) {
                /* Problem creating ESI/SCN thread */
                DTRACE_PROBE1(isns_reg_one_server_create_esi_scn_thr,
                    int, status);
                kmem_free(ap, sizeof (iscsi_addr_t));
                return (isns_internal_err);
        }

        reg_args_p = kmem_zalloc(sizeof (isns_reg_arg_t), KM_SLEEP);
        reg_args_p->isns_server_addr =
            kmem_zalloc(sizeof (iscsi_addr_t), KM_SLEEP);
        bcopy(ap, reg_args_p->isns_server_addr, sizeof (iscsi_addr_t));
        reg_args_p->node_name = kmem_zalloc(node_name_len, KM_SLEEP);
        bcopy(node_name, reg_args_p->node_name, node_name_len);
        reg_args_p->node_name_len = node_name_len;
        reg_args_p->node_alias = kmem_zalloc(node_alias_len, KM_SLEEP);
        bcopy(node_alias, reg_args_p->node_alias, node_alias_len);
        reg_args_p->node_alias_len = node_alias_len;
        reg_args_p->node_type = node_type;

        /* Dispatch the registration request */
        register_isns_client(reg_args_p);

        /* Register the scn_callback. */
        scn_callback_p = scn_callback;

        kmem_free(ap, sizeof (iscsi_addr_t));
        return (isns_ok);
}

isns_status_t
isns_dereg(uint8_t *lhba_handle,
        uint8_t *node_name)
{
        int i;
        int isns_svr_lst_sz;
        int list_space;
        iscsi_addr_list_t *isns_server_addr_list = NULL;
        isns_status_t dereg_stat, combined_dereg_stat;

        /* Look up the iSNS Server address(es) based on the specified ISID */
        if (discover_isns_server(lhba_handle, &isns_server_addr_list) !=
            ISNS_OK) {
                return (isns_no_svr_found);
        }
        ASSERT(isns_server_addr_list != NULL);
        if (isns_server_addr_list->al_out_cnt == 0) {
                isns_svr_lst_sz = sizeof (iscsi_addr_list_t);
                kmem_free(isns_server_addr_list, isns_svr_lst_sz);
                isns_server_addr_list = NULL;
                return (isns_no_svr_found);
        }

        combined_dereg_stat = isns_ok;
        for (i = 0; i < isns_server_addr_list->al_out_cnt; i++) {
                dereg_stat = do_isns_dev_dereg(
                    &isns_server_addr_list->al_addrs[i],
                    node_name);
                if (dereg_stat == isns_ok) {
                        if (combined_dereg_stat != isns_ok) {
                                combined_dereg_stat = isns_op_partially_failed;
                        }
                } else {
                        if (combined_dereg_stat == isns_ok) {
                                combined_dereg_stat = isns_op_partially_failed;
                        }
                }
        }

        /* Free the server list. */
        list_space = sizeof (iscsi_addr_list_t);
        if (isns_server_addr_list->al_out_cnt > 0) {
                list_space += (sizeof (iscsi_addr_t) *
                    (isns_server_addr_list->al_out_cnt - 1));
        }
        kmem_free(isns_server_addr_list, list_space);
        isns_server_addr_list = NULL;

        /* Cleanup ESI/SCN thread. */
        esi_scn_thr_cleanup();

        return (combined_dereg_stat);
}

isns_status_t
isns_dereg_one_server(entry_t *isns_server,
        uint8_t *node_name,
        boolean_t is_last_isns_server_b)
{
        iscsi_addr_t *ap;
        isns_status_t dereg_stat;

        ap = (iscsi_addr_t *)kmem_zalloc(sizeof (iscsi_addr_t), KM_SLEEP);
        ap->a_port = isns_server->e_port;
        ap->a_addr.i_insize = isns_server->e_insize;
        if (isns_server->e_insize == sizeof (struct in_addr)) {
                ap->a_addr.i_addr.in4.s_addr = (isns_server->e_u.u_in4.s_addr);
        } else if (isns_server->e_insize == sizeof (struct in6_addr)) {
                bcopy(&(isns_server->e_u.u_in6.s6_addr),
                    ap->a_addr.i_addr.in6.s6_addr,
                    sizeof (struct in6_addr));
        } else {
                kmem_free(ap, sizeof (iscsi_addr_t));
                return (isns_op_failed);
        }

        dereg_stat = do_isns_dev_dereg(ap, node_name);

        kmem_free(ap, sizeof (iscsi_addr_t));

        if (is_last_isns_server_b == B_TRUE) {
                /*
                 * Clean up ESI/SCN thread resource if it is the
                 * last known iSNS server.
                 */
                esi_scn_thr_cleanup();
        }

        return (dereg_stat);
}

isns_status_t
isns_query(uint8_t *lhba_handle,
        uint8_t *node_name,
        uint8_t *node_alias,
        uint32_t node_type,
        isns_portal_group_list_t **pg_list)
{
        return (do_isns_query(B_TRUE,
            lhba_handle,
            (uint8_t *)"",
            node_name,
            node_alias,
            node_type,
            pg_list));
}

/* ARGSUSED */
isns_status_t
isns_query_one_server(iscsi_addr_t *isns_server_addr,
        uint8_t *lhba_handle,
        uint8_t *node_name,
        uint8_t *node_alias,
        uint32_t node_type,
        isns_portal_group_list_t **pg_list)
{
        return (do_isns_dev_attr_query_all_nodes(isns_server_addr,
            node_name,
            node_alias,
            pg_list));
}

isns_status_t
isns_query_one_node(uint8_t *target_node_name,
        uint8_t *lhba_handle,
        uint8_t *source_node_name,
        uint8_t *source_node_alias,
        uint32_t source_node_type,
        isns_portal_group_list_t **pg_list)
{
        return (do_isns_query(B_FALSE,
            lhba_handle,
            target_node_name,
            source_node_name,
            source_node_alias,
            source_node_type,
            pg_list));
}

/* ARGSUSED */
isns_status_t
isns_query_one_server_one_node(iscsi_addr_t *isns_server_addr,
        uint8_t *target_node_name,
        uint8_t *lhba_handle,
        uint8_t *source_node_name,
        uint8_t *source_node_alias,
        uint32_t source_node_type,
        isns_portal_group_list_t **pg_list) {
        /* Not supported yet. */
        *pg_list = NULL;
        return (isns_op_failed);
}

/* ARGSUSED */
static
int
discover_isns_server(uint8_t *lhba_handle,
        iscsi_addr_list_t **isns_server_addrs)
{
        entry_t e;
        int i;
        int isns_server_count = 1;
        int list_space;
        void *void_p;

        /*
         * Use supported iSNS server discovery method to find out all the
         * iSNS servers. For now, only static configuration method is
         * supported.
         */
        isns_server_count = 0;
        void_p = NULL;
        persistent_isns_addr_lock();
        while (persistent_isns_addr_next(&void_p, &e) == B_TRUE) {
                isns_server_count++;
        }
        persistent_isns_addr_unlock();

        list_space = sizeof (iscsi_addr_list_t);
        if (isns_server_count > 0) {
                list_space += (sizeof (iscsi_addr_t) * (isns_server_count - 1));
        }
        *isns_server_addrs = (iscsi_addr_list_t *)kmem_zalloc(list_space,
            KM_SLEEP);
        (*isns_server_addrs)->al_out_cnt = isns_server_count;

        persistent_isns_addr_lock();
        i = 0;
        void_p = NULL;
        while (persistent_isns_addr_next(&void_p, &e) == B_TRUE) {
                iscsi_addr_t *ap;

                ap = &((*isns_server_addrs)->al_addrs[i]);
                ap->a_port = e.e_port;
                ap->a_addr.i_insize = e.e_insize;
                if (e.e_insize == sizeof (struct in_addr)) {
                        ap->a_addr.i_addr.in4.s_addr = (e.e_u.u_in4.s_addr);
                } else if (e.e_insize == sizeof (struct in6_addr)) {
                        bcopy(&e.e_u.u_in6.s6_addr,
                            ap->a_addr.i_addr.in6.s6_addr,
                            sizeof (struct in6_addr));
                } else {
                        kmem_free(*isns_server_addrs, list_space);
                        *isns_server_addrs = NULL;
                        return (ISNS_BAD_SVR_ADDR);
                }
                i++;
        }
        persistent_isns_addr_unlock();

        return (ISNS_OK);
}

static
int
create_esi_scn_thr(uint8_t *lhba_handle, iscsi_addr_t *isns_server_address)
{
        void *listening_so  = NULL;
        boolean_t found     = B_FALSE;

        ASSERT(lhba_handle != NULL);
        ASSERT(isns_server_address != NULL);

        /*
         * Bringing up of the thread should happen regardless of the
         * subsequent registration status. That means, do not destroy the
         * ESI/SCN thread already created.
         */
        /* Check and create ESI/SCN thread. */
        mutex_enter(&esi_scn_thr_mutex);

        /* Determine local port and address. */
        found = find_local_portal(isns_server_address,
            NULL, &listening_so);
        if (found == B_FALSE) {
                if (listening_so != NULL) {
                        iscsi_net->close(listening_so);
                }
                mutex_exit(&esi_scn_thr_mutex);
                return (ISNS_CANNOT_FIND_LOCAL_ADDR);
        }

        if (esi_scn_thr_id == NULL) {
                char thr_name[ISCSI_TH_MAX_NAME_LEN];
                int rval;
                isns_async_thread_arg_t *larg;

                /* Assume the LHBA handle has a length of 4 */
                if (snprintf(thr_name, sizeof (thr_name) - 1,
                    "isns_client_esi_%x%x%x%x",
                    lhba_handle[0],
                    lhba_handle[1],
                    lhba_handle[2],
                    lhba_handle[3]) >=
                    sizeof (thr_name)) {
                        esi_scn_thr_id = NULL;
                        if (listening_so != NULL) {
                                iscsi_net->close(listening_so);
                                listening_so = NULL;
                        }
                        mutex_exit(&esi_scn_thr_mutex);
                        return (ISNS_INTERNAL_ERR);
                }

                larg = kmem_zalloc(sizeof (isns_async_thread_arg_t), KM_SLEEP);
                larg->lhba_handle = lhba_handle;
                larg->listening_so = listening_so;
                instance_listening_so = listening_so;
                esi_scn_thr_to_shutdown = B_FALSE;
                esi_scn_thr_id = iscsi_thread_create(NULL,
                    thr_name, isns_service_esi_scn, (void *)larg);
                if (esi_scn_thr_id == NULL) {
                        if (listening_so != NULL) {
                                iscsi_net->close(listening_so);
                                listening_so = NULL;
                                instance_listening_so = NULL;
                        }
                        mutex_exit(&esi_scn_thr_mutex);
                        return (ISNS_INTERNAL_ERR);
                }

                rval = iscsi_thread_start(esi_scn_thr_id);
                if (rval == B_FALSE) {
                        iscsi_thread_destroy(esi_scn_thr_id);
                        esi_scn_thr_id = NULL;
                        if (listening_so != NULL) {
                                iscsi_net->close(listening_so);
                                listening_so = NULL;
                                instance_listening_so = NULL;
                        }
                        mutex_exit(&esi_scn_thr_mutex);
                        return (ISNS_INTERNAL_ERR);
                }
                (void) iscsi_thread_send_wakeup(esi_scn_thr_id);
        }
        mutex_exit(&esi_scn_thr_mutex);

        return (ISNS_OK);
}

static
void
register_isns_client(void *arg)
{
        isns_reg_arg_t *reg_args;
        isns_status_t status;

        reg_args = (isns_reg_arg_t *)arg;

        /* Deregister stale registration (if any). */
        status = do_isns_dev_dereg(reg_args->isns_server_addr,
            reg_args->node_name);

        if (status == isns_open_conn_err) {
                /* Cannot open connection to the server. Stop proceeding. */
                kmem_free(reg_args->isns_server_addr, sizeof (iscsi_addr_t));
                reg_args->isns_server_addr = NULL;
                kmem_free(reg_args->node_name, reg_args->node_name_len);
                reg_args->node_name = NULL;
                kmem_free(reg_args->node_alias, reg_args->node_alias_len);
                reg_args->node_alias = NULL;
                kmem_free(reg_args, sizeof (isns_reg_arg_t));
                return;
        }

        DTRACE_PROBE1(register_isns_client_dereg, isns_status_t, status);

        /* New registration. */
        status =  do_isns_dev_attr_reg(reg_args->isns_server_addr,
            reg_args->node_name, reg_args->node_alias, reg_args->node_type);

        DTRACE_PROBE1(register_isns_client_reg, isns_status_t, status);

        /* Cleanup */
        kmem_free(reg_args->isns_server_addr, sizeof (iscsi_addr_t));
        reg_args->isns_server_addr = NULL;
        kmem_free(reg_args->node_name, reg_args->node_name_len);
        reg_args->node_name = NULL;
        kmem_free(reg_args->node_alias, reg_args->node_alias_len);
        reg_args->node_alias = NULL;
        kmem_free(reg_args, sizeof (isns_reg_arg_t));
}

static
isns_status_t
do_isns_dev_attr_reg(iscsi_addr_t *isns_server_addr,
        uint8_t *node_name, uint8_t *node_alias, uint32_t node_type)
{
        int rcv_rsp_cnt = 0;
        int rsp_status;
        isns_pdu_t *in_pdu, *out_pdu;
        isns_status_t rval;
        size_t bytes_received, in_pdu_size = 0, out_pdu_size = 0;
        uint16_t xid;
        void *so = NULL;

        out_pdu_size = isns_create_dev_attr_reg_pdu(
            node_name,
            node_alias,
            node_type,
            &xid, &out_pdu);
        if (out_pdu_size == 0) {
                return (isns_create_msg_err);
        }

        ASSERT(out_pdu != NULL);
        ASSERT(out_pdu_size > 0);

        so = isns_open(isns_server_addr);
        if (so == NULL) {
                /* Log a message and return */
                kmem_free(out_pdu, out_pdu_size);
                out_pdu = NULL;
                return (isns_open_conn_err);
        }

        if (isns_send_pdu(so, out_pdu) != 0) {
                iscsi_net->close(so);
                kmem_free(out_pdu, out_pdu_size);
                out_pdu = NULL;
                return (isns_send_msg_err);
        }

        /* Done with the out PDU - free it */
        kmem_free(out_pdu, out_pdu_size);
        out_pdu = NULL;

        rcv_rsp_cnt = 0;
        rval = isns_ok;
        for (;;) {
                bytes_received = isns_rcv_pdu(so, &in_pdu, &in_pdu_size);
                ASSERT(bytes_received >= (size_t)0);
                if (bytes_received == 0) {
                        ASSERT(in_pdu == NULL);
                        ASSERT(in_pdu_size == 0);
                        rval = isns_rcv_msg_err;
                        break;
                }

                ASSERT(in_pdu != NULL);
                ASSERT(in_pdu_size > 0);

                if (ntohs(in_pdu->xid) != xid) {
                        rcv_rsp_cnt++;
                        if (rcv_rsp_cnt < MAX_RCV_RSP_COUNT) {
                                continue;
                        } else {
                                /* Exceed maximum receive count. */
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                rval = isns_no_rsp_rcvd;
                                break;
                        }
                }

                rsp_status = isns_process_dev_attr_reg_rsp(in_pdu);
                if (rsp_status != ISNS_RSP_SUCCESSFUL) {
                        if (rsp_status == ISNS_RSP_SRC_UNAUTHORIZED) {
                                rval = isns_op_partially_failed;
                        } else {
                                rval = isns_op_failed;
                        }
                }
                kmem_free(in_pdu, in_pdu_size);
                in_pdu = NULL;
                break;
        }

        if (rval != isns_ok) {
                iscsi_net->close(so);
                return (rval);
        }

        /* Always register SCN */
        out_pdu_size = isns_create_scn_reg_pdu(
            node_name, node_alias,
            &xid, &out_pdu);
        if (out_pdu_size == 0) {
                iscsi_net->close(so);
                return (isns_create_msg_err);
        }

        ASSERT(out_pdu != NULL);
        ASSERT(out_pdu_size > 0);

        if (isns_send_pdu(so, out_pdu) != 0) {
                iscsi_net->close(so);
                kmem_free(out_pdu, out_pdu_size);
                out_pdu = NULL;
                return (isns_send_msg_err);
        }

        /* Done with the out PDU - free it */
        kmem_free(out_pdu, out_pdu_size);
        out_pdu = NULL;

        rcv_rsp_cnt = 0;
        for (;;) {
                bytes_received = isns_rcv_pdu(so, &in_pdu, &in_pdu_size);
                ASSERT(bytes_received >= (size_t)0);
                if (bytes_received == 0) {
                        ASSERT(in_pdu == NULL);
                        ASSERT(in_pdu_size == 0);
                        rval = isns_rcv_msg_err;
                        break;
                }

                ASSERT(in_pdu != NULL);
                ASSERT(in_pdu_size > 0);

                if (ntohs(in_pdu->xid) != xid) {
                        rcv_rsp_cnt++;
                        if (rcv_rsp_cnt < MAX_RCV_RSP_COUNT) {
                                continue;
                        } else {
                                /* Exceed maximum receive count. */
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                rval = isns_no_rsp_rcvd;
                                break;
                        }
                }

                rsp_status = isns_process_scn_reg_rsp(in_pdu);
                if (rsp_status != ISNS_RSP_SUCCESSFUL) {
                        rval = isns_op_failed;
                }
                kmem_free(in_pdu, in_pdu_size);
                in_pdu = NULL;
                break;
        }

        iscsi_net->close(so);

        return (rval);
}

static
isns_status_t
do_isns_dev_dereg(iscsi_addr_t *isns_server_addr,
        uint8_t *node_name)
{
        int rcv_rsp_cnt = 0;
        int rsp_status;
        isns_pdu_t *in_pdu, *out_pdu;
        isns_status_t rval;
        size_t bytes_received, in_pdu_size = 0, out_pdu_size = 0;
        uint16_t xid;
        void *so = NULL;

        out_pdu_size = isns_create_dev_dereg_pdu(
            node_name,
            &xid, &out_pdu);
        if (out_pdu_size == 0) {
                return (isns_create_msg_err);
        }

        ASSERT(out_pdu != NULL);
        ASSERT(out_pdu_size > 0);

        so = isns_open(isns_server_addr);
        if (so == NULL) {
                /* Log a message and return */
                kmem_free(out_pdu, out_pdu_size);
                out_pdu = NULL;
                return (isns_open_conn_err);
        }

        if (isns_send_pdu(so, out_pdu) != 0) {
                iscsi_net->close(so);
                kmem_free(out_pdu, out_pdu_size);
                out_pdu = NULL;
                return (isns_send_msg_err);
        }

        /* Done with the out PDU - free it */
        kmem_free(out_pdu, out_pdu_size);
        out_pdu = NULL;

        rcv_rsp_cnt = 0;
        rval = isns_ok;
        for (;;) {
                bytes_received = isns_rcv_pdu(so, &in_pdu, &in_pdu_size);
                ASSERT(bytes_received >= (size_t)0);
                if (bytes_received == 0) {
                        ASSERT(in_pdu == NULL);
                        ASSERT(in_pdu_size == 0);
                        rval = isns_rcv_msg_err;
                        break;
                }

                ASSERT(in_pdu != NULL);
                ASSERT(in_pdu_size > 0);

                if (ntohs(in_pdu->xid) != xid) {
                        rcv_rsp_cnt++;
                        if (rcv_rsp_cnt < MAX_RCV_RSP_COUNT) {
                                continue;
                        } else {
                                /* Exceed maximum receive count. */
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                rval = isns_no_rsp_rcvd;
                                break;
                        }
                }

                rsp_status = isns_process_dev_attr_dereg_rsp(in_pdu);
                if (rsp_status != ISNS_RSP_SUCCESSFUL) {
                        rval = isns_op_failed;
                }
                kmem_free(in_pdu, in_pdu_size);
                in_pdu = NULL;
                break;
        }

        if (rval != isns_ok) {
                iscsi_net->close(so);
                return (rval);
        }

        /* Always deregister SCN */
        out_pdu_size = isns_create_scn_dereg_pdu(
            node_name,
            &xid, &out_pdu);
        if (out_pdu_size == 0) {
                iscsi_net->close(so);
                return (isns_create_msg_err);
        }

        ASSERT(out_pdu != NULL);
        ASSERT(out_pdu_size > 0);

        if (isns_send_pdu(so, out_pdu) != 0) {
                iscsi_net->close(so);
                kmem_free(out_pdu, out_pdu_size);
                out_pdu = NULL;
                return (isns_send_msg_err);
        }

        /* Done with the out PDU - free it */
        kmem_free(out_pdu, out_pdu_size);
        out_pdu = NULL;

        rcv_rsp_cnt = 0;
        for (;;) {
                bytes_received = isns_rcv_pdu(so, &in_pdu, &in_pdu_size);
                ASSERT(bytes_received >= (size_t)0);
                if (bytes_received == 0) {
                        ASSERT(in_pdu == NULL);
                        ASSERT(in_pdu_size == 0);
                        rval = isns_rcv_msg_err;
                        break;
                }

                ASSERT(in_pdu != NULL);
                ASSERT(in_pdu_size > 0);

                if (ntohs(in_pdu->xid) != xid) {
                        rcv_rsp_cnt++;
                        if (rcv_rsp_cnt < MAX_RCV_RSP_COUNT) {
                                continue;
                        } else {
                                /* Exceed maximum receive count. */
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                rval = isns_no_rsp_rcvd;
                                break;
                        }
                }

                rsp_status = isns_process_scn_dereg_rsp(in_pdu);
                if (rsp_status != ISNS_RSP_SUCCESSFUL) {
                        rval = isns_op_failed;
                }
                kmem_free(in_pdu, in_pdu_size);
                in_pdu = NULL;
                break;
        }

        iscsi_net->close(so);

        return (rval);
}

static
isns_status_t
do_isns_query(boolean_t is_query_all_nodes_b,
        uint8_t *lhba_handle,
        uint8_t *target_node_name,
        uint8_t *source_node_name,
        uint8_t *source_node_alias,
        uint32_t source_node_type,
        isns_portal_group_list_t **pg_list)
{
        int i, j, k;
        int combined_num_of_pgs, combined_pg_lst_sz,
            isns_svr_lst_sz,
            tmp_pg_list_sz,
            tmp_pg_lists_sz;
        iscsi_addr_list_t *isns_server_addr_list = NULL;
        isns_portal_group_t *pg;
        isns_portal_group_list_t *combined_pg_list,
            *tmp_pg_list, **tmp_pg_lists;
        isns_status_t qry_stat, combined_qry_stat;

        /* Look up the iSNS Server address(es) based on the specified ISID */
        if (discover_isns_server(lhba_handle, &isns_server_addr_list) !=
            ISNS_OK) {
                *pg_list = NULL;
                return (isns_no_svr_found);
        }
        if (isns_server_addr_list->al_out_cnt == 0) {
                isns_svr_lst_sz = sizeof (iscsi_addr_list_t);
                kmem_free(isns_server_addr_list, isns_svr_lst_sz);
                isns_server_addr_list = NULL;
                *pg_list = NULL;
                return (isns_no_svr_found);
        }

        /*
         * isns_server_addr_list->al_out_cnt should not be zero by the
         * time it comes to this point.
         */
        tmp_pg_lists_sz = isns_server_addr_list->al_out_cnt *
            sizeof (isns_portal_group_list_t *);
        tmp_pg_lists = (isns_portal_group_list_t **)kmem_zalloc(
            tmp_pg_lists_sz, KM_SLEEP);
        combined_num_of_pgs = 0;
        combined_qry_stat = isns_ok;
        for (i = 0; i < isns_server_addr_list->al_out_cnt; i++) {
                if (is_query_all_nodes_b) {
                        qry_stat = do_isns_dev_attr_query_all_nodes(
                            &isns_server_addr_list->al_addrs[i],
                            source_node_name,
                            source_node_alias,
                            &tmp_pg_list);
                } else {
                        qry_stat = do_isns_dev_attr_query_one_node(
                            &isns_server_addr_list->al_addrs[i],
                            target_node_name,
                            source_node_name,
                            source_node_alias,
                            source_node_type,
                            &tmp_pg_list);
                }

                /* Record the portal group list retrieved from this server. */
                tmp_pg_lists[i] = tmp_pg_list;
                if (tmp_pg_list != NULL) {
                        combined_num_of_pgs += tmp_pg_list->pg_out_cnt;
                }

                if (qry_stat == isns_ok) {
                        if (combined_qry_stat != isns_ok) {
                                combined_qry_stat = isns_op_partially_failed;
                        }
                } else {
                        if (combined_qry_stat != isns_op_partially_failed) {
                                if (combined_qry_stat == isns_ok && i > 0) {
                                        combined_qry_stat =
                                            isns_op_partially_failed;
                                } else {
                                        combined_qry_stat = qry_stat;
                                }
                        }
                }

                if (is_query_all_nodes_b == B_FALSE) {
                        if (qry_stat == isns_ok) {
                                /*
                                 * Break out of the loop if we already got
                                 * the node information for one node.
                                 */
                                break;
                        }
                }
        }

        /* Merge the retrieved portal lists */
        combined_pg_lst_sz = sizeof (isns_portal_group_list_t);
        if (combined_num_of_pgs > 0) {
                combined_pg_lst_sz += (combined_num_of_pgs - 1) *
                    sizeof (isns_portal_group_t);
        }
        combined_pg_list = (isns_portal_group_list_t *)kmem_zalloc(
            combined_pg_lst_sz, KM_SLEEP);

        combined_pg_list->pg_out_cnt = combined_num_of_pgs;
        k = 0;
        for (i = 0; i < isns_server_addr_list->al_out_cnt; i++) {
                if (tmp_pg_lists[i] == NULL) {
                        continue;
                }
                for (j = 0; j < tmp_pg_lists[i]->pg_out_cnt; j++) {
                        pg = &(combined_pg_list->pg_list[k]);
                        bcopy(&(tmp_pg_lists[i]->pg_list[j]),
                            pg, sizeof (isns_portal_group_t));
                        k++;
                }
                tmp_pg_list_sz = sizeof (isns_portal_group_list_t);
                if (tmp_pg_lists[i]->pg_out_cnt > 0) {
                        tmp_pg_list_sz += (tmp_pg_lists[i]->pg_out_cnt - 1) *
                            sizeof (isns_portal_group_t);
                }
                kmem_free(tmp_pg_lists[i], tmp_pg_list_sz);
                tmp_pg_lists[i] = NULL;
        }
        kmem_free(tmp_pg_lists, tmp_pg_lists_sz);
        tmp_pg_lists = NULL;

        isns_svr_lst_sz = sizeof (iscsi_addr_list_t);
        if (isns_server_addr_list->al_out_cnt > 0) {
                isns_svr_lst_sz += (sizeof (iscsi_addr_t) *
                    (isns_server_addr_list->al_out_cnt - 1));
        }
        kmem_free(isns_server_addr_list, isns_svr_lst_sz);
        isns_server_addr_list = NULL;

        DTRACE_PROBE1(list, isns_portal_group_list_t *, combined_pg_list);

        *pg_list = combined_pg_list;
        return (combined_qry_stat);
}

static
isns_status_t
do_isns_dev_attr_query_all_nodes(iscsi_addr_t *isns_server_addr,
        uint8_t *node_name,
        uint8_t *node_alias,
        isns_portal_group_list_t **pg_list)
{
        int bytes_received;
        int rcv_rsp_cnt = 0;
        int rsp_status;
        uint16_t xid, seq_id = 0, func_id;
        isns_pdu_t *in_pdu, *out_pdu;
        isns_pdu_mult_payload_t *combined_pdu = NULL, *old_combined_pdu = NULL;
        isns_status_t qry_stat;
        size_t out_pdu_size = 0, in_pdu_size = 0;
        size_t old_combined_pdu_size = 0, combined_pdu_size = 0;
        void *so = NULL;
        uint8_t *payload_ptr;

        /* Initialize */
        *pg_list = NULL;

        so = isns_open(isns_server_addr);
        if (so == NULL) {
                /* Log a message and return */
                return (isns_open_conn_err);
        }

        /*
         * Then, ask for all PG attributes. Filter the non-target nodes.
         */
        out_pdu_size = isns_create_dev_attr_qry_target_nodes_pdu(
            node_name, node_alias, &xid, &out_pdu);
        if (out_pdu_size == 0) {
                iscsi_net->close(so);
                return (isns_create_msg_err);
        }

        ASSERT(out_pdu != NULL);
        ASSERT(out_pdu_size > 0);

        if (isns_send_pdu(so, out_pdu) != 0) {
                iscsi_net->close(so);
                kmem_free(out_pdu, out_pdu_size);
                out_pdu = NULL;
                return (isns_send_msg_err);
        }

        /* Done with the out PDU - free it */
        kmem_free(out_pdu, out_pdu_size);
        out_pdu = NULL;

        rcv_rsp_cnt = 0;
        qry_stat = isns_ok;
        for (;;) {
                uint16_t flags;

                bytes_received = isns_rcv_pdu(so, &in_pdu, &in_pdu_size);
                ASSERT(bytes_received >= 0);
                if (bytes_received == 0) {
                        ASSERT(in_pdu == NULL);
                        ASSERT(in_pdu_size == 0);
                        qry_stat = isns_rcv_msg_err;
                        break;
                }

                ASSERT(in_pdu != NULL);
                ASSERT(in_pdu_size > 0);

                /*
                 * make sure we are processing the right transaction id
                 */
                if (ntohs(in_pdu->xid) != xid) {
                        rcv_rsp_cnt++;
                        if (rcv_rsp_cnt < MAX_RCV_RSP_COUNT) {
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                continue;
                        } else {
                                /* Exceed maximum receive count. */
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                qry_stat = isns_no_rsp_rcvd;
                                break;
                        }
                }

                /*
                 * check to see if FIRST and LAST PDU flag is set
                 * if they are both set, then this response only has one
                 * pdu and we can process the pdu
                 */
                flags = in_pdu->flags;
                if (((flags & ISNS_FLAG_FIRST_PDU) == ISNS_FLAG_FIRST_PDU) &&
                    ((flags & ISNS_FLAG_LAST_PDU) == ISNS_FLAG_LAST_PDU)) {
                        rsp_status =
                            isns_process_dev_attr_qry_target_nodes_pdu(
                            isns_server_addr,
                            in_pdu->func_id,
                            (isns_resp_t *)in_pdu->payload,
                            (size_t)in_pdu->payload_len,
                            pg_list);
                        kmem_free(in_pdu, in_pdu_size);
                        in_pdu = NULL;
                        break;
                }
                /*
                 * this pdu is part of a multi-pdu response.  save off the
                 * the payload of this pdu and continue processing
                 */
                if ((flags & ISNS_FLAG_FIRST_PDU) == ISNS_FLAG_FIRST_PDU) {
                        /* This is the first pdu, make sure sequence ID is 0 */
                        if (in_pdu->seq != 0) {
                                cmn_err(CE_NOTE, "isns query response invalid: "
                                    "first pdu is not sequence ID 0");
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                return (isns_op_failed);
                        }
                        seq_id = 0;

                        /* create new pdu and copy in data from old pdu */
                        combined_pdu_size = ISNSP_MULT_PAYLOAD_HEADER_SIZE +
                            in_pdu->payload_len;
                        combined_pdu = (isns_pdu_mult_payload_t *)kmem_zalloc(
                            combined_pdu_size, KM_SLEEP);
                        func_id = in_pdu->func_id;
                        combined_pdu->payload_len = in_pdu->payload_len;
                        bcopy(in_pdu->payload, combined_pdu->payload,
                            in_pdu->payload_len);

                        /* done with in_pdu, free it */
                        kmem_free(in_pdu, in_pdu_size);
                        in_pdu = NULL;
                } else {
                        seq_id++;
                        if (in_pdu->seq != seq_id) {
                                cmn_err(CE_NOTE, "isns query response invalid: "
                                    "Missing sequence ID %d from isns query "
                                    "response.", seq_id);
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                if (combined_pdu != NULL) {
                                        kmem_free(combined_pdu,
                                            combined_pdu_size);
                                        combined_pdu = NULL;
                                }
                                return (isns_op_failed);
                        }
                        /*
                         * if conbined_pdu_size is still zero, then we never
                         * processed the first pdu
                         */
                        if (combined_pdu_size == 0) {
                                cmn_err(CE_NOTE, "isns query response invalid: "
                                    "Did not receive first pdu.\n");
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                return (isns_op_failed);
                        }
                        /* save off the old combined pdu */
                        old_combined_pdu_size = combined_pdu_size;
                        old_combined_pdu = combined_pdu;

                        /*
                         * alloc a new pdu big enough to also hold the new
                         * pdu payload
                         */
                        combined_pdu_size += in_pdu->payload_len;
                        combined_pdu = (isns_pdu_mult_payload_t *)kmem_zalloc(
                            combined_pdu_size, KM_SLEEP);

                        /*
                         * copy the old pdu into the new allocated pdu buffer
                         * and append on the new pdu payload that we just
                         * received
                         */
                        bcopy(old_combined_pdu, combined_pdu,
                            old_combined_pdu_size);

                        payload_ptr = combined_pdu->payload +
                            combined_pdu->payload_len;
                        combined_pdu->payload_len += in_pdu->payload_len;
                        bcopy(in_pdu->payload, payload_ptr,
                            in_pdu->payload_len);

                        /* free in_pdu and old_combined_pdu */
                        kmem_free(in_pdu, in_pdu_size);
                        kmem_free(old_combined_pdu, old_combined_pdu_size);
                        in_pdu = NULL;
                        old_combined_pdu = NULL;
                }
                /*
                 * check to see if this is the LAST pdu.
                 * if it is, we can process it and move on
                 * otherwise continue to wait for the next pdu
                 */
                if ((flags & ISNS_FLAG_LAST_PDU) == ISNS_FLAG_LAST_PDU) {
                        rsp_status =
                            isns_process_dev_attr_qry_target_nodes_pdu(
                            isns_server_addr,
                            func_id,
                            (isns_resp_t *)combined_pdu->payload,
                            combined_pdu->payload_len,
                            pg_list);
                        kmem_free(combined_pdu, combined_pdu_size);
                        combined_pdu = NULL;
                        break;
                }
        }
        if (rsp_status != ISNS_RSP_SUCCESSFUL) {
                qry_stat = isns_op_failed;
        }

        iscsi_net->close(so);

        return (qry_stat);
}

/* ARGSUSED */
static
isns_status_t
do_isns_dev_attr_query_one_node(iscsi_addr_t *isns_server_addr,
        uint8_t *target_node_name,
        uint8_t *source_node_name,
        uint8_t *source_node_alias,
        uint32_t source_node_type,
        isns_portal_group_list_t **pg_list)
{
        int bytes_received;
        int rcv_rsp_cnt;
        int rsp_status;
        isns_pdu_t *in_pdu, *out_pdu;
        isns_status_t rval;
        size_t out_pdu_size = 0, in_pdu_size = 0;
        uint16_t xid;
        void *so = NULL;

        /* Obtain the list of target type storage nodes first */
        out_pdu_size = isns_create_dev_attr_qry_one_pg_pdu(
            target_node_name, source_node_name, &xid, &out_pdu);
        if (out_pdu_size == 0) {
                return (isns_create_msg_err);
        }

        ASSERT(out_pdu != NULL);
        ASSERT(out_pdu_size > 0);

        so = isns_open(isns_server_addr);
        if (so == NULL) {
                /* Log a message and return */
                kmem_free(out_pdu, out_pdu_size);
                out_pdu = NULL;
                return (isns_open_conn_err);
        }

        if (isns_send_pdu(so, out_pdu) != 0) {
                iscsi_net->close(so);
                kmem_free(out_pdu, out_pdu_size);
                out_pdu = NULL;
                return (isns_send_msg_err);
        }

        /* Done with the out PDU - free it */
        kmem_free(out_pdu, out_pdu_size);
        out_pdu = NULL;

        rcv_rsp_cnt = 0;
        rval = isns_ok;
        for (;;) {
                bytes_received = isns_rcv_pdu(so, &in_pdu, &in_pdu_size);
                ASSERT(bytes_received >= 0);
                if (bytes_received == 0) {
                        ASSERT(in_pdu == NULL);
                        ASSERT(in_pdu_size == 0);
                        rval = isns_rcv_msg_err;
                        break;
                }

                ASSERT(in_pdu != NULL);
                ASSERT(in_pdu_size > 0);

                if (ntohs(in_pdu->xid) != xid) {
                        rcv_rsp_cnt++;
                        if (rcv_rsp_cnt < MAX_RCV_RSP_COUNT) {
                                continue;
                        } else {
                                /* Exceed maximum receive count. */
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                rval = isns_no_rsp_rcvd;
                                break;
                        }
                }

                rsp_status = isns_process_dev_attr_qry_target_nodes_pdu(
                    isns_server_addr, in_pdu->func_id,
                    (isns_resp_t *)in_pdu->payload, (size_t)in_pdu->payload_len,
                    pg_list);
                if (rsp_status != ISNS_RSP_SUCCESSFUL) {
                        rval = isns_op_failed;
                }
                kmem_free(in_pdu, in_pdu_size);
                in_pdu = NULL;
                break;
        }

        iscsi_net->close(so);

        return (rval);
}

static
void
*isns_open(iscsi_addr_t *isns_server_addr)
{
        int rval = 0;
        union {
                struct sockaddr sin;
                struct sockaddr_in s_in4;
                struct sockaddr_in6 s_in6;
        } sa_rsvr = { 0 };
        void *so;
        struct sockaddr_in6     t_addr;
        socklen_t               t_addrlen;

        bzero(&t_addr, sizeof (struct sockaddr_in6));
        t_addrlen = sizeof (struct sockaddr_in6);
        if (isns_server_addr->a_addr.i_insize == sizeof (struct in_addr)) {
                /* IPv4 */
                sa_rsvr.s_in4.sin_family = AF_INET;
                sa_rsvr.s_in4.sin_port = htons(isns_server_addr->a_port);
                sa_rsvr.s_in4.sin_addr.s_addr =
                    isns_server_addr->a_addr.i_addr.in4.s_addr;

                /* Create socket */
                so = iscsi_net->socket(AF_INET, SOCK_STREAM, 0);
        } else {
                /* IPv6 */
                sa_rsvr.s_in6.sin6_family = AF_INET6;
                bcopy(&(isns_server_addr->a_addr.i_addr.in6),
                    sa_rsvr.s_in6.sin6_addr.s6_addr,
                    sizeof (struct in6_addr));
                sa_rsvr.s_in6.sin6_port = htons(isns_server_addr->a_port);
                /* Create socket */
                so = iscsi_net->socket(AF_INET6, SOCK_STREAM, 0);
        }

        if (so == NULL) {
                return (NULL);
        }

        rval = iscsi_net->connect(so, &sa_rsvr.sin,
            (isns_server_addr->a_addr.i_insize == sizeof (struct in_addr)) ?
            sizeof (struct sockaddr_in) :
            sizeof (struct sockaddr_in6), 0, 0);

        if (rval != 0) {
                /* Flag value 2 indicates both cantsend and cantrecv */
                iscsi_net->shutdown(so, 2);
                iscsi_net->close(so);
                return (NULL);
        }

        (void) iscsi_net->getsockname(so, (struct sockaddr *)&t_addr,
            &t_addrlen);

        return (so);
}

static ssize_t
isns_send_pdu(void *socket, isns_pdu_t *pdu)
{
        int             iovlen = 0;
        iovec_t         iovec[ISNS_MAX_IOVEC];
        struct msghdr   msg;
        size_t          send_len;
        size_t          total_len = 0;

        ASSERT(iovlen < ISNS_MAX_IOVEC);
        iovec[iovlen].iov_base = (void *)pdu;
        iovec[iovlen].iov_len = (ISNSP_HEADER_SIZE);
        total_len += (ISNSP_HEADER_SIZE);
        iovlen++;

        ASSERT(iovlen < ISNS_MAX_IOVEC);
        iovec[iovlen].iov_base = (void *)pdu->payload;
        iovec[iovlen].iov_len = ntohs(pdu->payload_len);
        total_len += ntohs(pdu->payload_len);
        iovlen++;

        /* Initialization of the message header. */
        bzero(&msg, sizeof (msg));
        msg.msg_iov = &iovec[0];
        msg.msg_flags   = MSG_WAITALL;
        msg.msg_iovlen  = iovlen;

        send_len = iscsi_net->sendmsg(socket, &msg);
        return (send_len == total_len ? 0 : -1);
}

static
size_t
isns_rcv_pdu(void *socket, isns_pdu_t **pdu, size_t *pdu_size)
{
        int poll_cnt;
        iovec_t iovec[ISNS_MAX_IOVEC];
        isns_pdu_t *tmp_pdu_hdr;
        size_t bytes_received, total_bytes_received = 0, payload_len = 0;
        struct msghdr msg;
        uint8_t *tmp_pdu_data;

        /* Receive the header first */
        tmp_pdu_hdr = (isns_pdu_t *)kmem_zalloc(ISNSP_HEADER_SIZE, KM_SLEEP);
        (void) memset((char *)&iovec[0], 0, sizeof (iovec_t));
        iovec[0].iov_base = (void *)tmp_pdu_hdr;
        iovec[0].iov_len = ISNSP_HEADER_SIZE;

        /* Initialization of the message header. */
        bzero(&msg, sizeof (msg));
        msg.msg_iov = &iovec[0];
        msg.msg_flags = MSG_WAITALL;
        msg.msg_iovlen = 1;

        /* Poll and receive the packets. */
        poll_cnt = 0;
        do {
                bytes_received = iscsi_net->recvmsg(socket, &msg,
                    ISNS_RCV_TIMEOUT);
                if (bytes_received == 0) {
                        /* Not yet. Increase poll count and try again. */
                        poll_cnt++;
                        continue;
                } else {
                        /* OK data received. */
                        break;
                }
        } while (poll_cnt < ISNS_RCV_RETRY_MAX);

        DTRACE_PROBE2(isns_rcv_pdu_hdr_summary,
            int, poll_cnt, int, bytes_received);
        if (poll_cnt >= ISNS_RCV_RETRY_MAX) {
                kmem_free(tmp_pdu_hdr, ISNSP_HEADER_SIZE);
                *pdu = NULL;
                *pdu_size = 0;
                return (0);
        }
        if (bytes_received == 0 || bytes_received != ISNSP_HEADER_SIZE) {
                kmem_free(tmp_pdu_hdr, ISNSP_HEADER_SIZE);
                *pdu = NULL;
                *pdu_size = 0;
                return (0);
        }
        total_bytes_received += bytes_received;

        payload_len = ntohs(tmp_pdu_hdr->payload_len);
        DTRACE_PROBE1(isns_rcv_pdu_probe1, int, payload_len);
        /* Verify the received payload len is within limit */
        if (payload_len > ISNSP_MAX_PAYLOAD_SIZE) {
                kmem_free(tmp_pdu_hdr, ISNSP_HEADER_SIZE);
                *pdu = NULL;
                *pdu_size = 0;
                return (0);
        }

        /* Proceed to receive additional data. */
        tmp_pdu_data = kmem_zalloc(payload_len, KM_SLEEP);
        (void) memset((char *)&iovec[0], 0, sizeof (iovec_t));
        iovec[0].iov_base = (void *)tmp_pdu_data;
        iovec[0].iov_len = payload_len;

        /* Initialization of the message header. */
        bzero(&msg, sizeof (msg));
        msg.msg_iov = &iovec[0];
        msg.msg_flags   = MSG_WAITALL;
        msg.msg_iovlen  = 1;

        /* Poll and receive the rest of the PDU. */
        poll_cnt = 0;
        do {
                bytes_received = iscsi_net->recvmsg(socket, &msg,
                    ISNS_RCV_TIMEOUT);
                if (bytes_received == 0) {
                        /* Not yet. Increase poll count and try again. */
                        poll_cnt++;
                        continue;
                } else {
                        /* OK data received. */
                        break;
                }
        } while (poll_cnt < ISNS_RCV_RETRY_MAX);

        DTRACE_PROBE2(isns_rcv_pdu_data_summary,
            int, poll_cnt, int, bytes_received);

        if (poll_cnt >= ISNS_RCV_RETRY_MAX) {
                kmem_free(tmp_pdu_data, payload_len);
                kmem_free(tmp_pdu_hdr, ISNSP_HEADER_SIZE);
                *pdu = NULL;
                *pdu_size = 0;
                return (0);
        }
        if (bytes_received == 0 || bytes_received != payload_len) {
                kmem_free(tmp_pdu_data, payload_len);
                kmem_free(tmp_pdu_hdr, ISNSP_HEADER_SIZE);
                *pdu = NULL;
                *pdu_size = 0;
                return (0);
        }
        total_bytes_received += bytes_received;

        *pdu_size = ISNSP_HEADER_SIZE + payload_len;
        (*pdu) = (isns_pdu_t *)kmem_zalloc((*pdu_size), KM_SLEEP);
        (*pdu)->version = ntohs(tmp_pdu_hdr->version);
        (*pdu)->func_id = ntohs(tmp_pdu_hdr->func_id);
        (*pdu)->payload_len = payload_len;
        (*pdu)->flags = ntohs(tmp_pdu_hdr->flags);
        (*pdu)->xid = ntohs(tmp_pdu_hdr->xid);
        (*pdu)->seq = ntohs(tmp_pdu_hdr->seq);
        bcopy(tmp_pdu_data, &((*pdu)->payload), payload_len);

        kmem_free(tmp_pdu_data, payload_len);
        tmp_pdu_data = NULL;
        kmem_free(tmp_pdu_hdr, ISNSP_HEADER_SIZE);
        tmp_pdu_hdr = NULL;

        return (total_bytes_received);
}


/*
 * isns_create_dev_attr_reg_pdu - isns client registration pdu
 */
static size_t
isns_create_dev_attr_reg_pdu(
        uint8_t *node_name,
        uint8_t *node_alias,
        uint32_t node_type,
        uint16_t *xid_p,
        isns_pdu_t **out_pdu)
{
        in_port_t local_port;
        isns_pdu_t *pdu;
        size_t pdu_size, node_name_len, node_alias_len;
        uint16_t flags;
        boolean_t       rval        = B_FALSE;
        iscsi_addr_t    local_addr;

        ASSERT(node_name != NULL);
        ASSERT(node_alias != NULL);

        /* RFC 4171 section 6.1 - NULLs included in the length. */
        node_name_len = strlen((char *)node_name) + 1;
        node_alias_len = strlen((char *)node_alias) + 1;

        if (node_name_len == 1) {
                *out_pdu = NULL;
                return (0);
        }

        /*
         * Create DevAttrReg Message
         *
         * Enable the replace bit so that we can update
         * existing registration
         */
        flags = ISNS_FLAG_FIRST_PDU |
            ISNS_FLAG_LAST_PDU |
            ISNS_FLAG_REPLACE_REG;
        pdu_size = isns_create_pdu_header(ISNS_DEV_ATTR_REG, flags, &pdu);
        *xid_p = pdu->xid;

        /* Source attribute */
        if (isns_add_attr(pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
            node_name_len, node_name, 0) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /*
         * Message Key Attributes
         *
         * EID attribute - Section 6.2.1
         * This is required for re-registrations or Replace
         * Bit is ignored - Section 5.6.5.1
         */
        if (isns_add_attr(pdu, pdu_size, ISNS_EID_ATTR_ID,
            node_name_len, node_name, 0) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* Delimiter */
        if (isns_add_attr(pdu, pdu_size, ISNS_DELIMITER_ATTR_ID, 0, 0, 0)
            != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* EID attribute - Section 6.2.1 */
        if (isns_add_attr(pdu, pdu_size, ISNS_EID_ATTR_ID,
            node_name_len, node_name, 0) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* ENTITY Protocol - Section 6.2.2 */
        if (isns_add_attr(pdu, pdu_size, ISNS_ENTITY_PROTOCOL_ATTR_ID, 4,
            0, ISNS_ENTITY_PROTOCOL_ISCSI) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* iSCSI Name - Section 6.4.1 */
        if (isns_add_attr(pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
            node_name_len, node_name, 0) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* iSCSI Alias - Section 6.4.3 Optional */
        if (node_alias_len > 1) {
                if (isns_add_attr(pdu, pdu_size, ISNS_ISCSI_ALIAS_ATTR_ID,
                    node_alias_len, node_alias, 0) != 0) {
                        kmem_free(pdu, pdu_size);
                        *out_pdu = NULL;
                        return (0);
                }
        }

        /* iSCSI Node Type - Section 6.4.2 */
        if (isns_add_attr(pdu, pdu_size, ISNS_ISCSI_NODE_TYPE_ATTR_ID, 4,
            0, node_type) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        mutex_enter(&esi_scn_thr_mutex);
        if (instance_listening_so != NULL) {
                rval = find_listening_addr(&local_addr, instance_listening_so);
                if (rval == B_FALSE) {
                        kmem_free(pdu, pdu_size);
                        *out_pdu = NULL;
                        mutex_exit(&esi_scn_thr_mutex);
                        return (0);
                }
        } else {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                mutex_exit(&esi_scn_thr_mutex);
                return (0);
        }
        local_port = local_addr.a_port;
        /* Portal IP Address - Section 6.5.2 */
        if (isns_add_attr(pdu, pdu_size, ISNS_PORTAL_IP_ADDR_ATTR_ID, 16,
            &(local_addr.a_addr.i_addr.in4),
            local_addr.a_addr.i_insize) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                mutex_exit(&esi_scn_thr_mutex);
                return (0);
        }
        mutex_exit(&esi_scn_thr_mutex);

        /* Portal Port  - Section 6.5.3 */
        if (isns_add_attr(pdu, pdu_size, ISNS_PORTAL_PORT_ATTR_ID, 4, 0,
            local_port) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* SCN Port  - Section 6.3.7 */
        if (isns_add_attr(pdu, pdu_size, ISNS_SCN_PORT_ATTR_ID, 4, 0,
            local_port) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* ESI Port - Section 6.3.5 */
        if (isns_add_attr(pdu, pdu_size, ISNS_ESI_PORT_ATTR_ID, 4, 0,
            local_port) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        *out_pdu = pdu;
        return (pdu_size);
}

/*
 * isns_create_dev_dereg_pdu - Create an iSNS PDU for deregistration.
 */
static size_t
isns_create_dev_dereg_pdu(
        uint8_t *node_name,
        uint16_t *xid_p,
        isns_pdu_t **out_pdu)
{
        isns_pdu_t *pdu;
        size_t pdu_size, node_name_len;
        uint16_t flags;

        ASSERT(node_name != NULL);

        /* RFC 4171 section 6.1 - NULLs included in the length. */
        node_name_len = strlen((char *)node_name) + 1;

        if (node_name_len == 1) {
                *out_pdu = NULL;
                return (0);
        }

        /*
         * Create DevDeReg Message
         */
        flags = ISNS_FLAG_FIRST_PDU |
            ISNS_FLAG_LAST_PDU;
        pdu_size = isns_create_pdu_header(ISNS_DEV_DEREG, flags, &pdu);
        *xid_p = pdu->xid;

        /* Source attribute */
        if (isns_add_attr(pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
            node_name_len, node_name, 0) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* Delimiter */
        if (isns_add_attr(pdu, pdu_size, ISNS_DELIMITER_ATTR_ID, 0, 0, 0)
            != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* Entity Identifier */
        if (isns_add_attr(pdu, pdu_size, ISNS_EID_ATTR_ID,
            node_name_len, node_name, 0) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        *out_pdu = pdu;
        return (pdu_size);
}

/*
 * isns_create_dev_attr_target_nodes_pdu - get all accessible targets
 *
 * Querys for a list of all accessible target nodes for this
 * initiator.  Requests all required login information (name,
 * ip, port, tpgt).
 */
static size_t
isns_create_dev_attr_qry_target_nodes_pdu(
        uint8_t *node_name,
        uint8_t *node_alias,
        uint16_t *xid_p, isns_pdu_t **out_pdu)
{
        isns_pdu_t *pdu_p;
        uint16_t flags;
        size_t pdu_size, node_name_len;

        ASSERT(node_name != NULL);
        ASSERT(node_alias != NULL);

        /* RFC 4171 section 6.1 - NULLs included in the length. */
        node_name_len = strlen((char *)node_name) + 1;

        if (node_name_len == 1) {
                *out_pdu = NULL;
                return (0);
        }

        /* Create DevAttrQry Message */
        flags = ISNS_FLAG_FIRST_PDU |
            ISNS_FLAG_LAST_PDU;
        pdu_size = isns_create_pdu_header(ISNS_DEV_ATTR_QRY, flags, &pdu_p);
        *xid_p = pdu_p->xid;

        /* Source attribute */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
            node_name_len, node_name, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /*
         * Message Key Attribute
         *
         * iSCSI Node Type
         * Query target nodes only
         */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_ISCSI_NODE_TYPE_ATTR_ID,
            4, 0, ISNS_TARGET_NODE_TYPE) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* Delimiter */
        if (isns_add_attr(pdu_p, pdu_size,
            ISNS_DELIMITER_ATTR_ID, 0, 0, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* PG iSCSI Name - Zero length TLV */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_PG_ISCSI_NAME_ATTR_ID,
            0, 0, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* PG Portal IP Address - Zero length TLV */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_PG_PORTAL_IP_ADDR_ATTR_ID,
            0, 0, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* PG Portal Port - Zero length TLV */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_PG_PORTAL_PORT_ATTR_ID,
            0, 0, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* PG Portal Group Tag - Zero length TLV */
        if (isns_add_attr(pdu_p, pdu_size,
            ISNS_PG_TAG_ATTR_ID, 0, 0, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        *out_pdu = pdu_p;
        return (pdu_size);
}

static
size_t
isns_create_dev_attr_qry_one_pg_pdu(
        uint8_t *target_node_name,
        uint8_t *source_node_name,
        uint16_t *xid_p,
        isns_pdu_t **out_pdu)
{
        isns_pdu_t *pdu_p;
        uint16_t flags;
        size_t pdu_size, source_node_name_len, target_node_name_len;

        ASSERT(target_node_name != NULL);
        ASSERT(source_node_name != NULL);

        /* RFC 4171 section 6.1 - NULLs included in the length. */
        source_node_name_len = strlen((char *)source_node_name) + 1;
        target_node_name_len = strlen((char *)target_node_name) + 1;

        if (source_node_name_len == 1) {
                *out_pdu = NULL;
                return (0);
        }

        /* Create DevAttrQry message scoped to target_node_name */
        flags = ISNS_FLAG_FIRST_PDU |
            ISNS_FLAG_LAST_PDU;
        pdu_size = isns_create_pdu_header(ISNS_DEV_ATTR_QRY, flags, &pdu_p);
        *xid_p = pdu_p->xid;

        /* Source attribute */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
            source_node_name_len, source_node_name, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* Message key attribute */
        /* iSCSI Node Name */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
            target_node_name_len,
            target_node_name, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* Delimiter */
        if (isns_add_attr(pdu_p, pdu_size,
            ISNS_DELIMITER_ATTR_ID, 0, 0, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* PG iSCSI Name - Zero length TLV */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_PG_ISCSI_NAME_ATTR_ID,
            0, 0, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* PG Portal IP Address - Zero length TLV */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_PG_PORTAL_IP_ADDR_ATTR_ID,
            0, 0, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* PG Portal Port - Zero length TLV */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_PG_PORTAL_PORT_ATTR_ID,
            0, 0, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* PG Portal Group Tag - Zero length TLV */
        if (isns_add_attr(pdu_p, pdu_size,
            ISNS_PG_TAG_ATTR_ID, 0, 0, 0) != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        *out_pdu = pdu_p;
        return (pdu_size);
}

static
size_t
isns_create_scn_reg_pdu(
        uint8_t *node_name,
        uint8_t *node_alias,
        uint16_t *xid_p,
        isns_pdu_t **out_pdu)
{
        isns_pdu_t *pdu;
        size_t pdu_size, node_name_len;
        uint16_t flags;

        ASSERT(node_name != NULL);
        ASSERT(node_alias != NULL);

        /* RFC 4171 section 6.1 - NULLs included in the length. */
        node_name_len = strlen((char *)node_name) + 1;

        if (node_name_len == 1) {
                *out_pdu = NULL;
                return (0);
        }

        /* Create SCNReg Message */
        flags = ISNS_FLAG_FIRST_PDU |
            ISNS_FLAG_LAST_PDU;
        pdu_size = isns_create_pdu_header(ISNS_SCN_REG, flags, &pdu);
        *xid_p = pdu->xid;

        /* Source attribute */
        if (isns_add_attr(pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
            node_name_len, node_name, 0) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* Message attribute */
        if (isns_add_attr(pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
            node_name_len, node_name, 0) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* Delimiter */
        if (isns_add_attr(pdu, pdu_size, ISNS_DELIMITER_ATTR_ID, 0, 0, 0)
            != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* Operating attribute */
        if (isns_add_attr(pdu, pdu_size, ISNS_ISCSI_SCN_BITMAP_ATTR_ID,
            4,
            0,
        /*
         * Microsoft seems to not differentiate between init and
         * target. Hence, it makes no difference to turn on/off
         * the initiator/target bit.
         */
            ISNS_TARGET_SELF_INFO_ONLY |
            ISNS_OBJ_REMOVED |
            ISNS_OBJ_ADDED |
            ISNS_OBJ_UPDATED) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        *out_pdu = pdu;
        return (pdu_size);
}

static
size_t
isns_create_scn_dereg_pdu(
        uint8_t *node_name,
        uint16_t *xid_p,
        isns_pdu_t **out_pdu)
{
        isns_pdu_t *pdu;
        size_t pdu_size, node_name_len;
        uint16_t flags;

        ASSERT(node_name != NULL);

        /* RFC 4171 section 6.1 - NULLs included in the length. */
        node_name_len = strlen((char *)node_name) + 1;

        if (node_name_len == 1) {
                *out_pdu = NULL;
                return (0);
        }

        /* Create SCNReg Message */
        flags = ISNS_FLAG_FIRST_PDU |
            ISNS_FLAG_LAST_PDU;
        pdu_size = isns_create_pdu_header(ISNS_SCN_DEREG, flags, &pdu);
        *xid_p = pdu->xid;

        /* Source attribute */
        if (isns_add_attr(pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
            node_name_len, node_name, 0) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* Message attribute */
        if (isns_add_attr(pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
            node_name_len, node_name, 0) != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* Delimiter */
        if (isns_add_attr(pdu, pdu_size, ISNS_DELIMITER_ATTR_ID, 0, 0, 0)
            != 0) {
                kmem_free(pdu, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        /* No operating attribute */

        *out_pdu = pdu;
        return (pdu_size);
}

static
size_t
isns_create_esi_rsp_pdu(uint32_t rsp_status_code,
        isns_pdu_t *esi_pdu,
        uint16_t *xid_p,
        isns_pdu_t **out_pdu)
{
        isns_pdu_t *pdu_p;
        uint16_t flags;
        uint8_t *payload_ptr;
        uint32_t swapped_status_code = htonl(rsp_status_code);
        size_t pdu_size, payload_len = 0;

        /* Create ESIRsp Message */
        flags = ISNS_FLAG_FIRST_PDU |
            ISNS_FLAG_LAST_PDU;
        pdu_size = isns_create_pdu_header(ISNS_ESI_RSP, flags, &pdu_p);
        *xid_p = pdu_p->xid;

        payload_len = ntohs(pdu_p->payload_len);

        /* Status Code */
        payload_ptr = pdu_p->payload + payload_len;
        bcopy(&swapped_status_code, payload_ptr, 4);
        payload_len += 4;

        payload_ptr = pdu_p->payload + payload_len;
        if ((esi_pdu->payload_len) < ISNSP_MAX_PAYLOAD_SIZE) {
                bcopy(esi_pdu->payload, payload_ptr,
                    (esi_pdu->payload_len));
                payload_len += (esi_pdu->payload_len);
        } else {
                bcopy(esi_pdu->payload, payload_ptr, ISNSP_MAX_PAYLOAD_SIZE);
                payload_len += ISNSP_MAX_PAYLOAD_SIZE;
        }
        pdu_p->payload_len = htons(payload_len);

        /* Delimiter */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_DELIMITER_ATTR_ID, 0, 0, 0)
            != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        *out_pdu = pdu_p;
        return (pdu_size);
}

static
size_t
isns_create_scn_rsp_pdu(uint32_t rsp_status_code,
        isns_pdu_t *scn_pdu,
        uint16_t *xid_p,
        isns_pdu_t **out_pdu)
{
        isns_pdu_t *pdu_p;
        uint16_t flags;
        uint8_t *payload_ptr;
        uint32_t swapped_status_code = htonl(rsp_status_code);
        size_t pdu_size, payload_len = 0;

        /* Create SCNRsp Message */
        flags = ISNS_FLAG_FIRST_PDU |
            ISNS_FLAG_LAST_PDU;
        pdu_size = isns_create_pdu_header(ISNS_SCN_RSP, flags, &pdu_p);
        *xid_p = pdu_p->xid;

        payload_len = ntohs(pdu_p->payload_len);

        /* Status Code */
        payload_ptr = pdu_p->payload + payload_len;
        bcopy(&swapped_status_code, payload_ptr, 4);
        payload_len += 4;

        payload_ptr = pdu_p->payload + payload_len;
        if ((scn_pdu->payload_len) < ISNSP_MAX_PAYLOAD_SIZE) {
                bcopy(scn_pdu->payload, payload_ptr,
                    (scn_pdu->payload_len));
                payload_len += (scn_pdu->payload_len);
        } else {
                bcopy(scn_pdu->payload, payload_ptr, ISNSP_MAX_PAYLOAD_SIZE);
                payload_len += ISNSP_MAX_PAYLOAD_SIZE;
        }
        pdu_p->payload_len = htons(payload_len);

        /* Delimiter */
        if (isns_add_attr(pdu_p, pdu_size, ISNS_DELIMITER_ATTR_ID, 0, 0, 0)
            != 0) {
                kmem_free(pdu_p, pdu_size);
                *out_pdu = NULL;
                return (0);
        }

        *out_pdu = pdu_p;
        return (pdu_size);
}

static
uint32_t
isns_process_dev_attr_reg_rsp(isns_pdu_t *resp_pdu_p)
{
        isns_resp_t *resp_p;

        if (resp_pdu_p->func_id != ISNS_DEV_ATTR_REG_RSP) {
                /* If this happens the iSNS server may have a problem. */
                return (ISNS_RSP_MSG_FORMAT_ERROR);
        }

        /* Check response's status code */
        resp_p = (isns_resp_t *)resp_pdu_p->payload;
        if (ntohl(resp_p->status) != ISNS_RSP_SUCCESSFUL) {
                return (ntohl(resp_p->status));
        }

        return (ISNS_RSP_SUCCESSFUL);
}

static
uint32_t
isns_process_dev_attr_dereg_rsp(isns_pdu_t *resp_pdu_p)
{
        isns_resp_t *resp_p;

        if (resp_pdu_p->func_id != ISNS_DEV_DEREG_RSP) {
                /* If this happens the iSNS server may have a problem. */
                return (ISNS_RSP_MSG_FORMAT_ERROR);
        }

        /* Check response's status code */
        resp_p = (isns_resp_t *)resp_pdu_p->payload;
        if (ntohl(resp_p->status) != ISNS_RSP_SUCCESSFUL) {
                return (ntohl(resp_p->status));
        }

        return (ISNS_RSP_SUCCESSFUL);
}

static
uint32_t
isns_process_scn_reg_rsp(isns_pdu_t *resp_pdu_p)
{
        isns_resp_t *resp_p;

        ASSERT(resp_pdu_p != NULL);
        if (resp_pdu_p->func_id != ISNS_SCN_REG_RSP) {
                /* If this happens the iSNS server may have a problem. */
                return (ISNS_RSP_MSG_FORMAT_ERROR);
        }

        /* Check response's status code */
        resp_p = (isns_resp_t *)resp_pdu_p->payload;
        if (ntohl(resp_p->status) != ISNS_RSP_SUCCESSFUL) {
                return (ntohl(resp_p->status));
        }
        return (ISNS_RSP_SUCCESSFUL);
}

static
uint32_t
isns_process_scn_dereg_rsp(isns_pdu_t *resp_pdu_p)
{
        isns_resp_t *resp_p;

        ASSERT(resp_pdu_p != NULL);
        if (resp_pdu_p->func_id != ISNS_SCN_DEREG_RSP) {
                /* If this happens the iSNS server may have a problem. */
                return (ISNS_RSP_MSG_FORMAT_ERROR);
        }

        /* Check response's status code */
        resp_p = (isns_resp_t *)resp_pdu_p->payload;
        if (ntohl(resp_p->status) != ISNS_RSP_SUCCESSFUL) {
                return (ntohl(resp_p->status));
        }
        return (ISNS_RSP_SUCCESSFUL);
}

static
uint32_t
isns_process_dev_attr_qry_target_nodes_pdu(
        iscsi_addr_t *isns_server_addr, uint16_t payload_funcId,
        isns_resp_t *resp_p, size_t resp_len,
        isns_portal_group_list_t **pg_list)
{
        boolean_t done_b, found_delimiter_b, target_node_type_b;
        int num_of_pgs = 0, pg_sz, idx;
        isns_tlv_t *attr_tlv_p;
        uint8_t *data_p;
        uint32_t len, total_payload_len = 0;
        isns_portal_group_t *pg;
        uint8_t junk[IPV4_RSVD_BYTES];

        *pg_list = NULL;
        bzero(junk, IPV4_RSVD_BYTES);

        if (payload_funcId != ISNS_DEV_ATTR_QRY_RSP) {
                /* If this happens the iSNS server may have a problem. */
                return (ISNS_RSP_MSG_FORMAT_ERROR);
        }

        if (ntohl(resp_p->status) != ISNS_RSP_SUCCESSFUL) {
                return (ntohl(resp_p->status));
        }

        /*
         * If payload is smaller than the length of even 1 attribute
         * there is something wrong with the PDU.
         */
        if (resp_len < (ISNS_TLV_ATTR_ID_LEN +
            ISNS_TLV_ATTR_LEN_LEN)) {
                return (ISNS_RSP_MSG_FORMAT_ERROR);
        }

        /*
         * Expected DevAttrQryRsp message format:
         *
         * Status Code
         * iSCSI Node Type
         * Delimiter
         * PG iSCSI Name                [Optional]
         * PG Portal IP Address         [Optional]
         * PG Portal Port               [Optional]
         * PG Tag                       [Optional]
         * PG iSCSI Name                [Optional]
         * PG Portal IP Address         [Optional]
         * PG Portal Port               [Optional]
         * PG Tag                       [Optional]
         * .
         * .
         * .
         */
        data_p = resp_p->data;
        done_b = B_FALSE;
        found_delimiter_b = B_FALSE;
        num_of_pgs = 0;
        total_payload_len = sizeof (resp_p->status);
        /* Find out the number of entries retrieved */
        while (!done_b) {
                attr_tlv_p = (isns_tlv_t *)data_p;
                if (ntohl(attr_tlv_p->attr_id) == ISNS_DELIMITER_ATTR_ID) {
                        if (found_delimiter_b) {
                                done_b = B_TRUE;
                        } else {
                                found_delimiter_b = B_TRUE;
                        }
                } else if (ntohl(attr_tlv_p->attr_id) ==
                    ISNS_PG_TAG_ATTR_ID) {
                        if (ntohl(attr_tlv_p->attr_len) > 0) {
                                /*
                                 * Count only those iSCSI node that have a
                                 * non-NULL PGT value as valid Entity.
                                 * Per rfc4171 section 3.4 - If the PGT value
                                 * registered for a specified Portal and iSCSI
                                 * Node is NULL, or if no PGT value is
                                 * registered, then the Portal does not provide
                                 * access to that iSCSI Node in the Entity.
                                 */
                                num_of_pgs++;
                        }
                }
                len = ntohl(attr_tlv_p->attr_len);

                total_payload_len += (ISNS_TLV_ATTR_ID_LEN +
                    ISNS_TLV_ATTR_LEN_LEN + len);
                if (total_payload_len >= resp_len) {
                        done_b = B_TRUE;
                } else {
                        data_p += (ISNS_TLV_ATTR_ID_LEN +
                            ISNS_TLV_ATTR_LEN_LEN + len);
                }
        }

        pg_sz = sizeof (isns_portal_group_list_t);
        if (num_of_pgs > 0) {
                pg_sz += (num_of_pgs - 1) * sizeof (isns_portal_group_t);
        }
        DTRACE_PROBE1(isns_process_dev_attr_qry_target_nodes_pdu_pg_size,
            int, pg_sz);
        /*
         * Once we passed this point, if for any reason we need to return
         * because of a failure, we need to free the memory allocated for
         * the pg_list and nullify it.
         */
        *pg_list = (isns_portal_group_list_t *)kmem_zalloc(pg_sz, KM_SLEEP);
        (*pg_list)->pg_out_cnt = 0;

        /* Assign the isns_server information to all portal groups */
        for (idx = 0; idx < num_of_pgs; idx++) {
                pg = &((*pg_list)->pg_list[idx]);
                bcopy(&isns_server_addr->a_addr, &pg->isns_server_ip,
                    sizeof (iscsi_ipaddr_t));
                pg->isns_server_port = isns_server_addr->a_port;
        }

        data_p = resp_p->data;
        done_b = B_FALSE;
        found_delimiter_b = B_FALSE;
        total_payload_len = sizeof (resp_p->status);
        while (!done_b) {
                attr_tlv_p = (isns_tlv_t *)data_p;
                pg = &((*pg_list)->pg_list[(*pg_list)->pg_out_cnt]);
                switch (ntohl(attr_tlv_p->attr_id)) {
                        case ISNS_DELIMITER_ATTR_ID:
                                if (found_delimiter_b) {
                                        done_b = B_TRUE;
                                } else {
                                        found_delimiter_b = B_TRUE;
                                }
                                break;

                        case ISNS_PG_ISCSI_NAME_ATTR_ID:
                                target_node_type_b = B_TRUE;
                                bcopy(attr_tlv_p->attr_value,
                                    (char *)pg->pg_iscsi_name,
                                    ntohl(attr_tlv_p->attr_len) <
                                    ISCSI_MAX_NAME_LEN ?
                                    ntohl(attr_tlv_p->attr_len) :
                                    ISCSI_MAX_NAME_LEN);

                                DTRACE_PROBE1(isns_dev_attr_qry_process1,
                                    char *, (char *)pg->pg_iscsi_name);
                                break;

                        case ISNS_PG_PORTAL_IP_ADDR_ATTR_ID:
                                if (target_node_type_b) {
                                        /*
                                         * Section 6.3.1 - The Portal IP Address
                                         * is a 16-byte field that may contain
                                         * an IPv4 or IPv6 address. When this
                                         * field contains an IPv4 address, it
                                         * is stored as an IPv4-mapped IPv6
                                         * address
                                         */
                                        if (ntohl(attr_tlv_p->attr_len) != 16) {
#define STRING_AALR "address attribute length received "
#define STRING_FISE16 "from iSNS server, Expected = 16, "
                                                cmn_err(CE_NOTE, "Wrong IP "
                                                    STRING_AALR
                                                    STRING_FISE16
                                                    "Received = %d",
                                                    ntohl(
                                                    attr_tlv_p->attr_len));
                                                return (
                                                    ISNS_RSP_MSG_FORMAT_ERROR);
#undef STRING_AALR
#undef STRING_FISE16
                                        }

                                        /*
                                         * Section 6.3.1 and RFC 2373 state
                                         * that an IPv4 address will be denoted
                                         * by the 10 top bytes as all zero
                                         * followed by either 2 bytes of
                                         * 0x0000 or 0xFFFF The 0x0000 states
                                         * that the address is is IPv6 capable
                                         * and 0xFFFF states its not capable.
                                         */
                                        if ((bcmp(attr_tlv_p->attr_value, junk,
                                            IPV4_RSVD_BYTES) == 0) &&
                                            (((attr_tlv_p->attr_value[10] ==
                                            0x00) &&
                                            (attr_tlv_p->attr_value[11] ==
                                            0x00)) ||
                                            ((attr_tlv_p->attr_value[10] ==
                                            0xFF) &&
                                            (attr_tlv_p->attr_value[11] ==
                                            0xFF)))) {

                                                /* IPv4 */
                                                bcopy(attr_tlv_p->attr_value +
                                                    12, &pg->pg_ip_addr.u_ip4,
                                                    sizeof (struct in_addr));
                                                pg->insize =
                                                    sizeof (struct in_addr);
                                        } else {
                                                /* IPv6 */
                                                bcopy(attr_tlv_p->attr_value,
                                                    &pg->pg_ip_addr.u_ip6,
                                                    sizeof (struct in6_addr));
                                                pg->insize =
                                                    sizeof (struct in6_addr);
                                        }
                                }
                                break;

                        case ISNS_PG_PORTAL_PORT_ATTR_ID:
                                if (target_node_type_b) {
                                        pg->pg_port =
                                            ntohl(*(uint32_t *)
                                            (*attr_tlv_p).
                                            attr_value);
                                }

                                break;

                        case ISNS_PG_TAG_ATTR_ID:
                                if (target_node_type_b) {
                                        pg->pg_tag =
                                            ntohl(*(uint32_t *)
                                            (*attr_tlv_p).
                                            attr_value);
                                }
                                target_node_type_b = B_FALSE;
                                if (ntohl(attr_tlv_p->attr_len) > 0) {
                                        /*
                                         * Only the iSCSI node that has a
                                         * non-NULL PGT value is an valid
                                         * Entity.
                                         */
                                        (*pg_list)->pg_out_cnt++;
                                }
                                break;

                        default:
                                break;
                }

                len = ntohl(attr_tlv_p->attr_len);
                total_payload_len += (ISNS_TLV_ATTR_ID_LEN +
                    ISNS_TLV_ATTR_LEN_LEN + len);
                if ((total_payload_len >= resp_len) ||
                    ((*pg_list)->pg_out_cnt == num_of_pgs)) {
                        done_b = B_TRUE;
                } else {
                        data_p += (ISNS_TLV_ATTR_ID_LEN +
                            ISNS_TLV_ATTR_LEN_LEN + len);
                }
        }

        return (ISNS_RSP_SUCCESSFUL);
}

/* ARGSUSED */
static
uint32_t
isns_process_esi(isns_pdu_t *esi_pdu_p)
{
        /* There's nothing particular to process for ESI. */
        return (ISNS_RSP_SUCCESSFUL);
}

static
uint32_t
isns_process_scn(isns_pdu_t *scn_pdu_p, uint8_t *lhba_handle)
{
        boolean_t dest_attr_found_b;
        boolean_t done_b;
        boolean_t scn_type_found_b;
        isns_scn_callback_arg_t *scn_args_p;
        isns_tlv_t *attr_tlv_p;
        uint8_t *data_p;
        uint8_t *src_attr;
        uint32_t attr_eff_len, normalized_attr_len;
        uint32_t scn_type;
        uint32_t total_payload_len;
        void (*scn_callback_to_use)(void *);

        /* get the lhba_handle to use for the call back */
        scn_callback_to_use = scn_callback_lookup(lhba_handle);
        if (scn_callback_to_use == NULL) {
                return (ISNS_RSP_INTERNAL_ERROR);
        }

        dest_attr_found_b = B_FALSE;
        scn_type = 0;
        scn_type_found_b = B_FALSE;
        data_p = scn_pdu_p->payload;
        done_b = B_FALSE;
        total_payload_len = 0;
        src_attr = (uint8_t *)kmem_zalloc(ISCSI_MAX_NAME_LEN, KM_SLEEP);
        /*
         * Section 5.6.5.8 states an SCN can have more than one
         * source attribute.  Process all attributes until we
         * each process all the data or encounter the delimiter.
         */
        while (!done_b) {
                attr_tlv_p = (isns_tlv_t *)data_p;

                switch (ntohl(attr_tlv_p->attr_id)) {
                /* ISNS_ISCSI_NAME_ATTR_ID - attribute name */
                case ISNS_ISCSI_NAME_ATTR_ID:
                        attr_eff_len = strlen(
                            (char *)attr_tlv_p->attr_value) + 1;
                        /*
                         * The attribute length must be 4-byte aligned.
                         * Section 5.1.3, RFC 4171.
                         */
                        normalized_attr_len = (attr_eff_len % 4) == 0 ?
                            (attr_eff_len) :
                            (attr_eff_len + (4 - (attr_eff_len % 4)));
                        if (normalized_attr_len !=
                            ntohl(attr_tlv_p->attr_len)) {
                                /* This SCN is bad. */
                                kmem_free(src_attr, ISCSI_MAX_NAME_LEN);
                                return (ISNS_RSP_MSG_FORMAT_ERROR);
                        }

                        /* Check if this was the Destination Attribute */
                        if ((dest_attr_found_b == B_TRUE) &&
                            (scn_type_found_b == B_TRUE)) {
                                bzero(src_attr, ISCSI_MAX_NAME_LEN);
                                bcopy(attr_tlv_p->attr_value,
                                    (char *)src_attr,
                                    ntohl(attr_tlv_p->attr_len) <
                                    ISCSI_MAX_NAME_LEN ?
                                    ntohl(attr_tlv_p->attr_len) :
                                    ISCSI_MAX_NAME_LEN);

                                /* allocate new callback structure */
                                scn_args_p =
                                    (isns_scn_callback_arg_t *)kmem_zalloc(
                                    sizeof (isns_scn_callback_arg_t),
                                    KM_SLEEP);
                                scn_args_p->scn_type = ntohl(scn_type);
                                bcopy(src_attr, scn_args_p->source_key_attr,
                                    sizeof (scn_args_p->source_key_attr));

                                /* Dispatch the callback to process the SCN */
                                mutex_enter(&scn_taskq_mutex);
                                if (scn_taskq != NULL) {
                                        (void) ddi_taskq_dispatch(scn_taskq,
                                            scn_callback_to_use,
                                            scn_args_p, DDI_SLEEP);
                                }
                                mutex_exit(&scn_taskq_mutex);
                        } else {
                                /* Skip Destination Attribute */
                                dest_attr_found_b = B_TRUE;
                        }
                        break;

                /* ISNS_ISCSI_SCN_BITMAP_ATTR_ID - change type */
                case ISNS_ISCSI_SCN_BITMAP_ATTR_ID:
                        /*
                         * Determine the type of action to take for this SCN.
                         */
                        scn_type_found_b = B_TRUE;
                        bcopy(&(attr_tlv_p->attr_value), &scn_type, 4);
                        break;

                /* ISNS_DELIMITER_ATTR_ID - end of the payload of a message */
                case ISNS_DELIMITER_ATTR_ID:
                        done_b = B_TRUE;
                        break;
                }

                if (done_b == B_FALSE) {
                        total_payload_len += ntohl(attr_tlv_p->attr_len) +
                            ISNS_TLV_ATTR_ID_LEN + ISNS_TLV_ATTR_LEN_LEN;
                        if ((total_payload_len >= scn_pdu_p->payload_len) ||
                            (total_payload_len > ISNSP_MAX_PAYLOAD_SIZE)) {
                                /* No more Attributes to process */
                                done_b = B_TRUE;
                        } else {
                                if (scn_pdu_p->payload_len -
                                    total_payload_len <=
                                    ISNS_TLV_ATTR_ID_LEN +
                                    ISNS_TLV_ATTR_LEN_LEN) {
                                        /*
                                         * The rest of the data in the PDU
                                         * is less than the size of a valid
                                         * iSNS TLV. This next attribute
                                         * probably spans across the PDU
                                         * boundary. For now, do not
                                         * process it further.
                                         */
                                        done_b = B_TRUE;
                                } else {
                                        /* Advance to the next Attribute */
                                        data_p += (ISNS_TLV_ATTR_ID_LEN +
                                            ISNS_TLV_ATTR_LEN_LEN +
                                            ntohl(attr_tlv_p->attr_len));
                                }
                        }
                }
        }

        kmem_free(src_attr, ISCSI_MAX_NAME_LEN);
        return (ISNS_RSP_SUCCESSFUL);
}

static
size_t
isns_create_pdu_header(uint16_t func_id, uint16_t flags, isns_pdu_t **pdu)
{
        /*
         * It should be ok to assume ISNSP_MAX_PDU_SIZE is large enough
         * since we are creating our own PDU which is fully under our control.
         */
        size_t pdu_size = ISNSP_MAX_PDU_SIZE;

        *pdu = (isns_pdu_t *)kmem_zalloc(pdu_size, KM_SLEEP);
        (void) memset((*pdu), 0, pdu_size);
        (*pdu)->version = htons((uint16_t)ISNSP_VERSION);
        (*pdu)->func_id = htons((uint16_t)func_id);
        (*pdu)->payload_len = htons(0);
        (*pdu)->flags = htons((uint16_t)(flags | ISNS_FLAG_CLIENT));
        (*pdu)->xid = htons(create_xid());
        (*pdu)->seq = htons(0);

        return (pdu_size);
}

static
int
isns_add_attr(isns_pdu_t *pdu,
        size_t max_pdu_size,
        uint32_t attr_id,
        uint32_t attr_len,
        void *attr_data,
        uint32_t attr_numeric_data)
{
        isns_tlv_t *attr_tlv;
        uint8_t *payload_ptr;
        uint16_t payload_len;
        uint32_t normalized_attr_len;
        uint64_t attr_tlv_len;

        /* The attribute length must be 4-byte aligned. Section 5.1.3. */
        normalized_attr_len = (attr_len % 4) == 0 ? (attr_len) :
            (attr_len + (4 - (attr_len % 4)));
        attr_tlv_len = ISNS_TLV_ATTR_ID_LEN
            + ISNS_TLV_ATTR_LEN_LEN
            + normalized_attr_len;
        /* Check if we are going to exceed the maximum PDU length. */
        payload_len = ntohs(pdu->payload_len);
        if ((payload_len + attr_tlv_len) > max_pdu_size) {
                return (1);
        }

        attr_tlv = (isns_tlv_t *)kmem_zalloc(attr_tlv_len, KM_SLEEP);

        attr_tlv->attr_id = htonl(attr_id);

        switch (attr_id) {
                case ISNS_DELIMITER_ATTR_ID:
                break;

                case ISNS_PORTAL_IP_ADDR_ATTR_ID:
                case ISNS_PG_PORTAL_IP_ADDR_ATTR_ID:
                        if (attr_numeric_data == sizeof (in_addr_t)) {
                                /* IPv4 */
                                attr_tlv->attr_value[10] = 0xFF;
                                attr_tlv->attr_value[11] = 0xFF;
                                bcopy(attr_data, ((attr_tlv->attr_value) + 12),
                                    sizeof (in_addr_t));
                        } else if (attr_numeric_data == sizeof (in6_addr_t)) {
                                /* IPv6 */
                                bcopy(attr_data, attr_tlv->attr_value,
                                    sizeof (in6_addr_t));
                        } else if (attr_numeric_data == 0) {
                                /* EMPTY */
                                /* Do nothing */
                        } else {
                                kmem_free(attr_tlv, attr_tlv_len);
                                attr_tlv = NULL;
                                return (1);
                        }
                break;

                case ISNS_EID_ATTR_ID:
                case ISNS_ISCSI_NAME_ATTR_ID:
                case ISNS_ISCSI_ALIAS_ATTR_ID:
                case ISNS_PG_ISCSI_NAME_ATTR_ID:
                        bcopy((char *)attr_data,
                            attr_tlv->attr_value,
                            attr_len);
                break;

                default:
                        switch (normalized_attr_len) {
                                case 0:
                                break;

                                case 4:
                                        *(uint32_t *)attr_tlv->attr_value =
                                            htonl(attr_numeric_data);
                                break;

                                case 8:
                                        *(uint64_t *)attr_tlv->attr_value =
                                            BE_64((uint64_t)
                                            attr_numeric_data);
                                break;
                        }
        }

        attr_tlv->attr_len = htonl(normalized_attr_len);
        /*
         * Convert the network byte ordered payload length to host byte
         * ordered for local address calculation.
         */
        payload_len = ntohs(pdu->payload_len);
        payload_ptr = pdu->payload + payload_len;
        bcopy(attr_tlv, payload_ptr, attr_tlv_len);
        payload_len += attr_tlv_len;

        /*
         * Convert the host byte ordered payload length back to network
         * byte ordered - it's now ready to be sent on the wire.
         */
        pdu->payload_len = htons(payload_len);

        kmem_free(attr_tlv, attr_tlv_len);
        attr_tlv = NULL;

        return (0);
}

/* ARGSUSED */
static
void
isns_service_esi_scn(iscsi_thread_t *thread, void *arg)
{
        int clnt_len;
        isns_async_thread_arg_t *larg;
        isns_pdu_t *in_pdu;
        size_t bytes_received, in_pdu_size = 0;
        uint8_t *lhba_handle;
        struct sockaddr_in6      t_addr;
        socklen_t               t_addrlen;
        union {
                struct sockaddr sin;
                struct sockaddr_in s_in4;
                struct sockaddr_in6 s_in6;
        } clnt_addr = { 0 };
        union {
                struct sockaddr_in      soa4;
                struct sockaddr_in6     soa6;
        } local_conn_prop;
        void *listening_so, *connecting_so;

        larg = (isns_async_thread_arg_t *)arg;
        listening_so = larg->listening_so;
        lhba_handle = larg->lhba_handle;

        /* Done using the argument - free it */
        kmem_free(larg, sizeof (*larg));
        bzero(&t_addr, sizeof (struct sockaddr_in6));
        t_addrlen = sizeof (struct sockaddr_in6);

        (void) iscsi_net->getsockname(listening_so,
            (struct sockaddr *)&t_addr, &t_addrlen);
        if (t_addrlen <= sizeof (local_conn_prop)) {
                bcopy(&t_addr, &local_conn_prop, t_addrlen);
        }

        if (iscsi_net->listen(listening_so, 5) < 0) {
                iscsi_net->close(listening_so);
        }

        for (;;) {
                int rval;
                isns_pdu_t *out_pdu;
                size_t out_pdu_size;

                clnt_len = sizeof (clnt_addr);

                /* Blocking call */
                connecting_so = iscsi_net->accept(
                    listening_so, &clnt_addr.sin, &clnt_len);

                mutex_enter(&esi_scn_thr_mutex);
                if (esi_scn_thr_to_shutdown == B_TRUE) {
                        /* Terminate the thread if instructed to do so. */
                        mutex_exit(&esi_scn_thr_mutex);
                        return;
                }
                mutex_exit(&esi_scn_thr_mutex);

                if (connecting_so == NULL) {
                        iscsi_net->close(listening_so);
                        continue;
                }

                bytes_received = isns_rcv_pdu(connecting_so, &in_pdu,
                    &in_pdu_size);
                if (in_pdu == NULL) {
                        continue;
                }
                if (bytes_received == 0) {
                        continue;
                }

                switch (in_pdu->func_id) {
                case ISNS_ESI:
                case ISNS_SCN:
                        if (in_pdu->func_id == ISNS_ESI) {
                                rval = isns_process_esi(in_pdu);
                                out_pdu_size = isns_create_esi_rsp_pdu(
                                    rval,
                                    in_pdu,
                                    &xid,
                                    &out_pdu);
                        } else if (in_pdu->func_id == ISNS_SCN) {
                                rval = isns_process_scn(in_pdu,
                                    lhba_handle);
                                out_pdu_size = isns_create_scn_rsp_pdu(
                                    rval,
                                    in_pdu,
                                    &xid,
                                    &out_pdu);
                        } else {
                                /*
                                 * Ignore all traffics other than
                                 * ESI and SCN.
                                 */
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                continue;
                        }

                        if (out_pdu_size == 0) {
                                kmem_free(in_pdu, in_pdu_size);
                                in_pdu = NULL;
                                continue;
                        }

                        (void) isns_send_pdu(connecting_so, out_pdu);

                        kmem_free(out_pdu, out_pdu_size);
                        out_pdu = NULL;
                        kmem_free(in_pdu, in_pdu_size);
                        in_pdu = NULL;

                        iscsi_net->close(connecting_so);
                        break;

                default:
                        kmem_free(in_pdu, in_pdu_size);
                        in_pdu = NULL;
                        continue;
                }
        }
}

static
boolean_t
find_listening_addr(iscsi_addr_t *local_addr, void *listening_so)
{
        union {
                struct sockaddr_in      soa4;
                struct sockaddr_in6     soa6;
        } local_conn_prop = { 0 };

        struct sockaddr_in6     t_addr;
        socklen_t               t_addrlen;

        if (local_addr == NULL || listening_so == NULL) {
                return (B_FALSE);
        }

        bzero(&t_addr, sizeof (struct sockaddr_in6));
        t_addrlen = sizeof (struct sockaddr_in6);

        (void) iscsi_net->getsockname(listening_so, (struct sockaddr *)&t_addr,
            &t_addrlen);
        if (t_addrlen > sizeof (local_conn_prop)) {
                return (B_FALSE);
        }
        bcopy(&t_addr, &local_conn_prop, t_addrlen);
        if (local_conn_prop.soa4.sin_family == AF_INET) {
                local_addr->a_addr.i_addr.in4.s_addr =
                    local_conn_prop.soa4.sin_addr.s_addr;
                local_addr->a_addr.i_insize = sizeof (in_addr_t);
        } else if (local_conn_prop.soa4.sin_family == AF_INET6) {
                /* Currently, IPv6 is not supported */
                return (B_FALSE);
        } else {
                return (B_FALSE);
        }

        local_addr->a_port = ntohs(local_conn_prop.soa4.sin_port);

        return (B_TRUE);
}

static
boolean_t
find_local_portal(iscsi_addr_t *isns_server_addr,
    iscsi_addr_t **local_addr, void **listening_so)
{
        union {
                struct sockaddr_in      soa4;
                struct sockaddr_in6     soa6;
        } local_conn_prop = { 0 };
        union {
                struct sockaddr sin;
                struct sockaddr_in s_in4;
                struct sockaddr_in6 s_in6;
        } serv_addr = { 0 };
        void *so;
        struct sockaddr_in6     t_addr;
        socklen_t               t_addrlen;

        if (listening_so == NULL) {
                return (B_FALSE);
        }

        if (local_addr != NULL) {
                *local_addr = NULL;
        }

        *listening_so = NULL;
        bzero(&t_addr, sizeof (struct sockaddr_in6));
        t_addrlen = sizeof (struct sockaddr_in6);

        /*
         * Determine the local IP address.
         */
        if (local_addr != NULL) {
                so = isns_open(isns_server_addr);
                if (so == NULL) {
                        return (B_FALSE);
                }

                iscsi_net->getsockname(so,
                    (struct sockaddr *)&t_addr, &t_addrlen);
                if (t_addrlen > sizeof (local_conn_prop)) {
                        iscsi_net->close(so);
                        return (B_FALSE);
                }

                bcopy(&t_addr, &local_conn_prop, t_addrlen);
                t_addrlen = sizeof (struct sockaddr_in6);
                if (local_conn_prop.soa4.sin_family == AF_INET) {
                        *local_addr =
                            (iscsi_addr_t *)kmem_zalloc(sizeof (iscsi_addr_t),
                            KM_SLEEP);
                        (*local_addr)->a_addr.i_addr.in4.s_addr =
                            local_conn_prop.soa4.sin_addr.s_addr;
                        (*local_addr)->a_addr.i_insize = sizeof (in_addr_t);
                } else if (local_conn_prop.soa4.sin_family == AF_INET6) {
                        /* Currently, IPv6 is not supported */
                        return (B_FALSE);
                } else {
                        iscsi_net->close(so);
                        return (B_FALSE);
                }

                iscsi_net->close(so);
        }
        /*
         * Determine the local IP address. (End)
         */

        serv_addr.s_in4.sin_family = AF_INET;
        /*
         * Use INADDR_ANY to accept connections from any of the connected
         * networks.
         */
        serv_addr.s_in4.sin_addr.s_addr = htonl(INADDR_ANY);
        /*
         * Use port number 0 to allow the system to assign a unique unused
         * port.
         */
        serv_addr.s_in4.sin_port = htons(0);

        so = iscsi_net->socket(AF_INET, SOCK_STREAM, 0);
        if (so == NULL) {
                if (local_addr != NULL && (*local_addr != NULL)) {
                        kmem_free((*local_addr), sizeof (iscsi_addr_t));
                        *local_addr = NULL;
                }
                return (B_FALSE);
        }

        if (iscsi_net->bind(so, &serv_addr.sin,
                sizeof (struct sockaddr), 0, 0) < 0) {
                if (local_addr != NULL && (*local_addr != NULL)) {
                        kmem_free((*local_addr), sizeof (iscsi_addr_t));
                        *local_addr = NULL;
                }
                iscsi_net->close(so);
                return (B_FALSE);
        }

        if (local_addr != NULL && (*local_addr != NULL)) {
                (void) iscsi_net->getsockname(so, (struct sockaddr *)&t_addr,
                    &t_addrlen);
                if (t_addrlen <= sizeof (local_conn_prop)) {
                        bcopy(&t_addr, &local_conn_prop, t_addrlen);
                        (*local_addr)->a_port =
                            ntohs(local_conn_prop.soa4.sin_port);
                } else {
                        (*local_addr)->a_port = ISNS_DEFAULT_ESI_SCN_PORT;
                }
        }

        *listening_so = so;

        return (B_TRUE);
}

/* ARGSUSED */
static
void
(*scn_callback_lookup(uint8_t *lhba_handle))(void *)
{
        /*
         * When we support multiple HBA instance we will use lhba_handle
         * to look up the associated SCN callback. For now, we only support
         * one HBA instance therefore we always return the same SCN callback.
         */
        return (scn_callback_p);
}

static
uint16_t
create_xid()
{
        return (xid++ % MAX_XID);
}

static
void
esi_scn_thr_cleanup()
{
        boolean_t       unblock_esi_scn_thr_b           = B_FALSE;
        iscsi_addr_t    local_addr;

        mutex_enter(&esi_scn_thr_mutex);
        if (esi_scn_thr_to_shutdown == B_FALSE) {

                /* Instruct the ESI/SCN to shut itself down. */
                esi_scn_thr_to_shutdown = B_TRUE;
                if (instance_listening_so != NULL &&
                    (find_listening_addr(&local_addr,
                    instance_listening_so) == B_TRUE)) {
                        isns_pdu_t *out_pdu;
                        size_t out_pdu_size;
                        void *connecting_so;

                        /*
                         * Open a connection to the local address and send
                         * a dummy header to unblock the accept call so that
                         * the ESI/SCN thread has a chance to terminate
                         * itself.
                         */
                        connecting_so = isns_open(&local_addr);
                        if (connecting_so == NULL) {
                                unblock_esi_scn_thr_b = B_FALSE;
                                esi_scn_thr_to_shutdown = B_FALSE;
                        } else {
                                out_pdu_size = isns_create_pdu_header(0,
                                    ISNS_FLAG_FIRST_PDU |
                                    ISNS_FLAG_LAST_PDU,
                                    &out_pdu);
                                if (isns_send_pdu(connecting_so,
                                    out_pdu) != 0) {
                                        unblock_esi_scn_thr_b = B_FALSE;
                                        esi_scn_thr_to_shutdown = B_FALSE;
                                } else {
                                        unblock_esi_scn_thr_b = B_TRUE;
                                }
                                iscsi_net->close(connecting_so);
                                kmem_free(out_pdu, out_pdu_size);
                                out_pdu = NULL;
                        }
                }

                if (unblock_esi_scn_thr_b == B_TRUE) {
                        mutex_exit(&esi_scn_thr_mutex);
                        (void) iscsi_thread_stop(esi_scn_thr_id);
                        iscsi_thread_destroy(esi_scn_thr_id);
                        mutex_enter(&esi_scn_thr_mutex);
                        esi_scn_thr_id = NULL;

                        /*
                         * Shutdown and close the listening socket.
                         */
                        iscsi_net->shutdown(instance_listening_so, 2);
                        iscsi_net->close(instance_listening_so);
                        instance_listening_so = NULL;
                }
        }
        mutex_exit(&esi_scn_thr_mutex);
}