iscsi_ffp.c revision 9e8164f5f5658c6e38a931780d499f5cb622908a
/*
* 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
* 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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* This file contains methods to handle the iSCSI Full Feature Phase aspects
* of the protocol.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <unistd.h>
#include <poll.h>
#include <strings.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <utility.h>
#include <inttypes.h>
#include <sys/iscsi_protocol.h>
#include "iscsi_ffp.h"
#include "iscsi_cmd.h"
#include "t10_spc.h"
#include "utility.h"
#include "iscsi_provider_impl.h"
{
iscsi_hdr_t h;
char debug[128];
int cc;
int ahslen;
if (errno == ECONNRESET) {
"CON%x full_feature -- initiator reset socket",
c->c_num);
} else {
"CON%x full_feature(got-%d, expect-%d), errno=%d",
}
conn_state(c, T8);
return (False);
}
/*
* Look to see if there's an Additional Header Segment available.
* If so, read it in.
*/
return (False);
"CON%x Failed to read in AHS", c->c_num);
return (False);
}
}
sizeof (crc_actual), MSG_WAITALL);
crc_calculated = iscsi_crc32c((void *)&h, sizeof (h));
if (ahslen)
if (crc_actual != crc_calculated) {
"CON%x CRC error: actual 0x%x v. calc 0x%x",
return (False);
}
}
switch (h.opcode & ISCSI_OPCODE_MASK) {
default:
/*
* Need to handle the error case here.
*/
"CON%x Wrong opcode for Discovery, %d",
break;
case ISCSI_OP_LOGOUT_CMD:
/*
* This will transition from S5_LOGGED_IN
* to S6_IN_LOGOUT to S1_FREE;
*/
break;
case ISCSI_OP_TEXT_CMD:
break;
}
} else {
switch (h.opcode & ISCSI_OPCODE_MASK) {
case ISCSI_OP_NOOP_OUT:
break;
case ISCSI_OP_SCSI_CMD:
break;
break;
case ISCSI_OP_LOGIN_CMD:
/*
* This is an illegal state transition. Should
* we drop the connection?
*/
break;
case ISCSI_OP_TEXT_CMD:
break;
case ISCSI_OP_SCSI_DATA:
break;
case ISCSI_OP_LOGOUT_CMD:
/*
* This will transition from S5_LOGGED_IN
* to S6_IN_LOGOUT.
*/
break;
case ISCSI_OP_SNACK_CMD:
default:
"CON%x Opcode: %d not handled",
conn_state(c, T8);
break;
}
}
return (rval);
}
/*ARGSUSED*/
static Boolean_t
{
return (False);
if (ISCSI_TASK_COMMAND_ENABLED()) {
}
return (False);
"CON%x PDU(Task Mgt): %s, cmdsn 0x%x\n",
c->c_num,
case ISCSI_TM_FUNC_ABORT_TASK:
"CON%x Invalid AbortTask rtt 0x%x\n",
} else {
iscsi_cmd_cancel(c, cmd);
}
break;
/* ---- This is actually "Function not support" ---- */
break;
case ISCSI_TM_FUNC_CLEAR_ACA:
/* ---- This is actually "Function not support" ---- */
break;
/* ---- This is actually "Function not support" ---- */
break;
/*FALLTHRU*/
(void) pthread_mutex_lock(&c->c_mutex);
/*
* Can't call cmd_cancel() here because it
* will attempt to grab the lock which we
* already have held.
*/
}
}
}
(void) pthread_mutex_unlock(&c->c_mutex);
else
break;
/*
* According to the specification a cold reset should
* close *all* connections on the target, not just those
* for this current session.
*/
conn_state(c, T8);
break;
default:
/* ---- This is actually "Function not support" ---- */
break;
}
(void) pthread_mutex_lock(&c->c_state_mutex);
if (c->c_state == S5_LOGGED_IN)
msg_send_pkt, rsp);
(void) pthread_mutex_unlock(&c->c_state_mutex);
return (True);
}
/*ARGSUSED*/
static Boolean_t
{
if (ISCSI_NOP_RECEIVE_ENABLED()) {
info.uip_datasn = 0;
}
/*
* Just an answer to our ping
*/
return (True);
"CON%x NopIn -- failed to malloc space for header",
c->c_num);
return (False);
}
/*
* Need to handle possible data associated with NOP-Out
*/
(void) pthread_mutex_lock(&c->c_state_mutex);
if (c->c_state == S5_LOGGED_IN)
msg_send_pkt, in);
(void) pthread_mutex_unlock(&c->c_state_mutex);
return (True);
}
/*ARGSUSED*/
static Boolean_t
{
if (ISCSI_DATA_RECEIVE_ENABLED()) {
}
/*
* Need to handle error case.
*/
return (False);
}
/*
* assert(cmd->c_lun == hp->lun[1]);
* Previously this check was done, but is caused a problem with
* the RedHat initiator. There was a discussion on the IPS alias
* around this very topic. Even though section 10.7.4 states:
* "If the Target Transfer Tag is provided, then the LUN field
* MUST hold a valid value and be consistent with whatever was
* specified with the command; otherwise, the LUN field is
* reserved."
* Everyone agreed though that for a DataOut command the LUN field
* wasn't required to be valid because the TTT gives the Target
* enough information to complete the command.
*/
(void) pthread_mutex_lock(&c->c_mutex);
(void) pthread_mutex_lock(&c->c_state_mutex);
if (c->c_state == S5_LOGGED_IN) {
}
}
(void) pthread_mutex_unlock(&c->c_state_mutex);
(void) pthread_mutex_unlock(&c->c_mutex);
#ifdef FULL_DEBUG
"CON%x PDU(DataOut) TTT 0x%x, offset=0x%x, len=0x%x\n",
#endif
}
static Boolean_t
{
return (False);
if (ahslen) {
/*
* Additional Header Section ----
*
* For Object Storage Devices the SCB is quite large. On
* the order of 140 bytes which means the data must be
* found in the AHS.
*/
do {
/*
* Find this header segment's length and type
*/
switch (hstyp) {
/* ---- Extended CDB ---- */
case 1:
/*
* The hslen accounts for the reserved
* data byte in the segment. So the first
* sixteen bytes are in hp->scb with the
* remainder here. By only adding 15 bytes
* we allocate the correct amount of space
*/
return (False);
/*
* First 16 bytes of extended SCB are
* found in the normal location.
*/
hslen - 16);
break;
/* ---- Expected bidirectional read data len ---- */
case 2:
/*
* We shouldn't need this since we're
* not prealloc'ing resources. If that should
* change or the need for error checking
* here's the spot to locate the data.
*/
break;
}
/*
* hslen contains the effective length in bytes of
* segment, excluding type and length (not including
* padding). Each segment is padded to a 4 byte
* boundary.
*/
} while (ahslen);
}
/*
* XXX Need to handle error case better.
*/
False) {
return (False);
}
if (ISCSI_SCSI_COMMAND_ENABLED()) {
info.uip_datasn = 0;
}
#ifdef FULL_DEBUG
"CON%x PDU(SCSI Cmd, TA=%d) CmdSN 0x%x ITT 0x%x TTT 0x%x "
"LUN[%02x] id=%p\n", c->c_num,
#endif
/*
* NOTE: This should only occur if ImmediateData==Yes.
* We can handle this even if the initiator violates
* the specification so no need to worry. Use the rule
* of "Be strict in what is sent, but lenient in what
* is accepted."
*/
} else {
(void) pthread_mutex_lock(&c->c_state_mutex);
if (c->c_state == S5_LOGGED_IN)
queue_message_set(c->c_sessq, 0,
msg_cmd_send, (void *)cmd);
(void) pthread_mutex_unlock(&c->c_state_mutex);
return (True);
}
}
/*
* []----
* | handle_text_msg -- process incoming test parameters
* |
* | NOTE: Need to handle continuation packets sent by the initiator.
* []----
*/
/*ARGSUSED*/
static Boolean_t
{
int text_length = 0;
if (ISCSI_TEXT_COMMAND_ENABLED()) {
char nil = '\0';
info.uip_datasn = 0;
}
c->c_num);
/*
* Need to determine if this incoming text PDU is an initial message
* or a continuation.
*/
/* ---- Initial text PDU, so parse the incoming data ---- */
"Failed to parse Text\n");
if (text) {
/*
* It's possible that we started to create
* a response, but yet an error occurred.
* Release the partial text response if that
* occurred.
*/
}
return (False);
}
/*
* 10.11.4 --
* When the target receives a Text Request with the Target
* Transfer Tag set to the reserved value of 0xffffffff, it
* resets its internal information (resets state) associated
* with the given Initiator Task Tag (restarts the negotiation).
*/
if (c->c_text_area != NULL)
free(c->c_text_area);
c->c_text_area = text;
if (text_length > c->c_max_recv_data) {
/*
* Too much data to send at once, break it up into
* multiple transfers.
*/
c->c_text_len = text_length;
text_length = c->c_max_recv_data;
c->c_text_sent = text_length;
} else {
}
} else {
/* ---- Continuation of previous text request ---- */
if (text_length > c->c_max_recv_data) {
text_length = c->c_max_recv_data;
c->c_text_sent += text_length;
} else {
}
}
"CON%x Text PDU: flags=0x%02x, ttt=0x%08x, len=%d\n",
(void) pthread_mutex_lock(&c->c_mutex);
(void) pthread_mutex_unlock(&c->c_mutex);
if (ISCSI_TEXT_RESPONSE_ENABLED()) {
char nil = '\0';
info.uip_datasn = 0;
}
if (release_at_end == True) {
free(c->c_text_area);
c->c_text_area = NULL;
}
return (True);
}
/*ARGSUSED*/
static Boolean_t
{
char debug[80];
if (ISCSI_LOGOUT_COMMAND_ENABLED()) {
char nil = '\0';
info.uip_datasn = 0;
}
return (False);
"CON%x PDU(Logout Request)", c->c_num);
(void) pthread_mutex_lock(&c->c_mutex);
c->c_sess->s_seencmdsn);
(void) pthread_mutex_unlock(&c->c_mutex);
(void) pthread_mutex_lock(&c->c_mutex);
(void) pthread_mutex_unlock(&c->c_mutex);
/*
* Call the state transition last. This will send out
* an asynchronous message to shutdown the session and STE.
* Once that's complete a shutdown reply will be sent to
* the transmit connection thread. That will cause another
* transition to T13 which expects to send out this logout
* response.
*/
if (c->c_state == S7_LOGOUT_REQUESTED)
conn_state(c, T10);
else
conn_state(c, T9);
return (True);
}
/*
* dataout_delayed -- possibly copy data from initiator
*
* If DataDigests are enabled copy the data from the socket into a buffer
* and perform the CRC check now.
*
* If MaxConnections==1 don't copy the data now and wait until the STE is
* ready to copy the data directly from the socket to it's final location.
* This is extremely beneficial when using mmap'd data.
* NOTE:
* (1) For this to work we must not use the queues and instead
* call the STE functions directly. If the queues are used
* this routine must pause until STE processes the data to
* prevent this thread from attempting to read data from
* the socket as if it's the next PDU header.
* (2) Currently we don't call STE directly. To prevent a performance
* issue we'll have the code in place to support calling
* STE directly, but any time MaxConnections is greater than 0
* we'll copy the buffer. This will be removed at some future
* point.
*/
static Boolean_t
{
int cc;
char pad_len;
char debug[80];
(void) pthread_mutex_lock(&c->c_mutex);
iscsi_cmd_free(c, cmd);
(void) pthread_mutex_unlock(&c->c_mutex);
return (False);
}
}
if (errno == ECONNRESET) {
"CON%x dataout_delayed -- "
"initiator reset socket\n", c->c_num);
} else {
"CON%x recv(got-%d, expect-%d), errno=%d\n",
}
(void) pthread_mutex_lock(&c->c_mutex);
iscsi_cmd_free(c, cmd);
(void) pthread_mutex_unlock(&c->c_mutex);
conn_state(c, T8);
return (True);
}
pad_len = ((ISCSI_PAD_WORD_LEN -
if (pad_len) {
if (errno == ECONNRESET) {
"CON%x dataout_delayed -- "
"initiator reset socket\n", c->c_num);
} else {
"CON%x Pad Word read errno=%d\n", c->c_num,
errno);
}
(void) pthread_mutex_lock(&c->c_mutex);
iscsi_cmd_free(c, cmd);
(void) pthread_mutex_unlock(&c->c_mutex);
conn_state(c, T8);
return (True);
}
}
if (c->c_data_digest == True) {
MSG_WAITALL) != sizeof (crc_actual)) {
if (errno == ECONNRESET) {
"CON%x dataout_delayed -- "
"initiator reset socket\n", c->c_num);
} else {
"CON%x CRC32 read errno=%d\n", c->c_num,
errno);
}
(void) pthread_mutex_lock(&c->c_mutex);
iscsi_cmd_free(c, cmd);
(void) pthread_mutex_unlock(&c->c_mutex);
conn_state(c, T8);
return (True);
}
if (crc_calc != crc_actual) {
"CON%x CRC Error: actual %x vs. calc 0x%x",
/*
* NOTE: Need to think about this one some more.
* Just because we get a data error doesn't mean
* we should drop the connection. Look at the
* spec and determine what's the appropriate
* error recovery for this issue.
*/
(void) pthread_mutex_lock(&c->c_mutex);
iscsi_cmd_free(c, cmd);
(void) pthread_mutex_unlock(&c->c_mutex);
conn_state(c, T8);
return (True);
}
}
/*
* We'll update the offset with the amount of data that
* has been received. During a SCSI response PDU this value
* will be used to determine if there's an overrun condition.
*/
(void) pthread_mutex_lock(&c->c_mutex);
(void) pthread_mutex_lock(&c->c_state_mutex);
if (c->c_state == S5_LOGGED_IN) {
(type == msg_cmd_data_out))
else
}
(void) pthread_mutex_unlock(&c->c_state_mutex);
(void) pthread_mutex_unlock(&c->c_mutex);
/*
* The else case here is if we're calling STE directly and the data
* will be read from the socket when STE is ready for it.
*/
return (True);
}
/*
* []----
* | dataout_callback -- copy data from socket to emulation buffer
* []----
*/
void
{
int cc;
char pad_len = 0;
pad_len = ((ISCSI_PAD_WORD_LEN -
(ISCSI_PAD_WORD_LEN - 1));
return;
}
if (errno == ECONNRESET) {
"CON%x data_callback -- initiator reset socket\n",
c->c_num);
} else {
"CON%x recv(got-%d, expect-%d) errno=%d",
}
conn_state(c, T8);
goto finish;
}
if (pad_len) {
if (errno == ECONNRESET) {
"CON%x data_callback -- "
"initiator reset socket\n", c->c_num);
} else {
"CON%x data_callback -- "
}
conn_state(c, T8);
goto finish;
}
}
/* ---- Send msg that receive side of the connection can go ---- */
}