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
* 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 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 <sys/types.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <utility.h>
#include <netinet/in.h>
#include <inttypes.h>
#include <sys/socket.h>
#include <sys/iscsi_protocol.h>
#include <arpa/inet.h>
#include "iscsi_ffp.h"
#include "iscsi_cmd.h"
#include "t10_spc.h"
#include "utility.h"
#include "iscsi_provider_impl.h"
static Boolean_t handle_text_msg(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t handle_logout_msg(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t handle_scsi_cmd(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t handle_noop_cmd(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t handle_scsi_data(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t handle_task_mgt(iscsi_conn_t *, iscsi_hdr_t *, char *, int);
static Boolean_t dataout_delayed(iscsi_cmd_t *cmd, msg_type_t type);
void dataout_callback(t10_cmd_t *t, char *data, size_t *xfer);
Boolean_t
iscsi_full_feature(iscsi_conn_t *c)
{
iscsi_hdr_t h;
Boolean_t rval = False;
char debug[128];
char *ahs = NULL;
int cc;
int ahslen;
if ((cc = recv(c->c_fd, &h, sizeof (h), MSG_WAITALL)) != sizeof (h)) {
if (errno == ECONNRESET) {
(void) snprintf(debug, sizeof (debug),
"CON%x full_feature -- initiator reset socket",
c->c_num);
} else {
(void) snprintf(debug, sizeof (debug),
"CON%x full_feature(got-%d, expect-%d), errno=%d",
c->c_num, cc, sizeof (h), errno);
}
queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
conn_state(c, T8);
return (False);
}
/*
* Look to see if there's an Additional Header Segment available.
* If so, read it in.
*/
if ((ahslen = (h.hlength * sizeof (uint32_t))) != 0) {
if ((ahs = malloc(ahslen)) == NULL)
return (False);
if (recv(c->c_fd, ahs, ahslen, MSG_WAITALL) != ahslen) {
(void) snprintf(debug, sizeof (debug),
"CON%x Failed to read in AHS", c->c_num);
queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
return (False);
}
}
if ((c->c_state == S5_LOGGED_IN) && (c->c_header_digest == True)) {
uint32_t crc_actual;
uint32_t crc_calculated;
(void) recv(c->c_fd, (char *)&crc_actual,
sizeof (crc_actual), MSG_WAITALL);
crc_calculated = iscsi_crc32c((void *)&h, sizeof (h));
if (ahslen)
crc_calculated = iscsi_crc32c_continued(ahs,
ahslen, crc_calculated);
if (crc_actual != crc_calculated) {
(void) snprintf(debug, sizeof (debug),
"CON%x CRC error: actual 0x%x v. calc 0x%x",
c->c_num, crc_actual, crc_calculated);
queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
return (False);
}
}
if (c->c_sess->s_type == SessionDiscovery) {
switch (h.opcode & ISCSI_OPCODE_MASK) {
default:
/*
* Need to handle the error case here.
*/
(void) snprintf(debug, sizeof (debug),
"CON%x Wrong opcode for Discovery, %d",
c->c_num, h.opcode & ISCSI_OPCODE_MASK);
queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
rval = False;
break;
case ISCSI_OP_LOGOUT_CMD:
/*
* This will transition from S5_LOGGED_IN
* to S6_IN_LOGOUT to S1_FREE;
*/
rval = handle_logout_msg(c, &h, ahs, ahslen);
break;
case ISCSI_OP_TEXT_CMD:
rval = handle_text_msg(c, &h, ahs, ahslen);
break;
}
} else {
iscsi_cmd_remove(c, htonl(h.expstatsn));
switch (h.opcode & ISCSI_OPCODE_MASK) {
case ISCSI_OP_NOOP_OUT:
rval = handle_noop_cmd(c, &h, ahs, ahslen);
break;
case ISCSI_OP_SCSI_CMD:
rval = handle_scsi_cmd(c, &h, ahs, ahslen);
break;
case ISCSI_OP_SCSI_TASK_MGT_MSG:
rval = handle_task_mgt(c, &h, ahs, ahslen);
break;
case ISCSI_OP_LOGIN_CMD:
/*
* This is an illegal state transition. Should
* we drop the connection?
*/
break;
case ISCSI_OP_TEXT_CMD:
rval = handle_text_msg(c, &h, ahs, ahslen);
break;
case ISCSI_OP_SCSI_DATA:
rval = handle_scsi_data(c, &h, ahs, ahslen);
break;
case ISCSI_OP_LOGOUT_CMD:
/*
* This will transition from S5_LOGGED_IN
* to S6_IN_LOGOUT.
*/
rval = handle_logout_msg(c, &h, ahs, ahslen);
break;
case ISCSI_OP_SNACK_CMD:
default:
(void) snprintf(debug, sizeof (debug),
"CON%x Opcode: %d not handled",
c->c_num, h.opcode & ISCSI_OPCODE_MASK);
queue_str(c->c_mgmtq, Q_CONN_ERRS, msg_log, debug);
conn_state(c, T8);
rval = True;
break;
}
}
if (ahs != NULL)
free(ahs);
return (rval);
}
/*ARGSUSED*/
static Boolean_t
handle_task_mgt(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
iscsi_scsi_task_mgt_hdr_t *hp = (iscsi_scsi_task_mgt_hdr_t *)p;
iscsi_scsi_task_mgt_rsp_hdr_t *rsp;
iscsi_cmd_t *cmd;
uint32_t lun;
Boolean_t lu_reset = False;
if (spc_decode_lu_addr(&hp->lun[0], 8, &lun) == False)
return (False);
if (ISCSI_TASK_COMMAND_ENABLED()) {
uiscsiproto_t info;
info.uip_target_addr = &c->c_target_sockaddr;
info.uip_initiator_addr = &c->c_initiator_sockaddr;
info.uip_target = c->c_sess->s_t_name;
info.uip_initiator = c->c_sess->s_i_name;
info.uip_lun = lun;
info.uip_itt = hp->itt;
info.uip_ttt = hp->itt;
info.uip_cmdsn = ntohl(hp->cmdsn);
info.uip_statsn = ntohl(hp->expstatsn);
info.uip_datasn = ntohl(hp->expdatasn);
info.uip_datalen = ntoh24(hp->dlength);
info.uip_flags = 0;
ISCSI_TASK_COMMAND(&info);
}
if ((rsp = calloc(1, sizeof (*rsp))) == NULL)
return (False);
rsp->opcode = ISCSI_OP_SCSI_TASK_MGT_RSP;
rsp->flags = ISCSI_FLAG_FINAL;
rsp->itt = hp->itt;
(void) pthread_mutex_lock(&c->c_sess->s_mutex);
if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn)
c->c_sess->s_seencmdsn = ntohl(hp->cmdsn);
(void) pthread_mutex_unlock(&c->c_sess->s_mutex);
queue_prt(c->c_mgmtq, Q_CONN_NONIO,
"CON%x PDU(Task Mgt): %s, cmdsn 0x%x\n",
c->c_num,
task_to_str(hp->function & ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK),
ntohl(hp->cmdsn));
switch (hp->function & ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK) {
case ISCSI_TM_FUNC_ABORT_TASK:
queue_prt(c->c_mgmtq, Q_CONN_NONIO,
"CON%x Abort ITT 0x%x\n", c->c_num, hp->rtt);
if ((cmd = iscsi_cmd_find(c, hp->rtt, FindITT)) == NULL) {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x Invalid AbortTask rtt 0x%x\n",
c->c_num, hp->rtt);
rsp->response = SCSI_TCP_TM_RESP_NO_TASK;
} else {
iscsi_cmd_cancel(c, cmd);
rsp->response = SCSI_TCP_TM_RESP_COMPLETE;
}
break;
case ISCSI_TM_FUNC_ABORT_TASK_SET:
/* ---- This is actually "Function not support" ---- */
rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS;
break;
case ISCSI_TM_FUNC_CLEAR_ACA:
/* ---- This is actually "Function not support" ---- */
rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS;
break;
case ISCSI_TM_FUNC_CLEAR_TASK_SET:
/* ---- This is actually "Function not support" ---- */
rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS;
break;
case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET:
lu_reset = True;
/*FALLTHRU*/
case ISCSI_TM_FUNC_TARGET_WARM_RESET:
(void) pthread_mutex_lock(&c->c_mutex);
for (cmd = c->c_cmd_head; cmd; cmd = cmd->c_next) {
if (((hp->function &
ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK) ==
ISCSI_TM_FUNC_TARGET_WARM_RESET) ||
(lun == cmd->c_lun)) {
/*
* Can't call cmd_cancel() here because it
* will attempt to grab the lock which we
* already have held.
*/
if (cmd->c_state == CmdAlloc) {
cmd->c_state = CmdCanceled;
t10_cmd_shoot_event(cmd->c_t10_cmd,
T10_Cmd_T6);
}
}
}
(void) pthread_mutex_unlock(&c->c_mutex);
if (lu_reset == True)
queue_message_set(c->c_sessq, 0, msg_reset_lu,
(void *)(uintptr_t)lun);
else
queue_message_set(c->c_sessq, 0, msg_reset_targ, 0);
rsp->response = SCSI_TCP_TM_RESP_COMPLETE;
break;
case ISCSI_TM_FUNC_TARGET_COLD_RESET:
/*
* According to the specification a cold reset should
* close *all* connections on the target, not just those
* for this current session.
*/
queue_message_set(c->c_sessq, 0, msg_reset_targ, (void *)1);
conn_state(c, T8);
break;
case ISCSI_TM_FUNC_TASK_REASSIGN:
default:
/* ---- This is actually "Function not support" ---- */
rsp->response = SCSI_TCP_TM_RESP_IN_PRGRESS;
break;
}
(void) pthread_mutex_lock(&c->c_state_mutex);
if (c->c_state == S5_LOGGED_IN)
queue_message_set(c->c_dataq,
hp->opcode & ISCSI_OP_IMMEDIATE ? Q_HIGH : 0,
msg_send_pkt, rsp);
(void) pthread_mutex_unlock(&c->c_state_mutex);
return (True);
}
/*ARGSUSED*/
static Boolean_t
handle_noop_cmd(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
iscsi_nop_out_hdr_t *hp = (iscsi_nop_out_hdr_t *)p;
iscsi_nop_in_hdr_t *in;
if (ISCSI_NOP_RECEIVE_ENABLED()) {
uiscsiproto_t info;
info.uip_target_addr = &c->c_target_sockaddr;
info.uip_initiator_addr = &c->c_initiator_sockaddr;
info.uip_target = c->c_sess->s_t_name;
info.uip_initiator = c->c_sess->s_i_name;
info.uip_lun = 0;
info.uip_itt = hp->itt;
info.uip_ttt = hp->ttt;
info.uip_cmdsn = ntohl(hp->cmdsn);
info.uip_statsn = ntohl(hp->expstatsn);
info.uip_datasn = 0;
info.uip_datalen = ntoh24(hp->dlength);
info.uip_flags = hp->flags;
ISCSI_NOP_RECEIVE(&info);
}
/*
* Just an answer to our ping
*/
if (hp->ttt != ISCSI_RSVD_TASK_TAG)
return (True);
if ((in = calloc(1, sizeof (*in))) == NULL) {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x NopIn -- failed to malloc space for header",
c->c_num);
return (False);
}
in->opcode = ISCSI_OP_NOOP_IN;
in->flags = ISCSI_FLAG_FINAL;
/*
* Need to handle possible data associated with NOP-Out
*/
bcopy(hp->lun, in->lun, 8);
in->itt = hp->itt;
in->ttt = ISCSI_RSVD_TASK_TAG;
(void) pthread_mutex_lock(&c->c_sess->s_mutex);
if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn)
c->c_sess->s_seencmdsn = ntohl(hp->cmdsn);
(void) pthread_mutex_unlock(&c->c_sess->s_mutex);
(void) pthread_mutex_lock(&c->c_state_mutex);
if (c->c_state == S5_LOGGED_IN)
queue_message_set(c->c_dataq,
hp->opcode & ISCSI_OP_IMMEDIATE ? Q_HIGH : 0,
msg_send_pkt, in);
(void) pthread_mutex_unlock(&c->c_state_mutex);
return (True);
}
/*ARGSUSED*/
static Boolean_t
handle_scsi_data(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
iscsi_data_hdr_t *hp = (iscsi_data_hdr_t *)p;
int dlen = ntoh24(hp->dlength);
iscsi_cmd_t *cmd;
if (ISCSI_DATA_RECEIVE_ENABLED()) {
uiscsiproto_t info;
info.uip_target_addr = &c->c_target_sockaddr;
info.uip_initiator_addr = &c->c_initiator_sockaddr;
info.uip_target = c->c_sess->s_t_name;
info.uip_initiator = c->c_sess->s_i_name;
info.uip_lun = 0;
info.uip_itt = hp->itt;
info.uip_ttt = hp->itt;
info.uip_cmdsn = 0;
info.uip_statsn = ntohl(hp->expstatsn);
info.uip_datasn = ntohl(hp->datasn);
info.uip_datalen = dlen;
info.uip_flags = hp->flags;
ISCSI_DATA_RECEIVE(&info);
}
if ((cmd = iscsi_cmd_find(c, hp->ttt, FindTTT)) == NULL) {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x failed to find ttt 0x%x\n", c->c_num, hp->ttt);
/*
* Need to handle error case.
*/
return (False);
}
cmd->c_opcode = hp->opcode & ISCSI_OPCODE_MASK;
/*
* 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.
*/
assert(cmd->c_allegiance == c);
assert(cmd->c_itt == hp->itt);
cmd->c_offset_out = ntohl(hp->offset);
cmd->c_data_len = dlen;
(void) pthread_mutex_lock(&c->c_mutex);
(void) pthread_mutex_lock(&c->c_state_mutex);
if (c->c_state == S5_LOGGED_IN) {
if (cmd->c_state != CmdCanceled) {
t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T4);
}
}
(void) pthread_mutex_unlock(&c->c_state_mutex);
(void) pthread_mutex_unlock(&c->c_mutex);
#ifdef FULL_DEBUG
queue_prt(c->c_mgmtq, Q_CONN_IO,
"CON%x PDU(DataOut) TTT 0x%x, offset=0x%x, len=0x%x\n",
c->c_num, cmd->c_ttt, cmd->c_t10_cmd->c_offset, dlen);
#endif
return (dataout_delayed(cmd, msg_cmd_data_out));
}
static Boolean_t
handle_scsi_cmd(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
iscsi_scsi_cmd_hdr_t *hp = (iscsi_scsi_cmd_hdr_t *)p;
int dlen = ntoh24(hp->dlength);
iscsi_cmd_t *cmd;
(void) pthread_mutex_lock(&c->c_sess->s_mutex);
if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn)
c->c_sess->s_seencmdsn = ntohl(hp->cmdsn);
(void) pthread_mutex_unlock(&c->c_sess->s_mutex);
if ((cmd = iscsi_cmd_alloc(c, hp->opcode & ISCSI_OPCODE_MASK)) == NULL)
return (False);
bcopy(hp->scb, cmd->c_scb_default, sizeof (cmd->c_scb_default));
cmd->c_scb = cmd->c_scb_default;
cmd->c_scb_len = sizeof (cmd->c_scb_default);
cmd->c_data_len = dlen;
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.
*/
uint16_t hslen;
uint16_t next_seg;
uint8_t hstyp;
do {
/*
* Find this header segment's length and type
*/
bcopy(ahs, &hslen, sizeof (hslen));
hslen = ntohs(hslen);
hstyp = ahs[2];
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
*/
cmd->c_scb_extended = malloc(hslen + 15);
cmd->c_scb_len = hslen + 15;
if (cmd->c_scb_extended == NULL)
return (False);
/*
* First 16 bytes of extended SCB are
* found in the normal location.
*/
bcopy(hp->scb, cmd->c_scb_extended, 16);
bcopy(&ahs[4], &cmd->c_scb_extended[16],
hslen - 16);
cmd->c_scb = cmd->c_scb_extended;
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.
*/
next_seg = ((hslen + sizeof (hslen) +
sizeof (hstyp) + 3) & ~3);
ahs += next_seg;
ahslen -= next_seg;
} while (ahslen);
}
/*
* XXX Need to handle error case better.
*/
if (spc_decode_lu_addr(&hp->lun[0], sizeof (hp->lun), &cmd->c_lun) ==
False) {
return (False);
}
if (ISCSI_SCSI_COMMAND_ENABLED()) {
uiscsiproto_t info;
uiscsicmd_t uc;
info.uip_target_addr = &c->c_target_sockaddr;
info.uip_initiator_addr = &c->c_initiator_sockaddr;
info.uip_target = c->c_sess->s_t_name;
info.uip_initiator = c->c_sess->s_i_name;
info.uip_lun = cmd->c_lun;
info.uip_itt = hp->itt;
info.uip_ttt = ISCSI_RSVD_TASK_TAG;
info.uip_cmdsn = ntohl(hp->cmdsn);
info.uip_statsn = ntohl(hp->expstatsn);
info.uip_datasn = 0;
info.uip_datalen = dlen;
info.uip_flags = hp->flags;
uc.uic_len = cmd->c_scb_len;
uc.uic_cdb = cmd->c_scb;
ISCSI_SCSI_COMMAND(&info, &uc);
}
cmd->c_itt = hp->itt;
cmd->c_cmdsn = ntohl(hp->cmdsn);
cmd->c_dlen_expected = ntohl(hp->data_length);
cmd->c_writeop = hp->flags & ISCSI_FLAG_CMD_WRITE ?
True : False;
#ifdef FULL_DEBUG
queue_prt(c->c_mgmtq, Q_CONN_IO,
"CON%x PDU(SCSI Cmd, TA=%d) CmdSN 0x%x ITT 0x%x TTT 0x%x "
"LUN[%02x] id=%p\n", c->c_num,
hp->flags & ISCSI_FLAG_CMD_ATTR_MASK, cmd->c_cmdsn, cmd->c_itt,
cmd->c_ttt, cmd->c_lun, cmd);
#endif
if (dlen && (hp->flags & ISCSI_FLAG_CMD_WRITE)) {
/*
* 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."
*/
return (dataout_delayed(cmd, msg_cmd_send));
} 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
handle_text_msg(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
iscsi_text_rsp_hdr_t rsp;
iscsi_text_hdr_t *hp = (iscsi_text_hdr_t *)p;
char *text = NULL;
int text_length = 0;
Boolean_t release_at_end = False;
int dlen = ntoh24(hp->dlength);
if (ISCSI_TEXT_COMMAND_ENABLED()) {
uiscsiproto_t info;
char nil = '\0';
info.uip_initiator = c->c_sess->s_i_name;
info.uip_target_addr = &c->c_target_sockaddr;
info.uip_initiator_addr = &c->c_initiator_sockaddr;
info.uip_target = c->c_sess->s_t_name;
info.uip_initiator = c->c_sess->s_i_name;
info.uip_target = &nil;
info.uip_lun = 0;
info.uip_itt = hp->itt;
info.uip_ttt = hp->ttt;
info.uip_cmdsn = ntohl(hp->cmdsn);
info.uip_statsn = ntohl(hp->expstatsn);
info.uip_datasn = 0;
info.uip_datalen = dlen;
info.uip_flags = hp->flags;
ISCSI_TEXT_COMMAND(&info);
}
bzero(&rsp, sizeof (rsp));
rsp.opcode = ISCSI_OP_TEXT_RSP;
rsp.itt = hp->itt;
queue_prt(c->c_mgmtq, Q_CONN_NONIO, "CON%x PDU(Text Message)\n",
c->c_num);
/*
* Need to determine if this incoming text PDU is an initial message
* or a continuation.
*/
if (hp->ttt == ISCSI_RSVD_TASK_TAG) {
/* ---- Initial text PDU, so parse the incoming data ---- */
if (parse_text(c, dlen, &text, &text_length, NULL) == False) {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"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.
*/
free(text);
}
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.
*/
rsp.flags = ISCSI_FLAG_TEXT_CONTINUE;
rsp.ttt = 1;
c->c_text_len = text_length;
text_length = c->c_max_recv_data;
c->c_text_sent = text_length;
} else {
rsp.flags = ISCSI_FLAG_FINAL;
rsp.ttt = ISCSI_RSVD_TASK_TAG;
release_at_end = True;
}
} else {
/* ---- Continuation of previous text request ---- */
text_length = c->c_text_len - c->c_text_sent;
text = c->c_text_area + c->c_text_sent;
if (text_length > c->c_max_recv_data) {
rsp.flags = ISCSI_FLAG_TEXT_CONTINUE;
rsp.ttt = 1;
text_length = c->c_max_recv_data;
c->c_text_sent += text_length;
} else {
rsp.flags = ISCSI_FLAG_FINAL;
rsp.ttt = ISCSI_RSVD_TASK_TAG;
release_at_end = True;
}
}
queue_prt(c->c_mgmtq, Q_CONN_NONIO,
"CON%x Text PDU: flags=0x%02x, ttt=0x%08x, len=%d\n",
c->c_num, rsp.flags, rsp.ttt, text_length);
hton24(rsp.dlength, text_length);
(void) pthread_mutex_lock(&c->c_mutex);
rsp.statsn = htonl(c->c_statsn++);
(void) pthread_mutex_lock(&c->c_sess->s_mutex);
if (ntohl(hp->cmdsn) > c->c_sess->s_seencmdsn)
c->c_sess->s_seencmdsn = ntohl(hp->cmdsn);
rsp.maxcmdsn = htonl(iscsi_cmd_window(c) + c->c_sess->s_seencmdsn);
rsp.expcmdsn = htonl(c->c_sess->s_seencmdsn + 1);
(void) pthread_mutex_unlock(&c->c_sess->s_mutex);
(void) pthread_mutex_unlock(&c->c_mutex);
if (ISCSI_TEXT_RESPONSE_ENABLED()) {
uiscsiproto_t info;
char nil = '\0';
info.uip_target_addr = &c->c_target_sockaddr;
info.uip_initiator_addr = &c->c_initiator_sockaddr;
info.uip_target = c->c_sess->s_t_name;
info.uip_initiator = c->c_sess->s_i_name;
info.uip_initiator = c->c_sess->s_i_name;
info.uip_target = &nil;
info.uip_lun = 0;
info.uip_itt = rsp.itt;
info.uip_ttt = rsp.ttt;
info.uip_cmdsn = ntohl(rsp.expcmdsn);
info.uip_statsn = ntohl(rsp.statsn);
info.uip_datasn = 0;
info.uip_datalen = text_length;
info.uip_flags = rsp.flags;
ISCSI_TEXT_RESPONSE(&info);
}
send_iscsi_pkt(c, (iscsi_hdr_t *)&rsp, text);
if (release_at_end == True) {
free(c->c_text_area);
c->c_text_area = NULL;
}
return (True);
}
/*ARGSUSED*/
static Boolean_t
handle_logout_msg(iscsi_conn_t *c, iscsi_hdr_t *p, char *ahs, int ahslen)
{
iscsi_logout_rsp_hdr_t *rsp;
iscsi_logout_hdr_t *hp = (iscsi_logout_hdr_t *)p;
char debug[80];
if (ISCSI_LOGOUT_COMMAND_ENABLED()) {
uiscsiproto_t info;
char nil = '\0';
info.uip_target_addr = &c->c_target_sockaddr;
info.uip_initiator_addr = &c->c_initiator_sockaddr;
info.uip_target = c->c_sess->s_t_name;
info.uip_initiator = c->c_sess->s_i_name;
info.uip_initiator = c->c_sess->s_i_name;
info.uip_target = &nil;
info.uip_lun = 0;
info.uip_itt = hp->itt;
info.uip_ttt = ISCSI_RSVD_TASK_TAG;
info.uip_cmdsn = ntohl(hp->cmdsn);
info.uip_statsn = ntohl(hp->expstatsn);
info.uip_datasn = 0;
info.uip_datalen = ntoh24(hp->dlength);
info.uip_flags = hp->flags;
ISCSI_LOGOUT_COMMAND(&info);
}
if ((rsp = calloc(1, sizeof (*rsp))) == NULL)
return (False);
(void) snprintf(debug, sizeof (debug),
"CON%x PDU(Logout Request)", c->c_num);
queue_str(c->c_mgmtq, Q_CONN_NONIO, msg_log, debug);
(void) pthread_mutex_lock(&c->c_mutex);
(void) pthread_mutex_lock(&c->c_sess->s_mutex);
if (hp->cmdsn > c->c_sess->s_seencmdsn)
c->c_sess->s_seencmdsn = htonl(hp->cmdsn);
rsp->expcmdsn = htonl(c->c_sess->s_seencmdsn + 1);
rsp->maxcmdsn = htonl(iscsi_cmd_window(c) +
c->c_sess->s_seencmdsn);
(void) pthread_mutex_unlock(&c->c_sess->s_mutex);
(void) pthread_mutex_unlock(&c->c_mutex);
rsp->opcode = ISCSI_OP_LOGOUT_RSP;
rsp->flags = ISCSI_FLAG_FINAL;
rsp->itt = hp->itt;
(void) pthread_mutex_lock(&c->c_mutex);
rsp->statsn = htonl(c->c_statsn++);
(void) pthread_mutex_unlock(&c->c_mutex);
c->c_last_pkg = (iscsi_hdr_t *)rsp;
/*
* 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
dataout_delayed(iscsi_cmd_t *cmd, msg_type_t type)
{
iscsi_conn_t *c = cmd->c_allegiance;
int dlen = cmd->c_data_len;
int cc;
uint32_t crc_calc;
uint32_t crc_actual;
char pad_buf[ISCSI_PAD_WORD_LEN - 1];
char pad_len;
char debug[80];
cmd->c_dataout_cb = dataout_callback;
if (cmd->c_data == NULL) {
if ((cmd->c_data = (char *)malloc(dlen)) == NULL) {
(void) pthread_mutex_lock(&c->c_mutex);
t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
iscsi_cmd_free(c, cmd);
(void) pthread_mutex_unlock(&c->c_mutex);
return (False);
}
cmd->c_data_alloc = True;
}
if ((cc = recv(c->c_fd, cmd->c_data, dlen, MSG_WAITALL)) != dlen) {
if (errno == ECONNRESET) {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x dataout_delayed -- "
"initiator reset socket\n", c->c_num);
} else {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x recv(got-%d, expect-%d), errno=%d\n",
c->c_num, cc, dlen, errno);
}
(void) pthread_mutex_lock(&c->c_mutex);
t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
iscsi_cmd_free(c, cmd);
(void) pthread_mutex_unlock(&c->c_mutex);
conn_state(c, T8);
return (True);
}
pad_len = ((ISCSI_PAD_WORD_LEN -
(dlen & (ISCSI_PAD_WORD_LEN - 1))) & (ISCSI_PAD_WORD_LEN - 1));
if (pad_len) {
if (recv(c->c_fd, pad_buf, pad_len, MSG_WAITALL) != pad_len) {
if (errno == ECONNRESET) {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x dataout_delayed -- "
"initiator reset socket\n", c->c_num);
} else {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x Pad Word read errno=%d\n", c->c_num,
errno);
}
(void) pthread_mutex_lock(&c->c_mutex);
t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
iscsi_cmd_free(c, cmd);
(void) pthread_mutex_unlock(&c->c_mutex);
conn_state(c, T8);
return (True);
}
}
if (c->c_data_digest == True) {
if (recv(c->c_fd, (char *)&crc_actual, sizeof (crc_actual),
MSG_WAITALL) != sizeof (crc_actual)) {
if (errno == ECONNRESET) {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x dataout_delayed -- "
"initiator reset socket\n", c->c_num);
} else {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x CRC32 read errno=%d\n", c->c_num,
errno);
}
(void) pthread_mutex_lock(&c->c_mutex);
t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
iscsi_cmd_free(c, cmd);
(void) pthread_mutex_unlock(&c->c_mutex);
conn_state(c, T8);
return (True);
}
crc_calc = iscsi_crc32c((void *)cmd->c_data, dlen);
if (crc_calc != crc_actual) {
(void) snprintf(debug, sizeof (debug),
"CON%x CRC Error: actual %x vs. calc 0x%x",
c->c_num, crc_actual, crc_calc);
/*
* 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);
t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
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.
*/
cmd->c_offset_out += dlen;
(void) pthread_mutex_lock(&c->c_mutex);
(void) pthread_mutex_lock(&c->c_state_mutex);
if (c->c_state == S5_LOGGED_IN) {
if ((cmd->c_state == CmdCanceled) &&
(type == msg_cmd_data_out))
t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
else
queue_message_set(c->c_sessq, 0, type, (void *)cmd);
} else if (cmd->c_state == CmdCanceled) {
t10_cmd_shoot_event(cmd->c_t10_cmd, T10_Cmd_T5);
}
(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
dataout_callback(t10_cmd_t *t, char *data, size_t *xfer)
{
iscsi_cmd_t *cmd = (iscsi_cmd_t *)T10_TRANS_ID(t);
iscsi_conn_t *c = cmd->c_allegiance;
int dlen = cmd->c_data_len;
int cc;
char pad_buf[ISCSI_PAD_WORD_LEN - 1];
char pad_len = 0;
pad_len = ((ISCSI_PAD_WORD_LEN -
(dlen & (ISCSI_PAD_WORD_LEN - 1))) &
(ISCSI_PAD_WORD_LEN - 1));
if (T10_DATA(t) != NULL) {
assert(T10_DATA(t) == cmd->c_data);
assert(cmd->c_data_alloc == True);
free(T10_DATA(t));
T10_DATA(t) = NULL;
cmd->c_data = NULL;
cmd->c_data_alloc = False;
return;
}
if ((cc = recv(c->c_fd, data, dlen, MSG_WAITALL)) != dlen) {
if (errno == ECONNRESET) {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x data_callback -- initiator reset socket\n",
c->c_num);
} else {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x recv(got-%d, expect-%d) errno=%d",
c->c_num, cc, dlen, errno);
}
conn_state(c, T8);
goto finish;
}
if (pad_len) {
if (recv(c->c_fd, pad_buf, pad_len, MSG_WAITALL) != pad_len) {
if (errno == ECONNRESET) {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x data_callback -- "
"initiator reset socket\n", c->c_num);
} else {
queue_prt(c->c_mgmtq, Q_CONN_ERRS,
"CON%x data_callback -- "
"pad read errno=%d\n", c->c_num, errno);
}
conn_state(c, T8);
goto finish;
}
}
finish:
*xfer = cc;
/* ---- Send msg that receive side of the connection can go ---- */
(void) sema_post(&c->c_datain);
}