iscsi_login.c revision 30e7468f8f41aa30ada067b2c1d5d284046514da
/*
* 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 2000 by Cisco Systems, Inc. All rights reserved.
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* iSCSI protocol login and enumeration
*/
#include "iscsi.h"
#include <sys/iscsi_protocol.h>
#include <sys/scsi/adapters/iscsi_door.h>
boolean_t iscsi_login_logging = B_FALSE;
/* internal login protocol interfaces */
static iscsi_status_t iscsi_login(iscsi_conn_t *icp,
uint8_t *status_class, uint8_t *status_detail);
static int iscsi_add_text(idm_pdu_t *text_pdu,
int max_data_length, char *param, char *value);
static int iscsi_find_key_value(char *param, char *ihp, char *pdu_end,
char **value_start, char **value_end);
static void iscsi_null_callback(void *user_handle, void *message_handle,
int auth_status);
static iscsi_status_t iscsi_process_login_response(iscsi_conn_t *icp,
iscsi_login_rsp_hdr_t *ilrhp, char *data, int max_data_length);
static iscsi_status_t iscsi_make_login_pdu(iscsi_conn_t *icp,
idm_pdu_t *text_pdu, char *data, int max_data_length);
static iscsi_status_t iscsi_update_address(iscsi_conn_t *icp,
char *address);
static char *iscsi_login_failure_str(uchar_t status_class,
uchar_t status_detail);
static void iscsi_login_end(iscsi_conn_t *icp,
iscsi_status_t status, iscsi_task_t *itp);
static iscsi_status_t iscsi_login_connect(iscsi_conn_t *icp);
static void iscsi_login_disconnect(iscsi_conn_t *icp);
static void iscsi_notice_key_values(iscsi_conn_t *icp);
#define ISCSI_LOGIN_RETRY_DELAY 5 /* seconds */
#define ISCSI_LOGIN_POLLING_DELAY 60 /* seconds */
/*
* +--------------------------------------------------------------------+
* | External Login Interface |
* +--------------------------------------------------------------------+
*/
/*
* iscsi_login_start - connect and perform iscsi protocol login
*/
iscsi_status_t
iscsi_login_start(void *arg)
{
iscsi_task_t *itp = (iscsi_task_t *)arg;
iscsi_status_t rval = ISCSI_STATUS_LOGIN_FAILED;
iscsi_conn_t *icp;
iscsi_sess_t *isp;
iscsi_hba_t *ihp;
unsigned char status_class;
unsigned char status_detail;
clock_t lbolt;
ASSERT(itp != NULL);
icp = (iscsi_conn_t *)itp->t_arg;
ASSERT(icp != NULL);
isp = icp->conn_sess;
ASSERT(isp != NULL);
ihp = isp->sess_hba;
ASSERT(ihp != NULL);
login_start:
ASSERT((icp->conn_state == ISCSI_CONN_STATE_IN_LOGIN) ||
(icp->conn_state == ISCSI_CONN_STATE_FAILED) ||
(icp->conn_state == ISCSI_CONN_STATE_POLLING));
icp->conn_state_ffp = B_FALSE;
/* reset connection statsn */
icp->conn_expstatsn = 0;
icp->conn_laststatsn = 0;
/* sync up authentication information */
(void) iscsi_sess_set_auth(isp);
/* sync up login and session parameters */
if (!ISCSI_SUCCESS(iscsi_conn_sync_params(icp))) {
/* unable to sync params. fail connection attempts */
iscsi_login_end(icp, ISCSI_STATUS_LOGIN_FAILED, itp);
return (ISCSI_STATUS_LOGIN_FAILED);
}
/* delay the login process if required */
lbolt = ddi_get_lbolt();
if (lbolt < icp->conn_login_min) {
delay(icp->conn_login_min - lbolt);
}
/*
* Attempt to open TCP connection, associated IDM connection will
* have a hold on it that must be released after the call to
* iscsi_login() below.
*/
if (!ISCSI_SUCCESS(iscsi_login_connect(icp))) {
/* retry this failure */
goto login_retry;
}
/*
* allocate response buffer with based on default max
* transfer size. This size might shift during login.
*/
icp->conn_login_max_data_length =
icp->conn_params.max_xmit_data_seg_len;
icp->conn_login_data = kmem_zalloc(icp->conn_login_max_data_length,
KM_SLEEP);
/*
* Start protocol login, upon return we will be either logged in
* or disconnected
*/
rval = iscsi_login(icp, &status_class, &status_detail);
/* done with buffer */
kmem_free(icp->conn_login_data, icp->conn_login_max_data_length);
/* Release connection hold */
idm_conn_rele(icp->conn_ic);
/* hard failure in login */
if (!ISCSI_SUCCESS(rval)) {
/*
* We should just give up retry if these failures are
* detected.
*/
switch (rval) {
/*
* We should just give up retry if these
* failures are detected.
*/
case ISCSI_STATUS_AUTHENTICATION_FAILED:
case ISCSI_STATUS_INTERNAL_ERROR:
case ISCSI_STATUS_VERSION_MISMATCH:
case ISCSI_STATUS_NEGO_FAIL:
/* we don't want to retry this failure */
iscsi_login_end(icp, ISCSI_STATUS_LOGIN_FAILED, itp);
return (ISCSI_STATUS_LOGIN_FAILED);
default:
/* retry this failure */
goto login_retry;
}
}
/* soft failure with reason */
switch (status_class) {
case ISCSI_STATUS_CLASS_SUCCESS:
/* login was successful */
iscsi_login_end(icp, ISCSI_STATUS_SUCCESS, itp);
return (ISCSI_STATUS_SUCCESS);
case ISCSI_STATUS_CLASS_REDIRECT:
/* Retry at the redirected address */
goto login_start;
case ISCSI_STATUS_CLASS_TARGET_ERR:
/* retry this failure */
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"%s (0x%02x/0x%02x)", icp->conn_oid,
iscsi_login_failure_str(status_class, status_detail),
status_class, status_detail);
goto login_retry;
case ISCSI_STATUS_CLASS_INITIATOR_ERR:
default:
/* All other errors are hard failures */
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"%s (0x%02x/0x%02x) Target: %s, TPGT: %d",
icp->conn_oid,
iscsi_login_failure_str(status_class, status_detail),
status_class, status_detail, isp->sess_name,
isp->sess_tpgt_conf);
/* we don't want to retry this failure */
iscsi_login_end(icp, ISCSI_STATUS_LOGIN_FAILED, itp);
break;
}
return (ISCSI_STATUS_LOGIN_FAILED);
login_retry:
/* retry this failure if we haven't run out of time */
if (icp->conn_login_max > ddi_get_lbolt()) {
if (icp->conn_state == ISCSI_CONN_STATE_POLLING) {
icp->conn_login_min = ddi_get_lbolt() +
SEC_TO_TICK(ISCSI_LOGIN_POLLING_DELAY);
} else {
icp->conn_login_min = ddi_get_lbolt() +
SEC_TO_TICK(ISCSI_LOGIN_RETRY_DELAY);
}
if (itp->t_blocking == B_TRUE) {
goto login_start;
} else {
if (ddi_taskq_dispatch(isp->sess_taskq,
(void(*)())iscsi_login_start, itp, DDI_SLEEP) !=
DDI_SUCCESS) {
iscsi_login_end(icp,
ISCSI_STATUS_LOGIN_TIMED_OUT, itp);
}
return (ISCSI_STATUS_SUCCESS);
}
} else {
/* Retries exceeded */
iscsi_login_end(icp, ISCSI_STATUS_LOGIN_TIMED_OUT, itp);
}
return (ISCSI_STATUS_LOGIN_FAILED);
}
static void
iscsi_login_end(iscsi_conn_t *icp, iscsi_status_t status, iscsi_task_t *itp)
{
iscsi_sess_t *isp;
ASSERT(icp != NULL);
isp = icp->conn_sess;
ASSERT(isp != NULL);
if (status == ISCSI_STATUS_SUCCESS) {
/* Inform IDM of the relevant negotiated values */
iscsi_notice_key_values(icp);
/* We are now logged in */
iscsi_conn_update_state(icp, ISCSI_CONN_STATE_LOGGED_IN);
/* startup TX thread */
(void) iscsi_thread_start(icp->conn_tx_thread);
/*
* Move login state machine to LOGIN_FFP. This will
* release the taskq thread handling the CN_FFP_ENABLED
* allowing the IDM connection state machine to resume
* processing events
*/
iscsi_login_update_state(icp, LOGIN_FFP);
/* Notify the session that a connection is logged in */
mutex_enter(&isp->sess_state_mutex);
iscsi_sess_state_machine(isp, ISCSI_SESS_EVENT_N1);
mutex_exit(&isp->sess_state_mutex);
} else {
/* If login failed reset nego tpgt */
isp->sess_tpgt_nego = ISCSI_DEFAULT_TPGT;
mutex_enter(&icp->conn_state_mutex);
switch (icp->conn_state) {
case ISCSI_CONN_STATE_IN_LOGIN:
iscsi_conn_update_state_locked(icp,
ISCSI_CONN_STATE_FREE);
mutex_exit(&icp->conn_state_mutex);
break;
case ISCSI_CONN_STATE_FAILED:
if (status == ISCSI_STATUS_LOGIN_FAILED) {
iscsi_conn_update_state_locked(icp,
ISCSI_CONN_STATE_FREE);
} else {
/* ISCSI_STATUS_LOGIN_TIMED_OUT */
iscsi_conn_update_state_locked(icp,
ISCSI_CONN_STATE_POLLING);
}
mutex_exit(&icp->conn_state_mutex);
mutex_enter(&isp->sess_state_mutex);
iscsi_sess_state_machine(isp, ISCSI_SESS_EVENT_N6);
mutex_exit(&isp->sess_state_mutex);
if (status == ISCSI_STATUS_LOGIN_TIMED_OUT) {
iscsi_conn_retry(isp, icp);
}
break;
case ISCSI_CONN_STATE_POLLING:
if (status == ISCSI_STATUS_LOGIN_FAILED) {
iscsi_conn_update_state_locked(icp,
ISCSI_CONN_STATE_FREE);
mutex_exit(&icp->conn_state_mutex);
mutex_enter(&isp->sess_state_mutex);
iscsi_sess_state_machine(isp,
ISCSI_SESS_EVENT_N6);
mutex_exit(&isp->sess_state_mutex);
} else {
/* ISCSI_STATUS_LOGIN_TIMED_OUT */
if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) {
mutex_exit(&icp->conn_state_mutex);
iscsi_conn_retry(isp, icp);
} else {
iscsi_conn_update_state_locked(icp,
ISCSI_CONN_STATE_FREE);
mutex_exit(&icp->conn_state_mutex);
}
}
break;
default:
ASSERT(0);
break;
}
}
if (itp->t_blocking == B_FALSE) {
kmem_free(itp, sizeof (iscsi_task_t));
}
}
/*
* +--------------------------------------------------------------------+
* | Begin of protocol login routines |
* +--------------------------------------------------------------------+
*/
/*
* iscsi_login - Attempt to login to the target. The caller
* must check the status class to determine if the login succeeded.
* A return of 1 does not mean the login succeeded, it just means
* this function worked, and the status class is valid info. This
* allows the caller to decide whether or not to retry logins, so
* that we don't have any policy logic here.
*/
iscsi_status_t
iscsi_login(iscsi_conn_t *icp, uint8_t *status_class, uint8_t *status_detail)
{
iscsi_status_t rval = ISCSI_STATUS_INTERNAL_ERROR;
struct iscsi_sess *isp = NULL;
IscsiAuthClient *auth_client = NULL;
int max_data_length = 0;
char *data = NULL;
idm_pdu_t *text_pdu;
char *buffer;
size_t bufsize;
iscsi_login_rsp_hdr_t *ilrhp;
clock_t response_timeout, timeout_result;
buffer = icp->conn_login_data;
bufsize = icp->conn_login_max_data_length;
ASSERT(icp != NULL);
ASSERT(buffer != NULL);
ASSERT(status_class != NULL);
ASSERT(status_detail != NULL);
isp = icp->conn_sess;
ASSERT(isp != NULL);
/*
* prepare the connection, hold IDM connection until login completes
*/
icp->conn_current_stage = ISCSI_INITIAL_LOGIN_STAGE;
icp->conn_partial_response = 0;
if (isp->sess_auth.auth_buffers &&
isp->sess_auth.num_auth_buffers) {
auth_client = (IscsiAuthClient *)isp->
sess_auth.auth_buffers[0].address;
/*
* prepare for authentication
*/
if (iscsiAuthClientInit(iscsiAuthNodeTypeInitiator,
isp->sess_auth.num_auth_buffers,
isp->sess_auth.auth_buffers) !=
iscsiAuthStatusNoError) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"unable to initialize authentication",
icp->conn_oid);
iscsi_login_disconnect(icp);
iscsi_login_update_state(icp, LOGIN_DONE);
return (ISCSI_STATUS_INTERNAL_ERROR);
}
if (iscsiAuthClientSetVersion(auth_client,
iscsiAuthVersionRfc) != iscsiAuthStatusNoError) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"unable to set authentication", icp->conn_oid);
goto iscsi_login_done;
}
if (isp->sess_auth.username &&
(iscsiAuthClientSetUsername(auth_client,
isp->sess_auth.username) !=
iscsiAuthStatusNoError)) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"unable to set username", icp->conn_oid);
goto iscsi_login_done;
}
if (isp->sess_auth.password &&
(iscsiAuthClientSetPassword(auth_client,
isp->sess_auth.password, isp->sess_auth.password_length) !=
iscsiAuthStatusNoError)) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"unable to set password", icp->conn_oid);
goto iscsi_login_done;
}
if (iscsiAuthClientSetIpSec(auth_client, 1) !=
iscsiAuthStatusNoError) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"unable to set ipsec", icp->conn_oid);
goto iscsi_login_done;
}
if (iscsiAuthClientSetAuthRemote(auth_client,
isp->sess_auth.bidirectional_auth) !=
iscsiAuthStatusNoError) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"unable to set remote authentication",
icp->conn_oid);
goto iscsi_login_done;
}
}
/*
* exchange PDUs until the login stage is complete, or an error occurs
*/
do {
/* setup */
bzero(buffer, bufsize);
data = buffer;
max_data_length = bufsize;
rval = ISCSI_STATUS_INTERNAL_ERROR;
text_pdu = idm_pdu_alloc(sizeof (iscsi_hdr_t), 0);
idm_pdu_init(text_pdu, icp->conn_ic, NULL, NULL);
/*
* fill in the PDU header and text data based on the
* login stage that we're in
*/
rval = iscsi_make_login_pdu(icp, text_pdu, data,
max_data_length);
if (!ISCSI_SUCCESS(rval)) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"unable to make login pdu", icp->conn_oid);
goto iscsi_login_done;
}
mutex_enter(&icp->conn_login_mutex);
/*
* Make sure we are still in LOGIN_START or LOGIN_RX
* state before switching to LOGIN_TX. It's possible
* for a connection failure to move us to LOGIN_ERROR
* before we get to this point.
*/
if (((icp->conn_login_state != LOGIN_READY) &&
(icp->conn_login_state != LOGIN_RX)) ||
!icp->conn_state_idm_connected) {
/* Error occurred */
mutex_exit(&icp->conn_login_mutex);
rval = (ISCSI_STATUS_INTERNAL_ERROR);
goto iscsi_login_done;
}
iscsi_login_update_state_locked(icp, LOGIN_TX);
icp->conn_login_data = data;
icp->conn_login_max_data_length = max_data_length;
/*
* send a PDU to the target. This is asynchronous but
* we don't have any particular need for a TX completion
* notification since we are going to block waiting for the
* receive.
*/
response_timeout = ddi_get_lbolt() +
SEC_TO_TICK(ISCSI_RX_TIMEOUT_VALUE);
idm_pdu_tx(text_pdu);
/*
* Wait for login failure indication or login RX.
* Handler for login response PDU will copy any data into
* the buffer pointed to by icp->conn_login_data
*/
while (icp->conn_login_state == LOGIN_TX) {
timeout_result = cv_timedwait(&icp->conn_login_cv,
&icp->conn_login_mutex, response_timeout);
if (timeout_result == -1)
break;
}
if (icp->conn_login_state != LOGIN_RX) {
mutex_exit(&icp->conn_login_mutex);
rval = (ISCSI_STATUS_INTERNAL_ERROR);
goto iscsi_login_done;
}
mutex_exit(&icp->conn_login_mutex);
/* check the PDU response type */
ilrhp = (iscsi_login_rsp_hdr_t *)&icp->conn_login_resp_hdr;
if (ilrhp->opcode != ISCSI_OP_LOGIN_RSP) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"received invalid login response (0x%02x)",
icp->conn_oid, ilrhp->opcode);
rval = (ISCSI_STATUS_PROTOCOL_ERROR);
goto iscsi_login_done;
}
/*
* give the caller the status class and detail from the
* last login response PDU received
*/
if (status_class) {
*status_class = ilrhp->status_class;
}
if (status_detail) {
*status_detail = ilrhp->status_detail;
}
switch (ilrhp->status_class) {
case ISCSI_STATUS_CLASS_SUCCESS:
/*
* process this response and possibly continue
* sending PDUs
*/
rval = iscsi_process_login_response(icp,
ilrhp, (char *)icp->conn_login_data,
icp->conn_login_max_data_length);
/* pass back whatever error we discovered */
if (!ISCSI_SUCCESS(rval)) {
goto iscsi_login_done;
}
break;
case ISCSI_STATUS_CLASS_REDIRECT:
/*
* we need to process this response to get the
* TargetAddress of the redirect, but we don't
* care about the return code.
*/
(void) iscsi_process_login_response(icp,
ilrhp, (char *)icp->conn_login_data,
icp->conn_login_max_data_length);
rval = ISCSI_STATUS_SUCCESS;
goto iscsi_login_done;
case ISCSI_STATUS_CLASS_INITIATOR_ERR:
if (ilrhp->status_detail ==
ISCSI_LOGIN_STATUS_AUTH_FAILED) {
cmn_err(CE_WARN, "iscsi connection(%u) login "
"failed - login failed to authenticate "
"with target", icp->conn_oid);
}
rval = ISCSI_STATUS_SUCCESS;
goto iscsi_login_done;
default:
/*
* some sort of error, login terminated unsuccessfully,
* though this function did it's job. the caller must
* check the status_class and status_detail and decide
* what to do next.
*/
rval = ISCSI_STATUS_SUCCESS;
goto iscsi_login_done;
}
} while (icp->conn_current_stage != ISCSI_FULL_FEATURE_PHASE);
rval = ISCSI_STATUS_SUCCESS;
iscsi_login_done:
if (auth_client) {
if (iscsiAuthClientFinish(auth_client) !=
iscsiAuthStatusNoError) {
cmn_err(CE_WARN, "iscsi connection(%u) login "
"failed - login failed to authenticate "
"with target", icp->conn_oid);
if (ISCSI_SUCCESS(rval))
rval = ISCSI_STATUS_INTERNAL_ERROR;
}
}
if (ISCSI_SUCCESS(rval) &&
(*status_class == ISCSI_STATUS_CLASS_SUCCESS)) {
mutex_enter(&icp->conn_state_mutex);
while (!icp->conn_state_ffp)
cv_wait(&icp->conn_state_change,
&icp->conn_state_mutex);
mutex_exit(&icp->conn_state_mutex);
} else {
iscsi_login_disconnect(icp);
}
iscsi_login_update_state(icp, LOGIN_DONE);
return (rval);
}
/*
* iscsi_make_login_pdu -
*
*/
static iscsi_status_t
iscsi_make_login_pdu(iscsi_conn_t *icp, idm_pdu_t *text_pdu,
char *data, int max_data_length)
{
struct iscsi_sess *isp = NULL;
int transit = 0;
iscsi_hdr_t *ihp = text_pdu->isp_hdr;
iscsi_login_hdr_t *ilhp =
(iscsi_login_hdr_t *)text_pdu->isp_hdr;
IscsiAuthClient *auth_client = NULL;
int keytype = 0;
int rc = 0;
char value[iscsiAuthStringMaxLength];
ASSERT(icp != NULL);
ASSERT(text_pdu != NULL);
isp = icp->conn_sess;
ASSERT(isp != NULL);
auth_client =
(isp->sess_auth.auth_buffers && isp->sess_auth.num_auth_buffers) ?
(IscsiAuthClient *)isp->sess_auth.auth_buffers[0].address : NULL;
/*
* initialize the PDU header
*/
bzero(ilhp, sizeof (*ilhp));
ilhp->opcode = ISCSI_OP_LOGIN_CMD | ISCSI_OP_IMMEDIATE;
ilhp->cid = icp->conn_cid;
bcopy(&isp->sess_isid[0], &ilhp->isid[0], sizeof (isp->sess_isid));
ilhp->tsid = 0;
/*
* Set data buffer pointer. The calls to iscsi_add_text will update the
* data length.
*/
text_pdu->isp_data = (uint8_t *)data;
/* don't increment on immediate */
ilhp->cmdsn = htonl(isp->sess_cmdsn);
ilhp->min_version = ISCSI_DRAFT20_VERSION;
ilhp->max_version = ISCSI_DRAFT20_VERSION;
/*
* we have to send 0 until full-feature stage
*/
ilhp->expstatsn = htonl(icp->conn_expstatsn);
/*
* the very first Login PDU has some additional requirements,
* * and we need to decide what stage to start in.
*/
if (icp->conn_current_stage == ISCSI_INITIAL_LOGIN_STAGE) {
if ((isp->sess_hba->hba_name) &&
(isp->sess_hba->hba_name[0])) {
if (!iscsi_add_text(text_pdu, max_data_length,
"InitiatorName",
(char *)isp->sess_hba->hba_name)) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
} else {
cmn_err(CE_WARN, "iscsi connection(%u) login "
"failed - initiator name is required",
icp->conn_oid);
return (ISCSI_STATUS_INTERNAL_ERROR);
}
if ((isp->sess_hba->hba_alias) &&
(isp->sess_hba->hba_alias[0])) {
if (!iscsi_add_text(text_pdu, max_data_length,
"InitiatorAlias",
(char *)isp->sess_hba->hba_alias)) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
}
if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) {
if (isp->sess_name[0] != '\0') {
if (!iscsi_add_text(text_pdu, max_data_length,
"TargetName", (char *)isp->sess_name)) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
}
if (!iscsi_add_text(text_pdu, max_data_length,
"SessionType", "Normal")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
} else if (isp->sess_type == ISCSI_SESS_TYPE_DISCOVERY) {
if (!iscsi_add_text(text_pdu, max_data_length,
"SessionType", "Discovery")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
} else {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
if (auth_client) {
/* we're prepared to do authentication */
icp->conn_current_stage =
ISCSI_SECURITY_NEGOTIATION_STAGE;
} else {
/* can't do any authentication, skip that stage */
icp->conn_current_stage =
ISCSI_OP_PARMS_NEGOTIATION_STAGE;
}
}
/*
* fill in text based on the stage
*/
switch (icp->conn_current_stage) {
case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
/*
* we always try to go from op params to full
* feature stage
*/
icp->conn_next_stage = ISCSI_FULL_FEATURE_PHASE;
transit = 1;
/*
* The terminology here may have gotten dated. A partial
* response is a login response that doesn't complete a
* login. If we haven't gotten a partial response, then
* either we shouldn't be here, or we just switched to
* this stage, and need to start offering keys.
*/
if (!icp->conn_partial_response) {
/*
* request the desired settings the first time
* we are in this stage
*/
switch (icp->conn_params.header_digest) {
case ISCSI_DIGEST_NONE:
if (!iscsi_add_text(text_pdu,
max_data_length, "HeaderDigest", "None")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
break;
case ISCSI_DIGEST_CRC32C:
if (!iscsi_add_text(text_pdu,
max_data_length,
"HeaderDigest", "CRC32C")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
break;
case ISCSI_DIGEST_CRC32C_NONE:
if (!iscsi_add_text(text_pdu,
max_data_length, "HeaderDigest",
"CRC32C,None")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
break;
default:
case ISCSI_DIGEST_NONE_CRC32C:
if (!iscsi_add_text(text_pdu,
max_data_length, "HeaderDigest",
"None,CRC32C")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
break;
}
switch (icp->conn_params.data_digest) {
case ISCSI_DIGEST_NONE:
if (!iscsi_add_text(text_pdu,
max_data_length, "DataDigest", "None")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
break;
case ISCSI_DIGEST_CRC32C:
if (!iscsi_add_text(text_pdu,
max_data_length, "DataDigest", "CRC32C")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
break;
case ISCSI_DIGEST_CRC32C_NONE:
if (!iscsi_add_text(text_pdu,
max_data_length, "DataDigest",
"CRC32C,None")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
break;
default:
case ISCSI_DIGEST_NONE_CRC32C:
if (!iscsi_add_text(text_pdu,
max_data_length, "DataDigest",
"None,CRC32C")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
break;
}
(void) sprintf(value, "%d",
icp->conn_params.max_recv_data_seg_len);
if (!iscsi_add_text(text_pdu, max_data_length,
"MaxRecvDataSegmentLength", value)) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
(void) sprintf(value, "%d",
icp->conn_params.default_time_to_wait);
if (!iscsi_add_text(text_pdu,
max_data_length, "DefaultTime2Wait", value)) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
(void) sprintf(value, "%d",
icp->conn_params.default_time_to_retain);
if (!iscsi_add_text(text_pdu,
max_data_length, "DefaultTime2Retain", value)) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
(void) sprintf(value, "%d",
icp->conn_params.error_recovery_level);
if (!iscsi_add_text(text_pdu,
max_data_length, "ErrorRecoveryLevel", "0")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
if (!iscsi_add_text(text_pdu,
max_data_length, "IFMarker",
icp->conn_params.ifmarker ? "Yes" : "No")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
if (!iscsi_add_text(text_pdu,
max_data_length, "OFMarker",
icp->conn_params.ofmarker ? "Yes" : "No")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
/*
* The following login parameters are "Irrelevant"
* for discovery sessions
*/
if (isp->sess_type != ISCSI_SESS_TYPE_DISCOVERY) {
if (!iscsi_add_text(text_pdu,
max_data_length, "InitialR2T",
icp->conn_params.initial_r2t ?
"Yes" : "No")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
if (!iscsi_add_text(text_pdu,
max_data_length, "ImmediateData",
icp->conn_params.immediate_data ?
"Yes" : "No")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
(void) sprintf(value, "%d",
icp->conn_params.max_burst_length);
if (!iscsi_add_text(text_pdu,
max_data_length, "MaxBurstLength", value)) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
(void) sprintf(value, "%d",
icp->conn_params.first_burst_length);
if (!iscsi_add_text(text_pdu, max_data_length,
"FirstBurstLength", value)) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
(void) sprintf(value, "%d",
icp->conn_params.max_outstanding_r2t);
if (!iscsi_add_text(text_pdu, max_data_length,
"MaxOutstandingR2T", value)) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
(void) sprintf(value, "%d",
icp->conn_params.max_connections);
if (!iscsi_add_text(text_pdu, max_data_length,
"MaxConnections", value)) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
if (!iscsi_add_text(text_pdu,
max_data_length, "DataPDUInOrder",
icp->conn_params.data_pdu_in_order ?
"Yes" : "No")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
if (!iscsi_add_text(text_pdu,
max_data_length, "DataSequenceInOrder",
icp->conn_params.data_sequence_in_order ?
"Yes" : "No")) {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
}
}
break;
case ISCSI_SECURITY_NEGOTIATION_STAGE:
keytype = iscsiAuthKeyTypeNone;
rc = iscsiAuthClientSendTransitBit(auth_client, &transit);
/*
* see if we're ready for a stage change
*/
if (rc == iscsiAuthStatusNoError) {
if (transit) {
icp->conn_next_stage =
ISCSI_OP_PARMS_NEGOTIATION_STAGE;
} else {
icp->conn_next_stage =
ISCSI_SECURITY_NEGOTIATION_STAGE;
}
} else {
return (ISCSI_STATUS_INTERNAL_ERROR);
}
/*
* enumerate all the keys the auth code might want to send
*/
while (iscsiAuthClientGetNextKeyType(&keytype) ==
iscsiAuthStatusNoError) {
int present = 0;
char *key = (char *)iscsiAuthClientGetKeyName(keytype);
int key_length = key ? strlen(key) : 0;
int pdu_length = text_pdu->isp_datalen;
char *auth_value = data + pdu_length + key_length + 1;
unsigned int max_length = max_data_length -
(pdu_length + key_length + 1);
/*
* add the key/value pairs the auth code wants to
* send directly to the PDU, since they could in
* theory be large.
*/
rc = iscsiAuthClientSendKeyValue(auth_client, keytype,
&present, auth_value, max_length);
if ((rc == iscsiAuthStatusNoError) && present) {
/*
* actually fill in the key
*/
(void) strncpy(&data[pdu_length], key,
key_length);
pdu_length += key_length;
data[pdu_length] = '=';
pdu_length++;
/*
* adjust the PDU's data segment length to
* include the value and trailing NULL
*/
pdu_length += strlen(auth_value) + 1;
text_pdu->isp_datalen = pdu_length;
hton24(ihp->dlength, pdu_length);
}
}
break;
case ISCSI_FULL_FEATURE_PHASE:
cmn_err(CE_WARN, "iscsi connection(%u) login "
"failed - can't send login in full feature stage",
icp->conn_oid);
return (ISCSI_STATUS_INTERNAL_ERROR);
default:
cmn_err(CE_WARN, "iscsi connection(%u) login "
"failed - can't send login in unknown stage (%d)",
icp->conn_oid, icp->conn_current_stage);
return (ISCSI_STATUS_INTERNAL_ERROR);
}
/* fill in the flags */
ilhp->flags = icp->conn_current_stage << 2;
if (transit) {
/* transit to the next stage */
ilhp->flags |= icp->conn_next_stage;
ilhp->flags |= ISCSI_FLAG_LOGIN_TRANSIT;
} else {
/* next == current */
ilhp->flags |= icp->conn_current_stage;
}
return (ISCSI_STATUS_SUCCESS);
}
/*
* iscsi_process_login_response - This assumes the text data is
* always NUL terminated. The caller can always arrange for that by
* using a slightly larger buffer than the max PDU size, and then
* appending a NUL to the PDU.
*/
static iscsi_status_t
iscsi_process_login_response(iscsi_conn_t *icp,
iscsi_login_rsp_hdr_t *ilrhp, char *data, int max_data_length)
{
iscsi_sess_t *isp = NULL;
IscsiAuthClient *auth_client = NULL;
int transit = 0;
char *text = data;
char *end = NULL;
int pdu_current_stage = 0;
int pdu_next_stage = 0;
int debug_status = 0;
unsigned long tmp;
char *tmpe;
boolean_t fbl_irrelevant = B_FALSE;
ASSERT(icp != NULL);
ASSERT(ilrhp != NULL);
ASSERT(data != NULL);
isp = icp->conn_sess;
ASSERT(isp != NULL);
auth_client =
(isp->sess_auth.auth_buffers && isp->sess_auth.num_auth_buffers) ?
(IscsiAuthClient *) isp->sess_auth.auth_buffers[0].address : NULL;
transit = ilrhp->flags & ISCSI_FLAG_LOGIN_TRANSIT;
/* verify the initial buffer was big enough to hold everything */
end = text + ntoh24(ilrhp->dlength) + 1;
if (end >= (data + max_data_length)) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"buffer too small", icp->conn_oid);
return (ISCSI_STATUS_INTERNAL_ERROR);
}
*end = '\0';
/* if the response status was success, sanity check the response */
if (ilrhp->status_class == ISCSI_STATUS_CLASS_SUCCESS) {
/* check the active version */
if (ilrhp->active_version != ISCSI_DRAFT20_VERSION) {
cmn_err(CE_WARN, "iscsi connection(%u) login "
"failed - target version incompatible "
"received:0x%0x2x expected:0x%02x",
icp->conn_oid, ilrhp->active_version,
ISCSI_DRAFT20_VERSION);
return (ISCSI_STATUS_VERSION_MISMATCH);
}
/* make sure the current stage matches */
pdu_current_stage = (ilrhp->flags &
ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2;
if (pdu_current_stage != icp->conn_current_stage) {
cmn_err(CE_WARN, "iscsi connection(%u) login "
"failed - login response contained invalid "
"stage %d", icp->conn_oid, pdu_current_stage);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
/*
* Make sure that we're actually advancing
* if the T-bit is set
*/
pdu_next_stage = ilrhp->flags &
ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK;
if (transit && (pdu_next_stage <= icp->conn_current_stage)) {
cmn_err(CE_WARN, "iscsi connection(%u) login "
"failed - login response wants to go to stage "
"%d, but we want stage %d", icp->conn_oid,
pdu_next_stage, icp->conn_next_stage);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
}
if (icp->conn_current_stage == ISCSI_SECURITY_NEGOTIATION_STAGE) {
if (iscsiAuthClientRecvBegin(auth_client) !=
iscsiAuthStatusNoError) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"authentication receive failed", icp->conn_oid);
return (ISCSI_STATUS_INTERNAL_ERROR);
}
if (iscsiAuthClientRecvTransitBit(auth_client,
transit) != iscsiAuthStatusNoError) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"authentication transmit failed", icp->conn_oid);
return (ISCSI_STATUS_INTERNAL_ERROR);
}
}
/*
* scan the text data
*/
more_text:
while (text && (text < end)) {
char *value = NULL;
char *value_end = NULL;
/*
* skip any NULs separating each text key=value pair
*/
while ((text < end) && (*text == '\0')) {
text++;
}
if (text >= end) {
break;
}
/*
* handle keys appropriate for each stage
*/
switch (icp->conn_current_stage) {
case ISCSI_SECURITY_NEGOTIATION_STAGE:
/*
* a few keys are possible in Security stage
* * which the auth code doesn't care about,
* * but which we might want to see, or at
* * least not choke on.
*/
if (iscsi_find_key_value("TargetAlias",
text, end, &value, &value_end)) {
isp->sess_alias_length =
sizeof (isp->sess_alias) - 1;
if ((value_end - value) <
isp->sess_alias_length) {
isp->sess_alias_length =
value_end - value;
}
bcopy(value, isp->sess_alias,
isp->sess_alias_length);
isp->sess_alias[isp->sess_alias_length + 1] =
'\0';
text = value_end;
} else if (iscsi_find_key_value("TargetAddress",
text, end, &value, &value_end)) {
if (!ISCSI_SUCCESS(iscsi_update_address(
icp, value))) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - login redirection "
"invalid", icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
text = value_end;
} else if (iscsi_find_key_value("TargetPortalGroupTag",
text, end, &value, &value_end)) {
/*
* We should have already obtained this via
* discovery. We've already picked an isid,
* so the most we can do is confirm we reached
* the portal group we were expecting to.
*/
if (ddi_strtoul(value, &tmpe, 0, &tmp) != 0) {
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
if (isp->sess_tpgt_conf != ISCSI_DEFAULT_TPGT) {
if (tmp != isp->sess_tpgt_conf) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - target "
"protocol group tag mismatch, expected %d, received %lu",
icp->conn_oid, isp->sess_tpgt_conf, tmp);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
}
isp->sess_tpgt_nego = (int)tmp;
text = value_end;
} else {
/*
* any key we don't recognize either goes
* to the auth code, or we choke on it
*/
int keytype = iscsiAuthKeyTypeNone;
while (iscsiAuthClientGetNextKeyType(
&keytype) == iscsiAuthStatusNoError) {
char *key =
(char *)iscsiAuthClientGetKeyName(
keytype);
if ((key) &&
(iscsi_find_key_value(key,
text, end, &value, &value_end))) {
if (iscsiAuthClientRecvKeyValue
(auth_client, keytype,
value) !=
iscsiAuthStatusNoError) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - can't accept "
"%s in security stage", icp->conn_oid, text);
return (ISCSI_STATUS_NEGO_FAIL);
}
text = value_end;
goto more_text;
}
}
cmn_err(CE_WARN, "iscsi connection(%u) login failed - can't except "
"%s in security stage", icp->conn_oid, text);
return (ISCSI_STATUS_NEGO_FAIL);
}
break;
case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
if (iscsi_find_key_value("TargetAlias", text,
end, &value, &value_end)) {
isp->sess_alias_length =
sizeof (isp->sess_alias) - 1;
if ((value_end - value) <
isp->sess_alias_length) {
isp->sess_alias_length =
value_end - value;
}
bcopy(value, isp->sess_alias,
isp->sess_alias_length);
isp->sess_alias[isp->sess_alias_length + 1] =
'\0';
text = value_end;
} else if (iscsi_find_key_value("TargetAddress",
text, end, &value, &value_end)) {
if (!ISCSI_SUCCESS(iscsi_update_address(
icp, value))) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - login "
"redirection invalid", icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
text = value_end;
} else if (iscsi_find_key_value("TargetPortalGroupTag",
text, end, &value, &value_end)) {
/*
* We should have already obtained this via
* discovery. We've already picked an isid,
* so the most we can do is confirm we reached
* the portal group we were expecting to.
*/
if (ddi_strtoul(value, &tmpe, 0, &tmp) != 0) {
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
if (isp->sess_tpgt_conf != ISCSI_DEFAULT_TPGT) {
if (tmp != isp->sess_tpgt_conf) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - target portal "
"tag mismatch, expected:%d received:%lu", icp->conn_oid,
isp->sess_tpgt_conf, tmp);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
}
isp->sess_tpgt_nego = (int)tmp;
text = value_end;
} else if (iscsi_find_key_value("InitialR2T",
text, end, &value, &value_end)) {
/*
* iSCSI RFC section 12.10 states that
* InitialR2T is Irrelevant for a
* discovery session.
*/
if (isp->sess_type ==
ISCSI_SESS_TYPE_DISCOVERY) {
/* EMPTY */
} else if (value == NULL) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - InitialR2T is "
"invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
} else if (strcmp(value, "Yes") == 0) {
icp->conn_params.initial_r2t = B_TRUE;
} else if (strcmp(value, "No") == 0) {
icp->conn_params.initial_r2t = B_FALSE;
} else {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - InitialR2T is "
"invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
text = value_end;
} else if (iscsi_find_key_value("ImmediateData",
text, end, &value, &value_end)) {
/*
* iSCSI RFC section 12.11 states that
* ImmediateData is Irrelevant for a
* discovery session.
*/
if (isp->sess_type ==
ISCSI_SESS_TYPE_DISCOVERY) {
/* EMPTY */
} else if (value == NULL) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - ImmediateData is "
"invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
} else if (strcmp(value, "Yes") == 0) {
icp->conn_params.immediate_data = 1;
} else if (strcmp(value, "No") == 0) {
icp->conn_params.immediate_data = 0;
} else {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - ImmediateData is "
"invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
text = value_end;
} else if (iscsi_find_key_value(
"MaxRecvDataSegmentLength", text, end,
&value, &value_end)) {
if (ddi_strtoul(value, &tmpe, 0, &tmp) != 0) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - MaxRecvDataSegment"
"Length is invalid - protocol "
"error", icp->conn_oid);
return (ISCSI_STATUS_NEGO_FAIL);
}
icp->conn_params.max_recv_data_seg_len =
icp->conn_params.max_xmit_data_seg_len =
(int)tmp;
text = value_end;
} else if (iscsi_find_key_value("FirstBurstLength",
text, end, &value, &value_end)) {
/*
* iSCSI RFC section 12.14 states that
* FirstBurstLength is Irrelevant if
* InitialR2T=Yes and ImmediateData=No
* or is this is a discovery session.
*/
if ((isp->sess_type ==
ISCSI_SESS_TYPE_DISCOVERY)) {
/* EMPTY */
} else if (value &&
(strcmp(value, "Irrelevant") == 0)) {
/* irrelevant */
fbl_irrelevant = B_TRUE;
} else if (ddi_strtoul(
value, &tmpe, 0, &tmp) != 0) {
/* bad value */
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - FirstBurstLength"
"is invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
} else {
/* good value */
icp->conn_params.first_burst_length =
(int)tmp;
}
text = value_end;
} else if (iscsi_find_key_value("MaxBurstLength",
text, end, &value, &value_end)) {
/*
* iSCSI RFC section 12.13 states that
* MaxBurstLength is Irrelevant for a
* discovery session.
*/
if (isp->sess_type ==
ISCSI_SESS_TYPE_DISCOVERY) {
/* EMPTY */
} else if (ddi_strtoul(
value, &tmpe, 0, &tmp) != 0) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - MaxBurstLength"
"is invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
} else {
icp->conn_params.max_burst_length =
(int)tmp;
}
text = value_end;
} else if (iscsi_find_key_value("HeaderDigest",
text, end, &value, &value_end)) {
if (strcmp(value, "None") == 0) {
if (icp->conn_params.header_digest !=
ISCSI_DIGEST_CRC32C) {
icp->conn_params.header_digest =
ISCSI_DIGEST_NONE;
} else {
cmn_err(CE_WARN, "iscsi "
"connection(%u) login "
"failed - HeaderDigest="
"CRC32 is required, can't "
"accept %s",
icp->conn_oid, text);
return (ISCSI_STATUS_NEGO_FAIL);
}
} else if (strcmp(value, "CRC32C") == 0) {
if (icp->conn_params.header_digest !=
ISCSI_DIGEST_NONE) {
icp->conn_params.header_digest =
ISCSI_DIGEST_CRC32C;
} else {
cmn_err(CE_WARN, "iscsi "
"connection(%u) login "
"failed - HeaderDigest="
"None is required, can't "
"accept %s",
icp->conn_oid, text);
return (ISCSI_STATUS_NEGO_FAIL);
}
} else {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - HeaderDigest "
"can't accept %s", icp->conn_oid,
text);
return (ISCSI_STATUS_NEGO_FAIL);
}
text = value_end;
} else if (iscsi_find_key_value("DataDigest", text,
end, &value, &value_end)) {
if (strcmp(value, "None") == 0) {
if (icp->conn_params.data_digest !=
ISCSI_DIGEST_CRC32C) {
icp->conn_params.data_digest =
ISCSI_DIGEST_NONE;
} else {
cmn_err(CE_WARN, "iscsi "
"connection(%u) login "
"failed - DataDigest="
"CRC32C is required, "
"can't accept %s",
icp->conn_oid, text);
return (ISCSI_STATUS_NEGO_FAIL);
}
} else if (strcmp(value, "CRC32C") == 0) {
if (icp->conn_params.data_digest !=
ISCSI_DIGEST_NONE) {
icp->conn_params.data_digest =
ISCSI_DIGEST_CRC32C;
} else {
cmn_err(CE_WARN, "iscsi "
"connection(%u) login "
"failed - DataDigest=None "
"is required, can't "
"accept %s",
icp->conn_oid, text);
return (ISCSI_STATUS_NEGO_FAIL);
}
} else {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - can't accept %s",
icp->conn_oid, text);
return (ISCSI_STATUS_NEGO_FAIL);
}
text = value_end;
} else if (iscsi_find_key_value("DefaultTime2Wait",
text, end, &value, &value_end)) {
if (ddi_strtoul(value, &tmpe, 0, &tmp) != 0) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - DefaultTime2Wait "
"is invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
icp->conn_params.default_time_to_wait =
(int)tmp;
text = value_end;
} else if (iscsi_find_key_value("DefaultTime2Retain",
text, end, &value, &value_end)) {
if (ddi_strtoul(value, &tmpe, 0, &tmp) != 0) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - DefaultTime2Retain "
"is invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
icp->conn_params.default_time_to_retain =
(int)tmp;
text = value_end;
} else if (iscsi_find_key_value("OFMarker", text,
end, &value, &value_end)) {
/*
* result function is AND, target must
* honor our No
*/
text = value_end;
} else if (iscsi_find_key_value("OFMarkInt", text,
end, &value, &value_end)) {
/*
* we don't do markers, so we don't care
*/
text = value_end;
} else if (iscsi_find_key_value("IFMarker", text,
end, &value, &value_end)) {
/*
* result function is AND, target must
* honor our No
*/
text = value_end;
} else if (iscsi_find_key_value("IFMarkInt", text,
end, &value, &value_end)) {
/*
* we don't do markers, so we don't care
*/
text = value_end;
} else if (iscsi_find_key_value("DataPDUInOrder",
text, end, &value, &value_end)) {
/*
* iSCSI RFC section 12.18 states that
* DataPDUInOrder is Irrelevant for a
* discovery session.
*/
if (isp->sess_type ==
ISCSI_SESS_TYPE_DISCOVERY) {
/* EMPTY */
} else if (value == NULL) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - InitialR2T is "
"invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
} else if (strcmp(value, "Yes") == 0) {
icp->conn_params.data_pdu_in_order =
B_TRUE;
} else if (strcmp(value, "No") == 0) {
icp->conn_params.data_pdu_in_order =
B_FALSE;
} else {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - InitialR2T is "
"invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
text = value_end;
} else if (iscsi_find_key_value("DataSequenceInOrder",
text, end, &value, &value_end)) {
/*
* iSCSI RFC section 12.19 states that
* DataSequenceInOrder is Irrelevant for a
* discovery session.
*/
if (isp->sess_type ==
ISCSI_SESS_TYPE_DISCOVERY) {
/* EMPTY */
} else if (value == NULL) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - InitialR2T is "
"invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
} else if (strcmp(value, "Yes") == 0) {
icp->conn_params.
data_sequence_in_order = B_TRUE;
} else if (strcmp(value, "No") == 0) {
icp->conn_params.
data_sequence_in_order = B_FALSE;
} else {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - InitialR2T is "
"invalid - protocol error",
icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
text = value_end;
} else if (iscsi_find_key_value("MaxOutstandingR2T",
text, end, &value, &value_end)) {
/*
* iSCSI RFC section 12.17 states that
* MaxOutstandingR2T is Irrelevant for a
* discovery session.
*/
if (isp->sess_type ==
ISCSI_SESS_TYPE_DISCOVERY) {
/* EMPTY */
} else if (strcmp(value, "1")) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - can't accept "
"MaxOutstandingR2T %s",
icp->conn_oid, value);
return (ISCSI_STATUS_NEGO_FAIL);
}
text = value_end;
} else if (iscsi_find_key_value("MaxConnections",
text, end, &value, &value_end)) {
/*
* iSCSI RFC section 12.2 states that
* MaxConnections is Irrelevant for a
* discovery session.
*/
if (isp->sess_type ==
ISCSI_SESS_TYPE_DISCOVERY) {
/* EMPTY */
} else if (strcmp(value, "1")) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - can't accept "
"MaxConnections %s",
icp->conn_oid, value);
return (ISCSI_STATUS_NEGO_FAIL);
}
text = value_end;
} else if (iscsi_find_key_value("ErrorRecoveryLevel",
text, end, &value, &value_end)) {
if (strcmp(value, "0")) {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - can't accept "
"ErrorRecoveryLevel %s",
icp->conn_oid, value);
return (ISCSI_STATUS_NEGO_FAIL);
}
text = value_end;
} else {
cmn_err(CE_WARN, "iscsi connection(%u) "
"login failed - ignoring login "
"parameter %s", icp->conn_oid, value);
text = value_end;
}
break;
default:
return (ISCSI_STATUS_INTERNAL_ERROR);
}
}
/*
* iSCSI RFC section 12.14 states that
* FirstBurstLength is Irrelevant if
* InitialR2T=Yes and ImmediateData=No.
* This is a final check to make sure
* the array didn't make a protocol
* violation.
*/
if ((fbl_irrelevant == B_TRUE) &&
((icp->conn_params.initial_r2t != B_TRUE) ||
(icp->conn_params.immediate_data != B_FALSE))) {
cmn_err(CE_WARN, "iscsi connection(%u) login failed - "
"FirstBurstLength=Irrelevant and (InitialR2T!=Yes or "
"ImmediateData!=No) - protocol error", icp->conn_oid);
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
if (icp->conn_current_stage == ISCSI_SECURITY_NEGOTIATION_STAGE) {
switch (iscsiAuthClientRecvEnd(auth_client, iscsi_null_callback,
(void *)isp, NULL)) {
case iscsiAuthStatusContinue:
/*
* continue sending PDUs
*/
break;
case iscsiAuthStatusPass:
break;
case iscsiAuthStatusInProgress:
/*
* this should only occur if we were authenticating the
* target, which we don't do yet, so treat this as an
* error.
*/
case iscsiAuthStatusNoError:
/*
* treat this as an error, since we should get a
* different code
*/
case iscsiAuthStatusError:
case iscsiAuthStatusFail:
default:
debug_status = 0;
if (iscsiAuthClientGetDebugStatus(auth_client,
&debug_status) != iscsiAuthStatusNoError) {
cmn_err(CE_WARN, "iscsi connection(%u) login "
"failed - authentication failed with "
"target (%s)", icp->conn_oid,
iscsiAuthClientDebugStatusToText(
debug_status));
} else {
cmn_err(CE_WARN, "iscsi connection(%u) login "
"failed - authentication failed with "
"target", icp->conn_oid);
}
return (ISCSI_STATUS_AUTHENTICATION_FAILED);
}
}
/*
* record some of the PDU fields for later use
*/
isp->sess_tsid = ntohs(ilrhp->tsid);
isp->sess_expcmdsn = ntohl(ilrhp->expcmdsn);
isp->sess_maxcmdsn = ntohl(ilrhp->maxcmdsn);
if (ilrhp->status_class == ISCSI_STATUS_CLASS_SUCCESS) {
icp->conn_expstatsn = ntohl(ilrhp->statsn) + 1;
}
if (transit) {
/*
* advance to the next stage
*/
icp->conn_partial_response = 0;
icp->conn_current_stage =
ilrhp->flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK;
} else {
/*
* we got a partial response, don't advance, more
* negotiation to do
*/
icp->conn_partial_response = 1;
}
/*
* this PDU is ok, though the login process
* may not be done yet
*/
return (ISCSI_STATUS_SUCCESS);
}
/*
* iscsi_add_text - caller is assumed to be well-behaved and passing NUL
* terminated strings
*/
int
iscsi_add_text(idm_pdu_t *text_pdu, int max_data_length,
char *param, char *value)
{
int param_len = 0;
int value_len = 0;
int length = 0;
int pdu_length = 0;
char *text = NULL;
char *end = NULL;
ASSERT(text_pdu != NULL);
ASSERT(param != NULL);
ASSERT(value != NULL);
param_len = strlen(param);
value_len = strlen(value);
/* param, separator, value, and trailing NULL */
length = param_len + 1 + value_len + 1;
pdu_length = text_pdu->isp_datalen;
text = (char *)text_pdu->isp_data + pdu_length;
end = (char *)text_pdu->isp_data + max_data_length;
pdu_length += length;
if (text + length >= end) {
return (0);
}
/* param */
(void) strncpy(text, param, param_len);
text += param_len;
/* separator */
*text++ = ISCSI_TEXT_SEPARATOR;
/* value */
(void) strncpy(text, value, value_len);
text += value_len;
/* NULL */
*text++ = '\0';
/* update the length in the PDU header */
text_pdu->isp_datalen = pdu_length;
hton24(text_pdu->isp_hdr->dlength, pdu_length);
return (1);
}
/*
* iscsi_get_next_text - get the next line of text from the given data
* buffer. This function searches from the address given for the
* curr_text parameter. If curr_text_parameter is NULL return first
* line in buffer. The return value is the address of the next line
* based upon where curr_text is located.
*
*/
char *
iscsi_get_next_text(char *data, int max_data_length, char *curr_text)
{
char *curr_data;
ASSERT(data != NULL);
/* check if any data exists, if not return */
if (max_data_length == 0) {
return (NULL);
}
/* handle first call to this function */
if (curr_text == NULL) {
return (data);
}
/* move to next text string */
curr_data = curr_text;
while ((curr_data < (data + max_data_length)) && *curr_data) {
curr_data++;
}
curr_data++; /* go past the NULL to the next entry */
/* check whether data end reached */
if (curr_data >= (data + max_data_length)) {
return (NULL);
}
return (curr_data);
}
/*
* iscsi_find_key_value -
*
*/
static int
iscsi_find_key_value(char *param, char *ihp, char *pdu_end,
char **value_start, char **value_end)
{
char *str = param;
char *text = ihp;
char *value = NULL;
if (value_start)
*value_start = NULL;
if (value_end)
*value_end = NULL;
/*
* make sure they contain the same bytes
*/
while (*str) {
if (text >= pdu_end) {
return (0);
}
if (*text == '\0') {
return (0);
}
if (*str != *text) {
return (0);
}
str++;
text++;
}
if ((text >= pdu_end) ||
(*text == '\0') ||
(*text != ISCSI_TEXT_SEPARATOR)) {
return (0);
}
/*
* find the value
*/
value = text + 1;
/*
* find the end of the value
*/
while ((text < pdu_end) && (*text))
text++;
if (value_start)
*value_start = value;
if (value_end)
*value_end = text;
return (1);
}
/*
* iscsi_update_address - This function is used on a login redirection.
* During the login redirection we are asked to switch to an IP address
* port different than the one we were logging into.
*/
static iscsi_status_t
iscsi_update_address(iscsi_conn_t *icp, char *in)
{
char *addr_str, *port_str, *tpgt_str;
int type;
struct hostent *hptr;
unsigned long tmp;
int error_num;
int port;
ASSERT(icp != NULL);
ASSERT(in != NULL);
/* parse login redirection response */
if (parse_addr_port_tpgt(in, &addr_str, &type,
&port_str, &tpgt_str) == B_FALSE) {
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
/* convert addr_str */
hptr = kgetipnodebyname(addr_str, type, AI_ALL, &error_num);
if (!hptr) {
return (ISCSI_STATUS_PROTOCOL_ERROR);
}
/* convert port_str */
if (port_str != NULL) {
(void) ddi_strtoul(port_str, NULL, 0, &tmp);
port = (int)tmp;
} else {
port = ISCSI_LISTEN_PORT;
}
iscsid_addr_to_sockaddr(hptr->h_length, *hptr->h_addr_list,
port, &icp->conn_curr_addr.sin);
kfreehostent(hptr);
return (ISCSI_STATUS_SUCCESS);
}
void
iscsi_login_update_state(iscsi_conn_t *icp, iscsi_login_state_t next_state)
{
mutex_enter(&icp->conn_login_mutex);
(void) iscsi_login_update_state_locked(icp, next_state);
mutex_exit(&icp->conn_login_mutex);
}
void
iscsi_login_update_state_locked(iscsi_conn_t *icp,
iscsi_login_state_t next_state)
{
ASSERT(mutex_owned(&icp->conn_login_mutex));
next_state = (next_state > LOGIN_MAX) ? LOGIN_MAX : next_state;
idm_sm_audit_state_change(&icp->conn_state_audit,
SAS_ISCSI_LOGIN, icp->conn_login_state, next_state);
ISCSI_LOGIN_LOG(CE_NOTE, "iscsi_login_update_state conn %p %d -> %d",
(void *)icp, icp->conn_login_state, next_state);
icp->conn_login_state = next_state;
cv_broadcast(&icp->conn_login_cv);
}
/*
* iscsi_null_callback - This callback may be used under certain
* conditions when authenticating a target, but I'm not sure what
* we need to do here.
*/
/* ARGSUSED */
static void
iscsi_null_callback(void *user_handle, void *message_handle, int auth_status)
{
}
/*
* iscsi_login_failure_str -
*
*/
static char *
iscsi_login_failure_str(uchar_t status_class, uchar_t status_detail)
{
switch (status_class) {
case 0x00:
switch (status_detail) {
case 0x00:
return ("Login is proceeding okay.");
default:
break;
}
case 0x01:
switch (status_detail) {
case 0x01:
return ("Requested ITN has moved temporarily to "
"the address provided.");
case 0x02:
return ("Requested ITN has moved permanently to "
"the address provided.");
default:
break;
}
case 0x02:
switch (status_detail) {
case 0x00:
return ("Miscellaneous iSCSI initiator errors.");
case 0x01:
return ("Initiator could not be successfully "
"authenticated.");
case 0x02:
return ("Initiator is not allowed access to the "
"given target.");
case 0x03:
return ("Requested ITN does not exist at this "
"address.");
case 0x04:
return ("Requested ITN has been removed and no "
"forwarding address is provided.");
case 0x05:
return ("Requested iSCSI version range is not "
"supported by the target.");
case 0x06:
return ("No more connections can be accepted on "
"this Session ID (SSID).");
case 0x07:
return ("Missing parameters (e.g., iSCSI initiator "
"and/or target name).");
case 0x08:
return ("Target does not support session spanning "
"to this connection (address).");
case 0x09:
return ("Target does not support this type of "
"session or not from this initiator.");
case 0x0A:
return ("Attempt to add a connection to a "
"nonexistent session.");
case 0x0B:
return ("Invalid request type during login.");
default:
break;
}
case 0x03:
switch (status_detail) {
case 0x00:
return ("Target hardware or software error.");
case 0x01:
return ("iSCSI service or target is not currently "
"operational.");
case 0x02:
return ("Target has insufficient session, connection "
"or other resources.");
default:
break;
}
}
return ("Unknown login response received.");
}
/*
* iscsi_login_connect -
*/
static iscsi_status_t
iscsi_login_connect(iscsi_conn_t *icp)
{
iscsi_hba_t *ihp;
iscsi_sess_t *isp;
struct sockaddr *addr;
idm_conn_req_t cr;
idm_status_t rval;
ASSERT(icp != NULL);
isp = icp->conn_sess;
ASSERT(isp != NULL);
ihp = isp->sess_hba;
ASSERT(ihp != NULL);
addr = &icp->conn_curr_addr.sin;
/* Make sure that scope_id is zero if it is an IPv6 address */
if (addr->sa_family == AF_INET6) {
((struct sockaddr_in6 *)addr)->sin6_scope_id = 0;
}
/* delay the connect process if required */
lbolt = ddi_get_lbolt();
if (lbolt < icp->conn_login_min) {
delay(icp->conn_login_min - lbolt);
}
/* Create IDM connection context */
cr.cr_domain = addr->sa_family;
cr.cr_type = SOCK_STREAM;
cr.cr_protocol = 0;
cr.cr_bound = icp->conn_bound;
cr.cr_li = icp->conn_sess->sess_hba->hba_li;
cr.icr_conn_ops.icb_rx_misc = &iscsi_rx_misc_pdu;
cr.icr_conn_ops.icb_rx_error = &iscsi_rx_error_pdu;
cr.icr_conn_ops.icb_rx_scsi_rsp = &iscsi_rx_scsi_rsp;
cr.icr_conn_ops.icb_client_notify = &iscsi_client_notify;
cr.icr_conn_ops.icb_build_hdr = &iscsi_build_hdr;
cr.icr_conn_ops.icb_task_aborted = &iscsi_task_aborted;
bcopy(addr, &cr.cr_ini_dst_addr,
sizeof (cr.cr_ini_dst_addr));
bcopy(&icp->conn_bound_addr, &cr.cr_bound_addr,
sizeof (cr.cr_bound_addr));
/*
* Allocate IDM connection context
*/
rval = idm_ini_conn_create(&cr, &icp->conn_ic);
if (rval != IDM_STATUS_SUCCESS) {
return (ISCSI_STATUS_LOGIN_FAILED);
}
icp->conn_ic->ic_handle = icp;
/*
* About to initiate connect, reset login state.
*/
iscsi_login_update_state(icp, LOGIN_START);
/*
* Make sure the connection doesn't go away until we are done with it.
* This hold will prevent us from receiving a CN_CONNECT_DESTROY
* notification on this connection until we are ready.
*/
idm_conn_hold(icp->conn_ic);
/*
* Attempt connection. Upon return we will either be ready to
* login or disconnected. If idm_ini_conn_connect fails we
* will eventually receive a CN_CONNECT_DESTROY at which point
* we will destroy the connection allocated above (so there
* is no need to explicitly free it here).
*/
rval = idm_ini_conn_connect(icp->conn_ic);
if (rval != IDM_STATUS_SUCCESS) {
cmn_err(CE_NOTE, "iscsi connection(%u) unable to "
"connect to target %s", icp->conn_oid,
icp->conn_sess->sess_name);
idm_conn_rele(icp->conn_ic);
}
return (rval == IDM_STATUS_SUCCESS ?
ISCSI_STATUS_SUCCESS : ISCSI_STATUS_INTERNAL_ERROR);
}
/*
* iscsi_login_disconnect
*/
static void
iscsi_login_disconnect(iscsi_conn_t *icp)
{
/* Tell IDM to disconnect is if we are not already disconnect */
idm_ini_conn_disconnect_sync(icp->conn_ic);
/*
* The function above may return before the CN_CONNECT_LOST
* notification. Wait for it.
*/
mutex_enter(&icp->conn_state_mutex);
while (icp->conn_state_idm_connected)
cv_wait(&icp->conn_state_change,
&icp->conn_state_mutex);
mutex_exit(&icp->conn_state_mutex);
}
/*
* iscsi_notice_key_values - Create an nvlist containing the values
* that have been negotiated for this connection and pass them down to
* IDM so it can pick up any values that are important.
*/
static void
iscsi_notice_key_values(iscsi_conn_t *icp)
{
nvlist_t *neg_nvl;
int rc;
rc = nvlist_alloc(&neg_nvl, NV_UNIQUE_NAME, KM_SLEEP);
ASSERT(rc == 0);
/* Only crc32c is supported so the digest logic is simple */
if (icp->conn_params.header_digest) {
rc = nvlist_add_string(neg_nvl, "HeaderDigest", "crc32c");
} else {
rc = nvlist_add_string(neg_nvl, "HeaderDigest", "none");
}
ASSERT(rc == 0);
if (icp->conn_params.data_digest) {
rc = nvlist_add_string(neg_nvl, "DataDigest", "crc32c");
} else {
rc = nvlist_add_string(neg_nvl, "DataDigest", "none");
}
ASSERT(rc == 0);
rc = nvlist_add_uint64(neg_nvl, "MaxRecvDataSegmentLength",
(uint64_t)icp->conn_params.max_recv_data_seg_len);
ASSERT(rc == 0);
rc = nvlist_add_uint64(neg_nvl, "MaxBurstLength",
(uint64_t)icp->conn_params.max_burst_length);
ASSERT(rc == 0);
rc = nvlist_add_uint64(neg_nvl, "MaxOutstandingR2T",
(uint64_t)icp->conn_params.max_outstanding_r2t);
ASSERT(rc == 0);
rc = nvlist_add_uint64(neg_nvl, "ErrorRecoveryLevel",
(uint64_t)icp->conn_params.error_recovery_level);
ASSERT(rc == 0);
rc = nvlist_add_uint64(neg_nvl, "DefaultTime2Wait",
(uint64_t)icp->conn_params.default_time_to_wait);
ASSERT(rc == 0);
rc = nvlist_add_uint64(neg_nvl, "DefaultTime2Retain",
(uint64_t)icp->conn_params.default_time_to_retain);
ASSERT(rc == 0);
/* Pass the list to IDM to examine, then free it */
idm_notice_key_values(icp->conn_ic, neg_nvl);
nvlist_free(neg_nvl);
}