--- /dev/null Fri Apr 4 13:31:04 2008 +++ new/src/sun_nws/comstar/port_providers/iscsit/src/iscsit_login.c Fri Apr 4 13:31:04 2008 @@ -0,0 +1,2262 @@ +/* + * 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 src/sun_nws/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 src/sun_nws/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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "@(#)iscsit_login.c 1.15 08/03/25 SMI" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* networking stuff */ +#include /* networking stuff */ +#include /* offsetof */ +#include /* ASSERT3U */ +#include + +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + list_node_t le_ctx_node; + iscsit_login_event_t le_ctx_event; + idm_pdu_t *le_pdu; +} login_event_ctx_t; + +#ifndef TRUE +#define TRUE B_TRUE +#endif + +#ifndef FALSE +#define FALSE B_FALSE +#endif + +extern char *iscsit_ils_name[]; +extern char *iscsit_ile_name[]; + +static void +login_sm_complete(void *ict_void); + +static void +login_sm_event_dispatch(iscsit_conn_login_t *lsm, iscsit_conn_t *ict, + login_event_ctx_t *ctx); + +static void +login_sm_init(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_waiting(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_processing(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_responding(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_responded(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_ffp(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_done(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_error(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_new_state(iscsit_conn_t *ict, login_event_ctx_t *ctx, + iscsit_login_state_t new_state); + +static void +login_sm_abort(iscsit_conn_t *ict); + +static void +login_sm_send_ack(iscsit_conn_t *ict, idm_pdu_t *pdu); + +static idm_status_t +login_sm_validate_ack(iscsit_conn_t *ict, idm_pdu_t *pdu); + +static boolean_t +login_sm_is_last_response(iscsit_conn_t *ict); + +static void +login_sm_handle_initial_login(iscsit_conn_t *ict, idm_pdu_t *pdu); + +static void +login_sm_send_next_response(iscsit_conn_t *ict); + +static void +login_rej_resp_cb(idm_pdu_t *pdu, idm_status_t status); + +static void +login_sm_process_request(iscsit_conn_t *ict); + +static idm_status_t +login_sm_req_pdu_check(iscsit_conn_t *ict, idm_pdu_t *pdu); + +static idm_status_t +login_sm_pdu_list_to_nvlist(iscsit_conn_t *ict); + +static idm_status_t +login_sm_process_nvlist(iscsit_conn_t *ict); + +static idm_status_t +login_sm_check_security(iscsit_conn_t *ict); + +static idm_status_t +login_sm_build_login_response(iscsit_conn_t *ict); + +static void +login_sm_ffp_actions(iscsit_conn_t *ict); + +static idm_status_t +login_sm_validate_initial_parameters(iscsit_conn_t *ict); + +static idm_status_t +login_sm_session_bind(iscsit_conn_t *ict); + +static idm_status_t +login_sm_session_register(iscsit_conn_t *ict); + +static kv_status_t +iscsit_handle_key(iscsit_conn_t *ict, nvpair_t *nvp, char *nvp_name); + +static kv_status_t +iscsit_handle_common_key(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_handle_security_key(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_reply_security_key(iscsit_conn_t *ict); + +static kv_status_t +iscsit_handle_operational_key(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_reply_constant(iscsit_conn_t *ict, + const char *nvp_name, const char *text); + +static kv_status_t +iscsit_handle_digest(iscsit_conn_t *ict, nvpair_t *choices, + const iscsi_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_handle_boolean(iscsit_conn_t *ict, nvpair_t *nvp, boolean_t value, + const iscsi_kv_xlate_t *ikvx, boolean_t iscsit_value); + +static kv_status_t +iscsit_handle_numerical(iscsit_conn_t *ict, nvpair_t *nvp, uint64_t value, + const iscsi_kv_xlate_t *ikvx, + uint64_t iscsi_min_value, uint64_t iscsi_max_value, + uint64_t iscsit_max_value); + +static void +iscsit_process_negotiated_values(iscsit_conn_t *ict); + +idm_status_t +iscsit_login_sm_init(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + int rc; + + bzero(lsm, sizeof (iscsit_conn_login_t)); + + rc = nvlist_alloc(&lsm->icl_negotiated_values, NV_UNIQUE_NAME, + KM_NOSLEEP); + if (rc != 0) { + return (IDM_STATUS_FAIL); + } + + /* + * Pre-allocating a login response PDU means we will always be + * able to respond to a login request -- even if we can't allocate + * a data buffer to hold the text responses we can at least send + * a login failure. + */ + lsm->icl_login_rej_resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), 0); + idm_pdu_init(lsm->icl_login_rej_resp, ict->ict_ic, NULL, + &login_rej_resp_cb); + bzero(lsm->icl_login_rej_resp->isp_hdr, sizeof (iscsi_hdr_t)); + + mutex_init(&lsm->icl_mutex, NULL, MUTEX_DEFAULT, NULL); + list_create(&lsm->icl_login_events, sizeof (login_event_ctx_t), + offsetof(login_event_ctx_t, le_ctx_node)); + list_create(&lsm->icl_pdu_list, sizeof (idm_pdu_t), + offsetof(idm_pdu_t, isp_client_lnd)); + + lsm->icl_login_state = ILS_LOGIN_INIT; + lsm->icl_login_last_state = ILS_LOGIN_INIT; + + /* + * Initialize operational parameters to default values. Anything + * we don't specifically negotiate stays at the default. + */ + ict->ict_op.op_discovery_session = B_FALSE; + ict->ict_op.op_initial_r2t = ISCSI_DEFAULT_INITIALR2T; + ict->ict_op.op_immed_data = ISCSI_DEFAULT_IMMEDIATE_DATA; + ict->ict_op.op_data_pdu_in_order = ISCSI_DEFAULT_DATA_PDU_IN_ORDER; + ict->ict_op.op_data_sequence_in_order = + ISCSI_DEFAULT_DATA_SEQUENCE_IN_ORDER; + ict->ict_op.op_max_connections = ISCSI_DEFAULT_MAX_CONNECTIONS; + ict->ict_op.op_max_recv_data_segment_length = + ISCSI_DEFAULT_MAX_RECV_SEG_LEN; + ict->ict_op.op_max_burst_length = ISCSI_DEFAULT_MAX_BURST_LENGTH; + ict->ict_op.op_first_burst_length = ISCSI_DEFAULT_FIRST_BURST_LENGTH; + ict->ict_op.op_default_time_2_wait = ISCSI_DEFAULT_TIME_TO_WAIT; + ict->ict_op.op_default_time_2_retain = ISCSI_DEFAULT_TIME_TO_RETAIN; + ict->ict_op.op_max_outstanding_r2t = ISCSI_DEFAULT_MAX_OUT_R2T; + ict->ict_op.op_error_recovery_level = + ISCSI_DEFAULT_ERROR_RECOVERY_LEVEL; + + return (IDM_STATUS_SUCCESS); +} + +static void +login_rej_resp_cb(idm_pdu_t *pdu, idm_status_t status) +{ + /* XXX Not sure what we need to do here, if anything. Login event? */ +} + +static void +login_resp_complete_cb(idm_pdu_t *pdu, idm_status_t status) +{ + iscsit_conn_t *ict = pdu->isp_private; + + /* XXX Not sure what we need to do here, if anything. */ + if (login_sm_is_last_response(ict) == B_TRUE) { + iscsit_login_sm_event(ict, ILE_LOGIN_RESP_COMPLETE, NULL); + } +} + +void +iscsit_login_sm_fini(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + + list_destroy(&lsm->icl_pdu_list); + list_destroy(&lsm->icl_login_events); + mutex_destroy(&lsm->icl_mutex); + + idm_pdu_free(lsm->icl_login_rej_resp); + if (lsm->icl_login_resp != NULL) { + idm_pdu_free(lsm->icl_login_resp); + } + + nvlist_free(lsm->icl_negotiated_values); +} + +void +iscsit_login_sm_event(iscsit_conn_t *ict, iscsit_login_event_t event, + idm_pdu_t *pdu) +{ + mutex_enter(&ict->ict_login_sm.icl_mutex); + iscsit_login_sm_event_locked(ict, event, pdu); + mutex_exit(&ict->ict_login_sm.icl_mutex); +} + +void +iscsit_login_sm_event_locked(iscsit_conn_t *ict, iscsit_login_event_t event, + idm_pdu_t *pdu) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + login_event_ctx_t *ctx; + + ctx = kmem_zalloc(sizeof (*ctx), KM_NOSLEEP); + + if (ctx == NULL) { + /* + * Cleanup any resources associated with our current login + * process and send a login reject. + */ + login_sm_abort(ict); + return; + } + + ctx->le_ctx_event = event; + ctx->le_pdu = pdu; + + list_insert_tail(&lsm->icl_login_events, ctx); + /* + * Use the icl_busy flag to keep the state machine single threaded. + * This also serves as recursion avoidance since this flag will + * always be set if we call login_sm_event from within the + * state machine code. + */ + if (!lsm->icl_busy) { + lsm->icl_busy = B_TRUE; + while (!list_is_empty(&lsm->icl_login_events)) { + ctx = list_head(&lsm->icl_login_events); + list_remove(&lsm->icl_login_events, ctx); + mutex_exit(&lsm->icl_mutex); + login_sm_event_dispatch(lsm, ict, ctx); + mutex_enter(&lsm->icl_mutex); + } + lsm->icl_busy = B_FALSE; + + /* + * When the state machine reaches ILS_LOGIN_DONE or + * ILS_LOGIN_ERROR state the login process has completed + * and it's time to cleanup. The state machine code will + * mark itself "complete" when this happens. + * + * To protect against spurious events (which shouldn't + * happen) set icl_busy again. + */ + if (lsm->icl_login_complete) { + lsm->icl_busy = B_TRUE; + (void) taskq_dispatch( + iscsit_global.global_dispatch_taskq, + login_sm_complete, ict, DDI_SLEEP); + } + } +} + +static void +login_sm_complete(void *ict_void) +{ + iscsit_conn_t *ict = ict_void; + + /* + * State machine has run to completion, release state machine resources + */ + iscsit_login_sm_fini(ict); +} + +static void +login_sm_event_dispatch(iscsit_conn_login_t *lsm, iscsit_conn_t *ict, + login_event_ctx_t *ctx) +{ + idm_pdu_t *pdu = ctx->le_pdu; /* Only valid for some events */ + + /* State independent actions */ + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + /* Perform basic sanity checks on the header */ + if (login_sm_req_pdu_check(ict, pdu) != IDM_STATUS_SUCCESS) { + iscsit_login_sm_event_locked(ict, ILE_LOGIN_ERROR, + NULL); + idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); + return; + } + break; + default: + break; + } + + /* State dependent actions */ + switch (lsm->icl_login_state) { + case ILS_LOGIN_INIT: + login_sm_init(ict, ctx); + break; + case ILS_LOGIN_WAITING: + login_sm_waiting(ict, ctx); + break; + case ILS_LOGIN_PROCESSING: + login_sm_processing(ict, ctx); + break; + case ILS_LOGIN_RESPONDING: + login_sm_responding(ict, ctx); + break; + case ILS_LOGIN_RESPONDED: + login_sm_responded(ict, ctx); + break; + case ILS_LOGIN_FFP: + login_sm_ffp(ict, ctx); + break; + case ILS_LOGIN_DONE: + login_sm_done(ict, ctx); + break; + case ILS_LOGIN_ERROR: + login_sm_error(ict, ctx); + break; + } + + kmem_free(ctx, sizeof (*ctx)); +} + +static void +login_sm_init(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + idm_pdu_t *pdu; + + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + pdu = ctx->le_pdu; + + /* + * This is the first login PDU we've received so use + * it to build the login response template and set our CSG. + */ + login_sm_handle_initial_login(ict, pdu); + + /* + * Accumulate all the login PDU's that make up this + * request on a queue. + */ + mutex_enter(&ict->ict_login_sm.icl_mutex); + list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu); + mutex_exit(&ict->ict_login_sm.icl_mutex); + + if (pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE) { + login_sm_send_ack(ict, pdu); + login_sm_new_state(ict, ctx, ILS_LOGIN_WAITING); + } else { + login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING); + } + break; + case ILE_LOGIN_CONN_ERROR: + case ILE_LOGIN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + default: + ASSERT(0); + } +} + +static void +login_sm_waiting(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + idm_pdu_t *pdu; + + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + pdu = ctx->le_pdu; + mutex_enter(&ict->ict_login_sm.icl_mutex); + list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu); + mutex_exit(&ict->ict_login_sm.icl_mutex); + if (!(pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE)) { + login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING); + } else { + login_sm_send_ack(ict, pdu); + } + break; + case ILE_LOGIN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + default: + ASSERT(0); + } +} + +static void +login_sm_processing(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RESP_READY: + login_sm_new_state(ict, ctx, ILS_LOGIN_RESPONDING); + break; + case ILE_LOGIN_RCV: + idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS); + /*FALLTHROUGH*/ + case ILE_LOGIN_CONN_ERROR: + case ILE_LOGIN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + default: + ASSERT(0); + } +} + +static void +login_sm_responding(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + idm_pdu_t *pdu; + + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + pdu = ctx->le_pdu; + /* + * We should only be in "responding" state if we have not + * sent the last PDU of a multi-PDU login response sequence. + * In that case we expect this received PDU to be an + * acknowledgement from the initiator (login PDU with C + * bit cleared and no data). If it's the acknowledgement + * we are expecting then we send the next PDU in the login + * response sequence. Otherwise it's a protocol error and + * the login fails. + */ + if (login_sm_validate_ack(ict, pdu) == IDM_STATUS_SUCCESS) { + login_sm_send_next_response(ict); + } else { + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + } + idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); + break; + case ILE_LOGIN_FFP: + login_sm_new_state(ict, ctx, ILS_LOGIN_FFP); + break; + case ILE_LOGIN_RESP_COMPLETE: + login_sm_new_state(ict, ctx, ILS_LOGIN_RESPONDED); + break; + case ILE_LOGIN_CONN_ERROR: + case ILE_LOGIN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + default: + ASSERT(0); + } +} + +static void +login_sm_responded(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + idm_pdu_t *pdu; + + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + pdu = ctx->le_pdu; + mutex_enter(&ict->ict_login_sm.icl_mutex); + list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu); + mutex_exit(&ict->ict_login_sm.icl_mutex); + if (pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE) { + login_sm_send_ack(ict, pdu); + login_sm_new_state(ict, ctx, ILS_LOGIN_WAITING); + } else { + login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING); + } + break; + case ILE_LOGIN_CONN_ERROR: + case ILE_LOGIN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + default: + ASSERT(0); + } +} + +static void +login_sm_ffp(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RESP_COMPLETE: + login_sm_new_state(ict, ctx, ILS_LOGIN_DONE); + break; + case ILE_LOGIN_CONN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + default: + ASSERT(0); + } + +} + +static void +login_sm_done(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + /* Terminal state, we should get no events */ + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + /* + * We've already processed everything we're going to + * process. Drop any additional login PDU's. + */ + idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS); + break; + case ILE_LOGIN_CONN_ERROR: + /* Don't care */ + break; + default: + ASSERT(0); + } + +} + +static void +login_sm_error(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + /* + * We've already processed everything we're going to + * process. Drop any additional login PDU's. + */ + idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS); + break; + case ILE_LOGIN_CONN_ERROR: + /* Don't care */ + break; + default: + ASSERT(0); + } +} + +static void +login_sm_new_state(iscsit_conn_t *ict, login_event_ctx_t *ctx, + iscsit_login_state_t new_state) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + + /* + * Validate new state + */ + ASSERT(new_state != ILS_UNDEFINED); + ASSERT3U(new_state, <, ILS_MAX_STATE); + + new_state = (new_state < ILS_MAX_STATE) ? + new_state : ILS_UNDEFINED; + + cmn_err(CE_NOTE, "login_sm_new_state: conn %p, evt %s (%d), " + "%s (%d) --> %s (%d)\n", + ict->ict_ic, iscsit_ile_name[ctx->le_ctx_event], ctx->le_ctx_event, + iscsit_ils_name[lsm->icl_login_state], lsm->icl_login_state, + iscsit_ils_name[new_state], new_state); + DTRACE_PROBE3(login__state__change, + iscsit_conn_t *, ict, login_event_ctx_t *, ctx, + iscsit_login_state_t, new_state); + + mutex_enter(&lsm->icl_mutex); + lsm->icl_login_last_state = lsm->icl_login_state; + lsm->icl_login_state = new_state; + mutex_exit(&lsm->icl_mutex); + + switch (lsm->icl_login_state) { + case ILS_LOGIN_WAITING: + /* Do nothing, waiting for more login PDU's */ + break; + case ILS_LOGIN_PROCESSING: + /* All login PDU's received, process login request */ + login_sm_process_request(ict); + break; + case ILS_LOGIN_RESPONDING: + login_sm_send_next_response(ict); + break; + case ILS_LOGIN_RESPONDED: + /* Do nothing, waiting for another login request */ + break; + case ILS_LOGIN_FFP: + login_sm_ffp_actions(ict); + break; + case ILS_LOGIN_DONE: + case ILS_LOGIN_ERROR: + /* Free login SM resources */ + lsm->icl_login_complete = B_TRUE; + break; + case ILS_LOGIN_INIT: /* Initial state, can't return */ + default: + ASSERT(0); + /*NOTREACHED*/ + } +} + +static void +login_sm_abort(iscsit_conn_t *ict) +{ + ASSERT(0); /* XXX */ +} + +static void +login_sm_send_ack(iscsit_conn_t *ict, idm_pdu_t *pdu) +{ + ASSERT(0); /* XXX */ +} + +static idm_status_t +login_sm_validate_ack(iscsit_conn_t *ict, idm_pdu_t *pdu) +{ + ASSERT(0); /* XXX */ + return (IDM_STATUS_FAIL); +} + +static boolean_t +login_sm_is_last_response(iscsit_conn_t *ict) +{ + /* + * XXX For the prototype we insist that responses fit in a single + * PDU so this is always true + */ + return (B_TRUE); +} + + +static void +login_sm_handle_initial_login(iscsit_conn_t *ict, idm_pdu_t *pdu) +{ + iscsi_login_hdr_t *lh_req = (iscsi_login_hdr_t *)pdu->isp_hdr; + iscsi_login_rsp_hdr_t *lh_resp = (iscsi_login_rsp_hdr_t *) + ict->ict_login_sm.icl_login_rej_resp->isp_hdr; + + /* + * First login PDU, this connection should not have a sesssion + * associated. + */ + ASSERT(ict->ict_sess == NULL); + + /* + * Save off TSIH and ISID for later use in finding a session + */ + ict->ict_login_sm.icl_cmdsn = ntohl(lh_req->cmdsn); + ict->ict_login_sm.icl_tsih = ntohs(lh_req->tsid); + bcopy(lh_req->isid, ict->ict_login_sm.icl_isid, ISCSI_ISID_LEN); + + /* + * We'll need the CID as well + */ + ict->ict_cid = ntohs(lh_req->cid); + + /* + * Set CSG based on the login PDU. We already validated that + * the CSG value is acceptable (for example we're not trying + * to go directly to FFP) in login_sm_req_pdu_check(). + */ + ict->ict_login_sm.icl_login_csg = + ISCSI_LOGIN_CURRENT_STAGE(lh_req->flags); + ict->ict_login_sm.icl_login_nsg = + ISCSI_LOGIN_NEXT_STAGE(lh_req->flags); + + /* + * Initialize header for login reject response. This will also + * be copied for use as a template for other login responses + */ + lh_resp->opcode = ISCSI_OP_LOGIN_RSP; + lh_resp->max_version = ISCSIT_MAX_VERSION; + + /* + * We already validated that we can support one of the initiator's + * versions in login_sm_req_pdu_check(). + */ + if (ISCSIT_MAX_VERSION >= lh_req->min_version) { + lh_resp->active_version = + MIN(lh_req->max_version, ISCSIT_MAX_VERSION); + } else { + ASSERT(ISCSIT_MAX_VERSION <= lh_req->max_version); + lh_resp->active_version = ISCSIT_MAX_VERSION; + } + + lh_resp->hlength = 0; /* No AHS */ + bcopy(lh_req->isid, lh_resp->isid, ISCSI_ISID_LEN); + lh_resp->tsid = lh_req->tsid; + lh_resp->itt = lh_req->itt; + + /* + * StatSn, ExpCmdSn and MaxCmdSn will be set immediately before + * transmission + */ +} + +static void +login_sm_send_next_response(iscsit_conn_t *ict) +{ + idm_pdu_t *pdu = ict->ict_login_sm.icl_login_resp; + iscsi_login_rsp_hdr_t *lh_resp = (iscsi_login_rsp_hdr_t *)pdu->isp_hdr; + + /* + * XXX For the prototype we insist that responses fit in a single + * PDU so "next response" is the only response. What we probably + * should do in the final product is to have a single text buffer + * that contains the entire formatted response, then use a PDU + * to send one segment of that response at a time. The PDU will + * be used like a sliding window into the text buffer. + */ + + /* + * Fill in header values + */ + hton24(lh_resp->dlength, pdu->isp_datalen); + + /* + * If this is going to be the last PDU of a login response + * that moves us to FFP then generate the ILE_LOGIN_FFP event. + */ + if ((lh_resp->flags & ISCSI_FLAG_LOGIN_TRANSIT) && + (ISCSI_LOGIN_NEXT_STAGE(lh_resp->flags) == + ISCSI_FULL_FEATURE_PHASE) && + !(lh_resp->flags & ISCSI_FLAG_LOGIN_CONTINUE)) { + iscsit_login_sm_event_locked(ict, ILE_LOGIN_FFP, NULL); + } + + iscsit_pdu_tx(ict->ict_login_sm.icl_login_resp); +} + +static void +login_sm_process_request(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + + /* + * First walk all the PDU's that make up this login request + * and compile all the iSCSI key-value pairs into nvlist format. + */ + if (login_sm_pdu_list_to_nvlist(ict) != IDM_STATUS_SUCCESS) { + goto request_fail; + } + + if (login_sm_process_nvlist(ict) != IDM_STATUS_SUCCESS) { + goto request_fail; + } + + if (login_sm_check_security(ict) != IDM_STATUS_SUCCESS) { + goto request_fail; + } + + /* + * This would be a very good time to make sure we have + * negotiated the required values for the login phase. For + * example we definitely should have defined InitiatorName, + * and Target name regardless of our current login phase. These + * values should all be on our "negotiated values" list. + */ + if (!ict->ict_op.op_initial_params_set) { + if (login_sm_validate_initial_parameters(ict) != + IDM_STATUS_SUCCESS) { + goto request_fail; + } + + /* + * Now setup our session association. This includes + * create a new session or looking up an existing session, + * and if this is not a discovery session then we will + * also register this session with STMF. + */ + if (login_sm_session_bind(ict) != IDM_STATUS_SUCCESS) { + goto request_fail; + } + + ict->ict_op.op_initial_params_set = B_TRUE; + } + +request_fail: + if (login_sm_build_login_response(ict) == IDM_STATUS_SUCCESS) { + iscsit_login_sm_event(ict, ILE_LOGIN_RESP_READY, NULL); + } else { + /* Generated "login error" event. State machine will cleanup */ + /* XXX */ + cmn_err(CE_NOTE, "login_sm_process_request: %s\n", + "failed building login response"); + ASSERT(0); + } + + /* clean up request_nvlist and response_nvlist */ + if (lsm->icl_request_nvlist != NULL) { + nvlist_free(lsm->icl_request_nvlist); + lsm->icl_request_nvlist = NULL; + } + if (lsm->icl_response_nvlist != NULL) { + nvlist_free(lsm->icl_response_nvlist); + lsm->icl_response_nvlist = NULL; + } +} + + +static void +login_sm_ffp_actions(iscsit_conn_t *ict) +{ + iscsit_sess_t *ist = ict->ict_sess; + + iscsit_process_negotiated_values(ict); +} + +static idm_status_t +login_sm_validate_initial_parameters(iscsit_conn_t *ict) +{ + int nvrc; + char *string_val; + uint8_t error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + uint8_t error_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS; + idm_status_t status = IDM_STATUS_FAIL; + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + + /* + * Make sure we received the required information from the initial + * login + */ + + /* + * Initiator name and target name + */ + nvrc = nvlist_lookup_string(lsm->icl_negotiated_values, + "InitiatorName", &string_val); + if (nvrc != 0) { + goto initial_params_done; + } + ASSERT(nvrc == 0); /* Should have caught this earlier */ + + lsm->icl_initiator_name = string_val; + if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values, + "SessionType", &string_val)) == ENOENT) { + ict->ict_op.op_discovery_session = B_FALSE; + } else { + ict->ict_op.op_discovery_session = + strcmp(string_val, "Discovery") == 0 ? B_TRUE : B_FALSE; + } + + /* + * Must have either TargetName or SessionType==Discovery + */ + lsm->icl_target_name = NULL; + if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values, + "TargetName", &string_val)) != ENOENT) { + lsm->icl_target_name = string_val; + } else if (ict->ict_op.op_discovery_session == B_FALSE) { + /* Missing target name */ + goto initial_params_done; + } + + /* Sucess */ + status = IDM_STATUS_SUCCESS; + error_class = ISCSI_STATUS_CLASS_SUCCESS; + error_detail = ISCSI_LOGIN_STATUS_ACCEPT; + +initial_params_done: + SET_LOGIN_ERROR(ict, error_class, error_detail); + return (status); +} + + +/* + * login_sm_session_bind + * + * This function looks at the data from the initial login request + * of a new connection and either looks up and existing session, + * creates a new session, or returns an error. RFC3720 section 5.3.1 + * defines these rules: + * + * +------------------------------------------------------------------+ + * |ISID | TSIH | CID | Target action | + * +------------------------------------------------------------------+ + * |new | non-zero | any | fail the login | + * | | | | ("session does not exist") | + * +------------------------------------------------------------------+ + * |new | zero | any | instantiate a new session | + * +------------------------------------------------------------------+ + * |existing | zero | any | do session reinstatement | + * | | | | (see section 5.3.5) | + * +------------------------------------------------------------------+ + * |existing | non-zero | new | add a new connection to | + * | | existing | | the session | + * +------------------------------------------------------------------+ + * |existing | non-zero |existing| do connection reinstatement| + * | | existing | | (see section 5.3.4) | + * +------------------------------------------------------------------+ + * |existing | non-zero | any | fail the login | + * | | new | | ("session does not exist") | + * +------------------------------------------------------------------+ + * + */ + +static idm_status_t +login_sm_session_bind(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_tgt_t *tgt = NULL; + iscsit_sess_t *sess = NULL; + iscsit_sess_t *new_sess = NULL; + iscsit_conn_t *existing_ict = NULL; + uint8_t error_class; + uint8_t error_detail; + + /* + * Look up target. If target name is set then we should have + * a corresponding target context configured. + */ + if (lsm->icl_target_name != NULL) { + tgt = iscsit_tgt_lookup(lsm->icl_target_name); + if (tgt == NULL) { + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_TGT_NOT_FOUND); + goto session_bind_error; + } + } + + ASSERT((tgt != NULL) || (ict->ict_op.op_discovery_session == B_TRUE)); + + /* + * Check if there is an existing session matching this ISID. If + * tgt == NULL then we'll look for the session on the global list + * of discovery session. If we find a session then the ISID + * exists. + * + * It shouldn't be necessary but as an extra precaution we'll match + * TSIH as well (treating TSIH of 0 as a wildcard). We should not + * have more than one session for the same ISID on a target so any + * session we find should match the TSIH we provide. + */ + sess = iscsit_tgt_lookup_sess(tgt, lsm->icl_initiator_name, + lsm->icl_isid, lsm->icl_tsih); + if (sess != NULL) { + existing_ict = iscsit_sess_lookup_conn(sess, + ict->ict_cid); + } + + /* + * If this is a discovery session, make sure it has appropriate + * parameters. + */ + if ((ict->ict_op.op_discovery_session == B_TRUE) && + ((lsm->icl_tsih != ISCSI_UNSPEC_TSIH) || (sess != NULL))) { + /* XXX Do we need to check for existing ISID (sess != NULL)? */ + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_INVALID_REQUEST); + goto session_bind_error; + } + + /* + * Check the two error conditions from the table. + * + * ISID=new, TSIH=non-zero + */ + if ((sess == NULL) && (lsm->icl_tsih != ISCSI_UNSPEC_TSIH)) { + /* fail the login */ + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_NO_SESSION); + goto session_bind_error; + } + + /* ISID=existing, TSIH=non-zero new */ + if ((sess != NULL) && (lsm->icl_tsih != 0) && + (sess->ist_tsih != lsm->icl_tsih)) { + /* fail the login */ + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_NO_SESSION); + goto session_bind_error; + } + + /* + * Handle the remaining table cases in order + */ + if (sess == NULL) { + /* Should have caught this above */ + ASSERT(lsm->icl_tsih == ISCSI_UNSPEC_TSIH); + /* + * ISID=new, TSIH=zero --> instantiate a new session + */ + new_sess = iscsit_sess_create(tgt, ict, lsm->icl_cmdsn, + lsm->icl_isid, lsm->icl_initiator_name, + lsm->icl_target_name, &error_class, &error_detail); + ASSERT(new_sess != NULL); + + /* Session create may have failed even if it returned a value */ + if (error_class != ISCSI_STATUS_CLASS_SUCCESS) { + SET_LOGIN_ERROR(ict, error_class, error_detail); + goto session_bind_error; + } + + /* + * If we don't already have an STMF session and this is not + * a discovery session then we need to allocate and register + * one. + */ + if (!ict->ict_op.op_discovery_session) { + if (login_sm_session_register(ict) != + IDM_STATUS_SUCCESS) { + /* login_sm_session_register sets error codes */ + goto session_bind_error; + } + } + + } else { + if (lsm->icl_tsih == ISCSI_UNSPEC_TSIH) { + /* + * ISID=existing, TSIH=zero --> Session reinstatement + */ + new_sess = iscsit_sess_reinstate(sess, ict, + &error_class, &error_detail); + ASSERT(new_sess != NULL); + + if (error_class != ISCSI_STATUS_CLASS_SUCCESS) { + SET_LOGIN_ERROR(ict, error_class, error_detail); + goto session_bind_error; + } + + /* + * If we don't already have an STMF session and this is + * not a discovery session then we need to allocate and + * register one. + */ + if (!ict->ict_op.op_discovery_session) { + if (login_sm_session_register(ict) != + IDM_STATUS_SUCCESS) { + /* + * login_sm_session_register sets + * error codes + */ + goto session_bind_error; + } + } + } else { + /* + * The following code covers these two cases: + * ISID=existing, TSIH=non-zero existing, CID=new + * --> add new connection to MC/S session + * ISID=existing, TSIH=non-zero existing, CID=existing + * --> do connection reinstatement + * + * Session continuation uses this path as well + */ + cmn_err(CE_NOTE, "login_sm_session_bind: add new " + "conn/sess continue"); + if (existing_ict != NULL) { + /* + * ISID=existing, TSIH=non-zero existing, + * CID=existing --> do connection reinstatement + */ + if (iscsit_conn_reinstate(existing_ict, ict) != + IDM_STATUS_SUCCESS) { + /* + * Most likely this means the connection + * the initiator is trying to reinstate + * is not in an acceptable state. + */ + SET_LOGIN_ERROR(ict, + ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_INIT_ERR); + goto session_bind_error; + } + } + + iscsit_sess_sm_event(sess, SE_CONN_IN_LOGIN, ict); + } + } + + return (IDM_STATUS_SUCCESS); + +session_bind_error: + /* + * If session bind fails we will fail the login but don't destroy + * the session until later. + */ + return (IDM_STATUS_FAIL); +} + + +static idm_status_t +login_sm_session_register(iscsit_conn_t *ict) +{ + iscsit_sess_t *ist = ict->ict_sess; + stmf_scsi_session_t *ss; + + ss = stmf_alloc(STMF_STRUCT_SCSI_SESSION, 0, + 0); + if (ss == NULL) { + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + return (IDM_STATUS_FAIL); + } + + ss->ss_rport_id = kmem_zalloc(sizeof (scsi_devid_desc_t) + + strlen(ist->ist_initiator_name) + 1, KM_SLEEP); + strcpy((char *)ss->ss_rport_id->ident, ist->ist_initiator_name); + ss->ss_rport_id->ident_length = strlen(ist->ist_initiator_name); + ss->ss_rport_id->protocol_id = PROTOCOL_iSCSI; + ss->ss_rport_id->piv = 1; + ss->ss_rport_id->code_set = CODE_SET_ASCII; + ss->ss_rport_id->association = ID_IS_TARGET_PORT; + + ss->ss_lport = ist->ist_lport; + + if (stmf_register_scsi_session(ict->ict_sess->ist_lport, ss) != + STMF_SUCCESS) { + kmem_free(ss->ss_rport_id, + sizeof (scsi_devid_desc_t) + + strlen(ist->ist_initiator_name) + 1); + stmf_free(ss); + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_TARGET_ERR, + ISCSI_LOGIN_STATUS_TARGET_ERROR); + return (IDM_STATUS_FAIL); + } + + ss->ss_port_private = ict->ict_sess; + ict->ict_sess->ist_stmf_sess = ss; + + return (IDM_STATUS_SUCCESS); +} + + +static idm_status_t +login_sm_req_pdu_check(iscsit_conn_t *ict, idm_pdu_t *pdu) +{ + uint8_t error_class; + uint8_t error_detail; + uint8_t csg_req; + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsi_login_hdr_t *lh = (iscsi_login_hdr_t *)pdu->isp_hdr; + iscsi_login_rsp_hdr_t *lh_resp = + (iscsi_login_rsp_hdr_t *)lsm->icl_login_rej_resp->isp_hdr; + + /* + * Check CSG + */ + csg_req = ISCSI_LOGIN_CURRENT_STAGE(lh->flags); + switch (csg_req) { + case ISCSI_SECURITY_NEGOTIATION_STAGE: + case ISCSI_OP_PARMS_NEGOTIATION_STAGE: + if ((csg_req != lsm->icl_login_csg) && + (lsm->icl_login_state != ILS_LOGIN_INIT)) { + /* + * Inappropriate CSG change. Initiator can only + * change CSG after we've responded with the + * transit bit set. If we had responded with + * a CSG change previous we would have updated + * our copy of CSG. + * + * The exception is when we are in ILS_LOGIN_INIT + * state since we haven't determined our initial + * CSG value yet. + */ + error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + error_detail = ISCSI_LOGIN_STATUS_INVALID_REQUEST; + goto pdu_check_fail; + } + break; + case ISCSI_FULL_FEATURE_PHASE: + default: + error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + error_detail = ISCSI_LOGIN_STATUS_INVALID_REQUEST; + goto pdu_check_fail; + } + + /* + * If this is the first login PDU for a new connection then + * the session will be NULL. + */ + if (ict->ict_sess != NULL) { + /* + * We've already created a session on a previous PDU. Make + * sure this PDU is consistent with what we've already seen + */ + if ((ict->ict_cid != ntohs(lh->cid)) || + (bcmp(ict->ict_sess->ist_isid, lh->isid, + ISCSI_ISID_LEN) != 0)) { + error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + error_detail = ISCSI_LOGIN_STATUS_INIT_ERR; + goto pdu_check_fail; + } + } + + /* + * Make sure we are compatible with the version range + */ + if ((lh->min_version > ISCSIT_MAX_VERSION) || + (lh->max_version < ISCSIT_MIN_VERSION)) { + error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + error_detail = ISCSI_LOGIN_STATUS_NO_VERSION; + goto pdu_check_fail; + } + + /* + * Just in case the initiator changes things up on us along the way + * check against our active_version -- we can't change the active + * version and the initiator is not *supposed* to change its + * min_version and max_version values so this should never happen. + * Of course we only do this if the response header template has + * been built. + */ + if ((lh_resp->opcode == ISCSI_OP_LOGIN_RSP) && /* header valid */ + ((lh->min_version > lh_resp->active_version) || + (lh->max_version < lh_resp->active_version))) { + error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + error_detail = ISCSI_LOGIN_STATUS_INIT_ERR; + goto pdu_check_fail; + } + + return (IDM_STATUS_SUCCESS); + +pdu_check_fail: + /* Error status contains login response code */ + ASSERT(0); /* build and send login response XXX */ + return (IDM_STATUS_FAIL); +} + +static idm_status_t +login_sm_pdu_list_to_nvlist(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + idm_pdu_t *pdu, *next_pdu; + boolean_t split_kv = B_FALSE; + char *textbuf, *leftover_textbuf; + int textbuflen, leftover_textbuflen; + char *split_kvbuf; + int split_kvbuflen, cont_fraglen; + iscsi_login_hdr_t *lh; + boolean_t first_pdu = B_TRUE; /* XXX */ + uint8_t error_class = 0; + uint8_t error_detail = 0; + int rc; + + /* Allocate a new nvlist for request key/value pairs */ + ASSERT(lsm->icl_request_nvlist == NULL); + rc = nvlist_alloc(&lsm->icl_request_nvlist, NV_UNIQUE_NAME, + KM_NOSLEEP); + if (rc != 0) { + error_class = ISCSI_STATUS_CLASS_TARGET_ERR; + error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + goto kvpair_xlate_fail; + } + + /* + * A login request can be split across multiple PDU's. The state + * machine has collected all the PDU's that make up this login request + * and assembled them on the "icl_pdu_list" queue. Process each PDU + * and convert the text keywords to nvlist form. + */ + pdu = list_head(&lsm->icl_pdu_list); + while (pdu != NULL) { + next_pdu = list_next(&lsm->icl_pdu_list, pdu); + + list_remove(&lsm->icl_pdu_list, pdu); + + lh = (iscsi_login_hdr_t *)pdu->isp_hdr; + + /* + * Set the CSG, NSG and Transit bits based on the first PDU + * in the login sequence. We'll clear the transit bit if + * we encounter any login parameters in the request that + * required an additional login transfer (i.e. no acceptable + * choices in range or we needed to change a boolean + * value from "Yes" to "No"). + */ + if (first_pdu) { + lsm->icl_login_csg = + ISCSI_LOGIN_CURRENT_STAGE(lh->flags); + lsm->icl_login_nsg = + ISCSI_LOGIN_NEXT_STAGE(lh->flags); + lsm->icl_login_transit = + lh->flags & ISCSI_FLAG_LOGIN_TRANSIT; + first_pdu = B_FALSE; + } + + textbuf = (char *)pdu->isp_data; + textbuflen = pdu->isp_datalen; + if (textbuflen == 0) { + /* This shouldn't really happen but it could.. */ + pdu = next_pdu; + continue; + } + + /* + * If we encountered a split key-value pair on the last + * PDU then handle it now by grabbing the remainder of the + * key-value pair from the next PDU and splicing them + * together. Obviously on the first PDU this will never + * happen. + */ + if (split_kv) { + cont_fraglen = iscsi_textbuf_to_firstfraglen(textbuf, + textbuflen); + if (cont_fraglen == pdu->isp_datalen) { + /* + * This key-value pair spans more than two + * PDU's. We don't handle this. + * + * XXX Actually we probably *do* need to handle + * this. And it shouldn't be that difficult + * since we have all the PDU's up front. + * The standard talks about 64k key-value pairs + * but the login PDU's are required to use + * the default "max recv segment size" which is + * 8k. A 64k key-value with thus be spread + * across at least 8 PDU's. We'll do it later.. + */ + ASSERT(0); /* XXX */ + + /* XXX Not an error, remove it later */ + error_class = ISCSI_STATUS_CLASS_TARGET_ERR; + error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; + goto kvpair_xlate_fail; + } + + split_kvbuflen = leftover_textbuflen + cont_fraglen; + split_kvbuf = kmem_alloc(split_kvbuflen, KM_NOSLEEP); + if (split_kvbuf == NULL) { + error_class = ISCSI_STATUS_CLASS_TARGET_ERR; + error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + goto kvpair_xlate_fail; + } + + bcopy(leftover_textbuf, split_kvbuf, + leftover_textbuflen); + bcopy(textbuf, + (uint8_t *)split_kvbuf + leftover_textbuflen, + cont_fraglen); + + + if (iscsi_textbuf_to_nvlist(lsm->icl_request_nvlist, + &split_kvbuf, &split_kvbuflen) != 0) { + /* + * Need to handle E2BIG case, indicating that + * a key-value pair is split across multiple + * PDU's. + */ + ASSERT(0); /* XXX */ + + error_class = ISCSI_STATUS_CLASS_TARGET_ERR; + error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; + goto kvpair_xlate_fail; + } + + ASSERT(split_kvbuflen != NULL); + kmem_free(split_kvbuf, split_kvbuflen); + + /* Now handle the remainder of the PDU as normal */ + textbuf += (cont_fraglen + 1); + textbuflen -= (cont_fraglen + 1); + } + + /* + * Convert each key-value pair in the text buffer to nvlist + * format. If the list has already been created the nvpair + * elements will be added on to the existing list. Otherwise + * a new nvlist will be created. + */ + if (iscsi_textbuf_to_nvlist(lsm->icl_request_nvlist, + &textbuf, &textbuflen) != 0) { + ASSERT(0); /* XXX */ + + error_class = ISCSI_STATUS_CLASS_TARGET_ERR; + error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; + goto kvpair_xlate_fail; + } + + ASSERT( + ((lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) & + (next_pdu != NULL)) | + (!(lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) & + (next_pdu == NULL))); + + if ((lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) & + (textbuflen != 0)) { + /* + * Key-value pair is split over two PDU's. We + * assume it willl never be split over more than + * two PDU's. + */ + split_kv = B_TRUE; + leftover_textbuf = textbuf; + leftover_textbuflen = textbuflen; + } else { + split_kv = B_FALSE; + if (textbuflen != 0) { + /* + * Incomplete keyword but no additional + * PDU's. This is a malformed login + * request. + */ + ASSERT(0); /* XXX */ + + error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + error_detail = + ISCSI_LOGIN_STATUS_INVALID_REQUEST; + goto kvpair_xlate_fail; + } + } + + pdu = next_pdu; + } + + /* + * At this point we should have a complete nvlist representing + * all the iSCSI key-value pairs in the login request PDU's + * that make up this request. + */ + + return (IDM_STATUS_SUCCESS); + +kvpair_xlate_fail: + if (error_class != 0) { + SET_LOGIN_ERROR(ict, error_class, error_detail); + } + return (IDM_STATUS_FAIL); +} + + +static idm_status_t +login_sm_process_nvlist(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + char *nvp_name; + data_type_t nvp_type; + nvpair_t *nvp; + nvpair_t *next_nvp; + nvpair_t *negotiated_nvp; + int nvrc, rc; + kv_status_t kvrc; + uint8_t error_class; + uint8_t error_detail; + idm_status_t idm_status; + + error_class = ISCSI_STATUS_CLASS_SUCCESS; + error_detail = ISCSI_LOGIN_STATUS_ACCEPT; + + /* Allocate a new nvlist for response key/value pairs */ + ASSERT(lsm->icl_response_nvlist == NULL); + rc = nvlist_alloc(&lsm->icl_response_nvlist, NV_UNIQUE_NAME, + KM_NOSLEEP); + if (rc != 0) { + error_class = ISCSI_STATUS_CLASS_TARGET_ERR; + error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + goto process_nvlist_done; + } + + nvp = nvlist_next_nvpair(lsm->icl_request_nvlist, NULL); + while (nvp != NULL) { + next_nvp = nvlist_next_nvpair(lsm->icl_request_nvlist, nvp); + nvp_name = nvpair_name(nvp); + nvp_type = nvpair_type(nvp); + /* + * If we've already agreed upon a value then make sure this + * is not attempting to change that value. From RFC3270 + * section 5.3: + * + * "Neither the initiator nor the target should attempt to + * declare or negotiate a parameter more than once during + * login except for responses to specific keys that + * explicitly allow repeated key declarations (e.g., + * TargetAddress). An attempt to renegotiate/redeclare + * parameters not specifically allowed MUST be detected + * by the initiator and target. If such an attempt is + * detected by the target, the target MUST respond + * with Login reject (initiator error); ..." + */ + if (nvlist_lookup_nvpair(lsm->icl_negotiated_values, + nvp_name, &negotiated_nvp) == 0) { + /* + * OK, we've already negotiated a value for this. + * If the new value is the same value then everything's + * OK. If the new value is different then we have + * to reject the login (section 5.3 of rfc3270, last + * paragraph) + */ + if (iscsi_nvpair_cmp(nvp, negotiated_nvp) != 0) { + kvrc = KV_VALUE_ERROR; /* initiator error */ + } else { + kvrc = KV_HANDLED; /* silently ignore it */ + } + } else { + kvrc = iscsit_handle_key(ict, nvp, nvp_name); + } + + iscsi_kvstat_to_error(kvrc, &error_class, &error_detail); + if (error_class != ISCSI_STATUS_CLASS_SUCCESS) { + break; + } + + nvp = next_nvp; + } + +process_nvlist_done: + if (error_class == ISCSI_STATUS_CLASS_SUCCESS) { + idm_status = IDM_STATUS_SUCCESS; + } else { + /* supply login class/detail for login errors */ + SET_LOGIN_ERROR(ict, error_class, error_detail); + idm_status = IDM_STATUS_FAIL; + } + + return (idm_status); +} + +static idm_status_t +login_sm_check_security(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + uint8_t error_class; + uint8_t error_detail; + idm_status_t idm_status; + + error_class = ISCSI_STATUS_CLASS_SUCCESS; + error_detail = ISCSI_LOGIN_STATUS_ACCEPT; + + /* Check authentication status. */ + if (lsm->icl_login_csg == ISCSI_SECURITY_NEGOTIATION_STAGE) { + /* + * We should have some authentication key/value pair(s) + * received from initiator and the authentication phase + * has been shifted when the key/value pair(s) are being + * handled in the previous call iscsit_handle_security_key. + * Now it turns to target to check the authentication phase + * and shift it after taking some authentication action. + */ + kvrc = iscsit_reply_security_key(ict); + iscsi_kvstat_to_error(kvrc, &error_class, &error_detail); + } else if (!ict->ict_login_sm.icl_auth_pass) { + /* + * XXX check access. + * This is just a temporary workaround -- obviously + * in the long run we can't take this approach since the target + * may be configurerd to require authentication. + */ + ict->ict_login_sm.icl_auth_pass = 1; + } + + if (error_class == ISCSI_STATUS_CLASS_SUCCESS) { + idm_status = IDM_STATUS_SUCCESS; + } else { + /* supply login class/detail for login errors */ + SET_LOGIN_ERROR(ict, error_class, error_detail); + idm_status = IDM_STATUS_FAIL; + } + + return (idm_status); +} + +static idm_status_t +login_sm_build_login_response(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsi_login_rsp_hdr_t *lh; + int rc; + + char *nvp_name; + data_type_t nvp_type; + nvpair_t *nvp; + nvpair_t *next_nvp; + int nvrc; + + idm_status_t idm_status = IDM_STATUS_SUCCESS; + + /* + * XXX In the future we need to implement the following process + * for the response: + * + * 1. Calculate the required text buffer space to hold the + * response key-value pairs. + * 2. Allocate a text buffer for those responses. + * 3. Convert the nvlist values into key-value pairs + * 4. Build a PDU to transmit the first login response PDU + * 5. If there is more data, wait for an ack then goto step 4. + * + */ + if (lsm->icl_login_resp == NULL) { + lsm->icl_login_resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), 0); + idm_pdu_init(lsm->icl_login_resp, ict->ict_ic, ict, + login_resp_complete_cb); + + /* + * Use the BHS header values from the response template + */ + bcopy(lsm->icl_login_rej_resp->isp_hdr, + lsm->icl_login_resp->isp_hdr, sizeof (iscsi_hdr_t)); + } + + lh = (iscsi_login_rsp_hdr_t *)lsm->icl_login_resp->isp_hdr; + + /* Set error class/detail */ + lh->status_class = lsm->icl_login_resp_err_class; + lh->status_detail = lsm->icl_login_resp_err_detail; + + /* Set CSG, NSG and Transit */ + lh->flags = 0; + lh->flags |= lsm->icl_login_csg << 2; + if (lh->status_class == ISCSI_STATUS_CLASS_SUCCESS) { + if (lsm->icl_login_transit && + lsm->icl_auth_pass != 0) { + lh->flags |= lsm->icl_login_nsg; + lh->flags |= ISCSI_FLAG_LOGIN_TRANSIT; + lsm->icl_login_csg = lsm->icl_login_nsg; + /* If we are transitioning to FFP then set TSIH */ + if (lsm->icl_login_csg == ISCSI_FULL_FEATURE_PHASE) { + lh->tsid = htons(ict->ict_sess->ist_tsih); + } + } + + rc = iscsi_nvlist_to_textbuf(lsm->icl_response_nvlist, + &lsm->icl_login_resp_buf, + &lsm->icl_login_resp_len, + &lsm->icl_login_resp_valid_len); + if (rc != 0) { + idm_status = IDM_STATUS_FAIL; + } else { + lsm->icl_login_resp->isp_data = + (uint8_t *)lsm->icl_login_resp_buf; + lsm->icl_login_resp->isp_datalen = + lsm->icl_login_resp_valid_len; + } + } else { + lsm->icl_login_resp->isp_data = 0; + lsm->icl_login_resp->isp_datalen = 0; + } + + return (idm_status); +} + +static kv_status_t +iscsit_handle_key(iscsit_conn_t *ict, nvpair_t *nvp, char *nvp_name) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + const iscsi_kv_xlate_t *ikvx; + + ikvx = iscsi_lookup_kv_xlate(nvp_name, strlen(nvp_name)); + if (ikvx->ik_key_id == KI_MAX_KEY) { + /* + * Any key not understood by the acceptor may be igonred + * by the acceptor without affecting the basic function. + * However, the answer for a key not understood MUST be + * key=NotUnderstood. + */ + kvrc = iscsit_reply_constant(ict, nvp_name, + ISCSI_TEXT_NOTUNDERSTOOD); + } else { + kvrc = iscsit_handle_common_key(ict, nvp, ikvx); + if (kvrc == KV_UNHANDLED) { + switch (lsm->icl_login_csg) { + case ISCSI_SECURITY_NEGOTIATION_STAGE: + kvrc = iscsit_handle_security_key( + ict, nvp, ikvx); + break; + case ISCSI_OP_PARMS_NEGOTIATION_STAGE: + kvrc = iscsit_handle_operational_key( + ict, nvp, ikvx); + break; + case ISCSI_FULL_FEATURE_PHASE: + default: + /* What are we doing here? */ + ASSERT(0); + kvrc = KV_UNHANDLED; + } + } + } + + return (kvrc); +} + +static kv_status_t +iscsit_handle_common_key(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + char *string_val; + uint8_t *binary_val; + int binary_val_len; + int nvrc; + + switch (ikvx->ik_key_id) { + case KI_INITIATOR_NAME: + case KI_INITIATOR_ALIAS: + nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp); + kvrc = iscsi_nvstat_to_kvstat(nvrc); + break; + case KI_TARGET_NAME: + /* We'll validate the target during login_sm_session_bind() */ + nvrc = nvpair_value_string(nvp, &string_val); + ASSERT(nvrc == 0); /* We built this nvlist */ + + nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp); + kvrc = iscsi_nvstat_to_kvstat(nvrc); + break; + case KI_TARGET_ALIAS: + case KI_TARGET_ADDRESS: + case KI_TARGET_PORTAL_GROUP_TAG: + kvrc = KV_TARGET_ONLY; /* Only the target can declare this */ + break; + case KI_SESSION_TYPE: + /* + * If we don't receive this key on the initial login + * we assume this is a normal session. + */ + nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp); + kvrc = iscsi_nvstat_to_kvstat(nvrc); + nvrc = nvpair_value_string(nvp, &string_val); + ASSERT(nvrc == 0); /* We built this nvlist */ + ict->ict_op.op_discovery_session = + strcmp(string_val, "Discovery") == 0 ? B_TRUE : B_FALSE; + break; + default: + /* + * This is not really an error but we should + * leave this nvpair on the list since we + * didn't do anything with it. Either + * the security or operational phase + * handling functions should process it. + */ + kvrc = KV_UNHANDLED; + break; + } + + return (kvrc); +} + +static kv_status_t +iscsit_handle_security_key(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + iscsikey_id_t kv_id; + kv_status_t kvrc; + iscsit_auth_handler_t handler; + + /* + * After all of security keys are handled, this function will + * be called again to verify current authentication status + * and perform some actual authentication work. At this time, + * the nvp and ikvx will be passed in as NULLs. + */ + if (ikvx != NULL) { + kv_id = ikvx->ik_key_id; + } else { + kv_id = 0; + } + + handler = iscsit_auth_get_handler(client, kv_id); + if (handler) { + kvrc = handler(ict, nvp, ikvx); + } else { + kvrc = KV_UNHANDLED; /* invalid request */ + } + + return (kvrc); +} + +static kv_status_t +iscsit_reply_security_key(iscsit_conn_t *ict) +{ + return (iscsit_handle_security_key(ict, NULL, NULL)); +} + +static kv_status_t +iscsit_handle_operational_key(iscsit_conn_t *ict, nvpair_t *nvp, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc = KV_UNHANDLED; + nvpair_t *lov_nvpair; + char *string_val; + boolean_t bool_val; + uint64_t num_val; + int nvrc; + + /* + * Retrieve values. All value lookups are expected to succeed + * since we build the nvlist while decoding the text buffer. This + * step is intended to eliminate some duplication of code (for example + * we only need to code the numerical value lookup once). We will + * handle the values (if necessary) below. + */ + switch (ikvx->ik_key_id) { + /* Lists */ + case KI_HEADER_DIGEST: + case KI_DATA_DIGEST: + break; + /* Booleans */ + case KI_INITIAL_R2T: + case KI_IMMEDIATE_DATA: + case KI_DATA_PDU_IN_ORDER: + case KI_DATA_SEQUENCE_IN_ORDER: + case KI_IFMARKER: + case KI_OFMARKER: + nvrc = nvpair_value_boolean_value(nvp, &bool_val); + ASSERT(nvrc == 0); /* We built this nvlist */ + break; + /* Numericals */ + case KI_MAX_CONNECTIONS: + case KI_MAX_RECV_DATA_SEGMENT_LENGTH: + case KI_MAX_BURST_LENGTH: + case KI_FIRST_BURST_LENGTH: + case KI_DEFAULT_TIME_2_WAIT: + case KI_DEFAULT_TIME_2_RETAIN: + case KI_MAX_OUTSTANDING_R2T: + case KI_ERROR_RECOVERY_LEVEL: + nvrc = nvpair_value_uint64(nvp, &num_val); + ASSERT(nvrc == 0); + break; + /* Ranges */ + case KI_OFMARKERINT: + case KI_IFMARKERINT: + break; + default: + break; + } + + /* + * Now handle the values according to the key name. Sometimes we + * don't care what the value is -- in that case we just add the nvpair + * to the negotiated values list. + */ + switch (ikvx->ik_key_id) { + case KI_HEADER_DIGEST: + kvrc = iscsit_handle_digest(ict, nvp, ikvx); + break; + case KI_DATA_DIGEST: + kvrc = iscsit_handle_digest(ict, nvp, ikvx); + break; + case KI_INITIAL_R2T: + /* We *require* INITIAL_R2T=yes */ + kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx, + B_TRUE); + break; + case KI_IMMEDIATE_DATA: +#ifdef LATER + /* We allow any value for IMMEDIATE_DATA */ + kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx, + bool_val); +#else + /* + * For now we *require* IMMEDIATE_DATA=no. Need to figure + * out how this works in STMF-land. + */ + kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx, + B_FALSE); +#endif + break; + case KI_DATA_PDU_IN_ORDER: + kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx, + B_TRUE); + break; + case KI_DATA_SEQUENCE_IN_ORDER: + /* We allow any value for DATA_SEQUENCE_IN_ORDER */ + kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx, + bool_val); + break; + case KI_OFMARKER: + case KI_IFMARKER: + /* We don't support markers */ + kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx, + B_FALSE); + break; + case KI_MAX_CONNECTIONS: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_CONNECTIONS, + ISCSI_MAX_CONNECTIONS, + ISCSIT_MAX_CONNECTIONS); + break; + case KI_MAX_RECV_DATA_SEGMENT_LENGTH: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_RECV_DATA_SEGMENT_LENGTH, + ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH, + ISCSIT_MAX_RECV_DATA_SEGMENT_LENGTH); + break; + case KI_MAX_BURST_LENGTH: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_MAX_BURST_LENGTH, + ISCSI_MAX_BURST_LENGTH, + ISCSIT_MAX_BURST_LENGTH); + break; + case KI_FIRST_BURST_LENGTH: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_FIRST_BURST_LENGTH, + ISCSI_MAX_FIRST_BURST_LENGTH, + ISCSIT_MAX_FIRST_BURST_LENGTH); + break; + case KI_DEFAULT_TIME_2_WAIT: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_TIME2WAIT, + ISCSI_MAX_TIME2WAIT, + ISCSIT_MAX_TIME2WAIT); + break; + case KI_DEFAULT_TIME_2_RETAIN: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_TIME2RETAIN, + ISCSI_MAX_TIME2RETAIN, + ISCSIT_MAX_TIME2RETAIN); + break; + case KI_MAX_OUTSTANDING_R2T: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_MAX_OUTSTANDING_R2T, + ISCSI_MAX_OUTSTANDING_R2T, + ISCSIT_MAX_OUTSTANDING_R2T); + break; + case KI_ERROR_RECOVERY_LEVEL: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_ERROR_RECOVERY_LEVEL, + ISCSI_MAX_ERROR_RECOVERY_LEVEL, + ISCSIT_MAX_ERROR_RECOVERY_LEVEL); + break; + case KI_OFMARKERINT: + case KI_IFMARKERINT: + kvrc = iscsit_reply_constant(ict, ikvx->ik_key_name, + ISCSI_TEXT_IRRELEVANT); + break; + default: + kvrc = KV_UNHANDLED; /* invalid request */ + break; + } + + return (kvrc); +} + +static kv_status_t +iscsit_reply_constant(iscsit_conn_t *ict, + const char *nvp_name, const char *text) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + int nvrc; + + nvrc = nvlist_add_string(lsm->icl_response_nvlist, + nvp_name, text); + kvrc = iscsi_nvstat_to_kvstat(nvrc); + + return (kvrc); +} + +/* XXX Ugh. This needs to be revisited */ +static kv_status_t +iscsit_handle_digest(iscsit_conn_t *ict, nvpair_t *choices, + const iscsi_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc = KV_VALUE_ERROR; + int nvrc; + nvpair_t *digest_choice; + nvpair_t *digest_response; + char *digest_type_string; + char *digest_choice_string; + + /* + * XXX Need to add persistent config here if we want users to allow + * disabling of digests on the target side. You could argue that + * this makes things too complicated... just let the initiator state + * what it wants and we'll take it. For now that's exactly what + * we'll do. + */ + digest_choice = iscsi_get_next_listvalue(choices, NULL); + + /* + * Loop through all choices. As soon as we find a choice + * that we support add the value to our negotiated values list + * and respond with that value in the login response. + */ + while (digest_choice != NULL) { + nvrc = nvpair_value_string(digest_choice, + &digest_choice_string); + ASSERT(nvrc == 0); + + /* + * XXX no crc32c for now. Digest calculation is done in + * IDM and we don't have the interface in place to tell + * IDM to calculate digests. + */ +#ifdef LATER + if ((strcasecmp(digest_choice, "crc32c") == 0) || + (strcasecmp(digest_choice_string, "none") == 0)) { + } /* removeme, don't break bracket-matching */ +#else + if (strcasecmp(digest_choice_string, "none") == 0) { +#endif + /* Add to negotiated values list */ + nvrc = nvlist_add_string(lsm->icl_negotiated_values, + ikvx->ik_key_name, digest_choice_string); + kvrc = iscsi_nvstat_to_kvstat(nvrc); + if (nvrc == 0) { + /* Add to login response list */ + nvrc = nvlist_add_string( + lsm->icl_response_nvlist, + ikvx->ik_key_name, digest_choice_string); + kvrc = iscsi_nvstat_to_kvstat(nvrc); + } + break; + } + digest_choice = iscsi_get_next_listvalue(choices, + digest_choice); + } + + /* + * XXX + * Would the initiator ever specify only crc32c? We can avoid issues + * as long as we support both (which we will but not in the prototype.) + */ + + return (kvrc); +} + + +static kv_status_t +iscsit_handle_boolean(iscsit_conn_t *ict, nvpair_t *nvp, boolean_t value, + const iscsi_kv_xlate_t *ikvx, boolean_t iscsit_value) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + int nvrc; + + if (value != iscsit_value) { + /* Respond back to initiator with our value */ + value = iscsit_value; + lsm->icl_login_transit = B_FALSE; + nvrc = 0; + } else { + /* Add this to our negotiated values */ + nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, + nvp); + } + + /* Response of Simple-value Negotiation */ + if (nvrc == 0 && !ikvx->ik_declarative) { + nvrc = nvlist_add_boolean_value( + lsm->icl_response_nvlist, ikvx->ik_key_name, value); + } + kvrc = iscsi_nvstat_to_kvstat(nvrc); + + return (kvrc); +} + +static kv_status_t +iscsit_handle_numerical(iscsit_conn_t *ict, nvpair_t *nvp, uint64_t value, + const iscsi_kv_xlate_t *ikvx, + uint64_t iscsi_min_value, uint64_t iscsi_max_value, + uint64_t iscsit_max_value) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + int nvrc; + + /* Validate against standard */ + if ((value < iscsi_min_value) || (value > iscsi_max_value)) { + kvrc = KV_VALUE_ERROR; + } else { + if (value > iscsit_max_value) { + /* Respond back to initiator with our value */ + value = iscsit_max_value; + lsm->icl_login_transit = B_FALSE; + nvrc = 0; + } else { + /* Add this to our negotiated values */ + nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, + nvp); + } + + /* Response of Simple-value Negotiation */ + if (nvrc == 0 && !ikvx->ik_declarative) { + nvrc = nvlist_add_uint64(lsm->icl_response_nvlist, + ikvx->ik_key_name, value); + } + kvrc = iscsi_nvstat_to_kvstat(nvrc); + } + + return (kvrc); +} + + +static void +iscsit_process_negotiated_values(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + char *string_val; + boolean_t boolean_val; + uint64_t uint64_val; + int nvrc; + kv_status_t kvrc; + + /* + * Initiator alias and target alias + */ + if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values, + "InitiatorAlias", &string_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_sess->ist_initiator_alias = + kmem_alloc(strlen(string_val) + 1, KM_SLEEP); + strcpy(ict->ict_sess->ist_initiator_alias, string_val); + } + + if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values, + "TargetAlias", &string_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_sess->ist_target_alias = + kmem_alloc(strlen(string_val) + 1, KM_SLEEP); + strcpy(ict->ict_sess->ist_target_alias, string_val); + } + + /* + * Operational parameters. We process SessionType when it is + * initially received since it is required on the initial login. + */ + if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values, + "InitialR2T", &boolean_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_initial_r2t = boolean_val; + } + + if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values, + "ImmediateData", &boolean_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_immed_data = boolean_val; + } + + if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values, + "DataPDUInOrder", &boolean_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_data_pdu_in_order = boolean_val; + } + + if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values, + "DataSequenceInOrder", &boolean_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_data_sequence_in_order = boolean_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "MaxConnections", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_max_connections = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "MaxRecvDataSegmentLength", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_max_recv_data_segment_length = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "MaxBurstLength", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_max_burst_length = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "FirstBurstLength", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_first_burst_length = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "DefaultTime2Wait", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_default_time_2_wait = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "DefaultTime2Retain", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_default_time_2_retain = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "MaxOutstandingR2T", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_max_outstanding_r2t = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "ErrorRecoveryLevel", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_error_recovery_level = uint64_val; + } +}