Files
libiscsi/lib/pdu.c
Peter Lieven ac46262afa pdu: add workaround for DELL Equallogic FW bugs 71409 & 73732
Dell Equallogic storages may redirect connections from one member of a multi member storage
group to another. When the member that has the volume lead changes the storage issues
an async disconnect request message. The correct behaviour would be to either fail all I/O requests
with a check condition (waiting for logout) or fullfil requests until the time specified
in the disconnect request message has elapsed and then drop the connection. In firwamre 7.x
however the storage sometimes cancels requests with SENSE KEY:ILLEGAL_REQUEST(5)
ASCQ:LOGICAL_UNIT_NOT_SUPPORTED(0x2500). This leads to I/O errors propagated to the application.
This patch was reported in May 2014 to Dell and the initial bug 71409 has been fixed in 7.0.7.
However, the first fix did not entirely solve the problem and bug 73732 has been raised.

This patch adds an environment variable LIBISCSI_DROP_CONN_ON_ASYNC_EVENT1 which if set leads
to an immediate drop of the connection when such an async event is received. This is ugly, but
works around the I/O errors.

Signed-off-by: Peter Lieven <pl@kamp.de>
2014-10-28 09:20:05 +01:00

675 lines
17 KiB
C

/*
Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#if defined(WIN32)
#include <winsock2.h>
#else
#include <strings.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "iscsi.h"
#include "iscsi-private.h"
#include "scsi-lowlevel.h"
#include "slist.h"
/* This adds 32-bit serial comparision as defined in RFC1982.
* It returns 0 for equality, 1 if s1 is greater than s2 and
* -1 if s1 is less than s2. According to RFC1982 section 3.2
* there are rare cases where the result of the comparision is
* undefined e.g. when s1 = 0 and s2=2^31. This cases should
* not happen in iSCSI protocol.
*/
int
iscsi_serial32_compare(uint32_t s1, uint32_t s2) {
if (s1 == s2) return 0;
if (s1 < s2 && s2-s1 < (uint32_t)1<<31) return -1;
if (s1 > s2 && s1-s2 < (uint32_t)1<<31) return 1;
if (s1 > s2 && s1-s2 > (uint32_t)1<<31) return -1;
if (s1 < s2 && s2-s1 > (uint32_t)1<<31) return 1;
/* undefined result */
return -1;
}
uint32_t
iscsi_itt_post_increment(struct iscsi_context *iscsi) {
uint32_t old_itt = iscsi->itt;
iscsi->itt++;
/* 0xffffffff is a reserved value */
if (iscsi->itt == 0xffffffff) {
iscsi->itt = 0;
}
return old_itt;
}
void iscsi_adjust_statsn(struct iscsi_context *iscsi, struct iscsi_in_pdu *in) {
uint32_t statsn = scsi_get_uint32(&in->hdr[24]);
uint32_t itt = scsi_get_uint32(&in->hdr[16]);
if (itt == 0xffffffff) {
/* target will not increase statsn if itt == 0xffffffff */
statsn--;
}
if (iscsi_serial32_compare(statsn, iscsi->statsn) > 0) {
iscsi->statsn = statsn;
}
}
void iscsi_adjust_maxexpcmdsn(struct iscsi_context *iscsi, struct iscsi_in_pdu *in) {
uint32_t maxcmdsn = scsi_get_uint32(&in->hdr[32]);
if (iscsi_serial32_compare(maxcmdsn, iscsi->maxcmdsn) > 0) {
iscsi->maxcmdsn = maxcmdsn;
}
uint32_t expcmdsn = scsi_get_uint32(&in->hdr[28]);
if (iscsi_serial32_compare(expcmdsn, iscsi->expcmdsn) > 0) {
iscsi->expcmdsn = expcmdsn;
}
}
void iscsi_dump_pdu_header(struct iscsi_context *iscsi, unsigned char *data) {
char dump[ISCSI_RAW_HEADER_SIZE*3+1]={0};
int i;
for (i=0;i<ISCSI_RAW_HEADER_SIZE;i++) {
snprintf(&dump[i * 3], 4, " %02x", data[i]);
}
ISCSI_LOG(iscsi, 0, "PDU header:%s",dump);
}
struct iscsi_pdu *
iscsi_allocate_pdu_with_itt_flags(struct iscsi_context *iscsi, enum iscsi_opcode opcode,
enum iscsi_opcode response_opcode, uint32_t itt, uint32_t flags)
{
struct iscsi_pdu *pdu;
pdu = iscsi_szmalloc(iscsi, sizeof(struct iscsi_pdu));
if (pdu == NULL) {
iscsi_set_error(iscsi, "failed to allocate pdu");
return NULL;
}
pdu->outdata.size = ISCSI_HEADER_SIZE;
pdu->outdata.data = iscsi_szmalloc(iscsi, pdu->outdata.size);
if (pdu->outdata.data == NULL) {
iscsi_set_error(iscsi, "failed to allocate pdu header");
iscsi_free(iscsi, pdu);
return NULL;
}
/* opcode */
pdu->outdata.data[0] = opcode;
pdu->response_opcode = response_opcode;
/* isid */
if (opcode == ISCSI_PDU_LOGIN_REQUEST) {
memcpy(&pdu->outdata.data[8], &iscsi->isid[0], 6);
}
/* itt */
iscsi_pdu_set_itt(pdu, itt);
pdu->itt = itt;
/* flags */
pdu->flags = flags;
return pdu;
}
struct iscsi_pdu *
iscsi_allocate_pdu(struct iscsi_context *iscsi, enum iscsi_opcode opcode,
enum iscsi_opcode response_opcode)
{
return iscsi_allocate_pdu_with_itt_flags(iscsi, opcode, response_opcode, iscsi_itt_post_increment(iscsi), 0);
}
void
iscsi_free_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu)
{
if (pdu == NULL) {
iscsi_set_error(iscsi, "trying to free NULL pdu");
return;
}
if (pdu->outdata.size <= iscsi->smalloc_size) {
iscsi_sfree(iscsi, pdu->outdata.data);
} else {
iscsi_free(iscsi, pdu->outdata.data);
}
pdu->outdata.data = NULL;
if (pdu->indata.size <= iscsi->smalloc_size) {
iscsi_sfree(iscsi, pdu->indata.data);
} else {
iscsi_free(iscsi, pdu->indata.data);
}
pdu->indata.data = NULL;
if (iscsi->outqueue_current == pdu) {
iscsi->outqueue_current = NULL;
}
iscsi_sfree(iscsi, pdu);
}
int
iscsi_add_data(struct iscsi_context *iscsi, struct iscsi_data *data,
unsigned char *dptr, int dsize, int pdualignment)
{
size_t len, aligned;
if (dsize == 0) {
iscsi_set_error(iscsi, "Trying to append zero size data to "
"iscsi_data");
return -1;
}
len = data->size + dsize;
aligned = len;
if (pdualignment) {
aligned = (aligned+3)&0xfffffffc;
}
if (data->size == 0) {
if (aligned <= iscsi->smalloc_size) {
data->data = iscsi_szmalloc(iscsi, aligned);
} else {
data->data = iscsi_malloc(iscsi, aligned);
}
} else {
if (aligned > iscsi->smalloc_size) {
data->data = iscsi_realloc(iscsi, data->data, aligned);
}
}
if (data->data == NULL) {
iscsi_set_error(iscsi, "failed to allocate buffer for %d "
"bytes", (int) len);
return -1;
}
memcpy(data->data + data->size, dptr, dsize);
data->size += dsize;
if (len != aligned) {
/* zero out any padding at the end */
memset(data->data + len, 0, aligned - len);
}
return 0;
}
int
iscsi_pdu_add_data(struct iscsi_context *iscsi, struct iscsi_pdu *pdu,
unsigned char *dptr, int dsize)
{
if (pdu == NULL) {
iscsi_set_error(iscsi, "trying to add data to NULL pdu");
return -1;
}
if (dsize == 0) {
iscsi_set_error(iscsi, "Trying to append zero size data to "
"pdu");
return -1;
}
if (iscsi_add_data(iscsi, &pdu->outdata, dptr, dsize, 1) != 0) {
iscsi_set_error(iscsi, "failed to add data to pdu buffer");
return -1;
}
/* update data segment length */
scsi_set_uint32(&pdu->outdata.data[4], pdu->outdata.size
- ISCSI_HEADER_SIZE);
return 0;
}
int
iscsi_get_pdu_data_size(const unsigned char *hdr)
{
int size;
size = scsi_get_uint32(&hdr[4]) & 0x00ffffff;
return size;
}
int
iscsi_get_pdu_padding_size(const unsigned char *hdr)
{
int data_size, padded_size;
data_size = scsi_get_uint32(&hdr[4]) & 0x00ffffff;
padded_size = (data_size+3) & 0xfffffffc;
return padded_size - data_size;
}
enum iscsi_reject_reason {
ISCSI_REJECT_RESERVED = 0x01,
ISCSI_REJECT_DATA_DIGEST_ERROR = 0x02,
ISCSI_REJECT_SNACK_REJECT = 0x03,
ISCSI_REJECT_PROTOCOL_ERROR = 0x04,
ISCSI_REJECT_COMMAND_NOT_SUPPORTED = 0x05,
ISCSI_REJECT_IMMEDIATE_COMMAND_REJECT = 0x06,
ISCSI_REJECT_TASK_IN_PROCESS = 0x07,
ISCSI_REJECT_INVALID_DATA_ACK = 0x08,
ISCSI_REJECT_INVALID_PDU_FIELD = 0x09,
ISCSI_REJECT_LONG_OPERATION_REJECT = 0x0a,
ISCSI_REJECT_NEGOTIATION_RESET = 0x0b,
ISCSI_REJECT_WAITING_FOR_LOGOUT = 0x0c
};
static const char *iscsi_reject_reason_str(enum iscsi_reject_reason reason)
{
switch (reason) {
case ISCSI_REJECT_RESERVED:
return "Reserved";
case ISCSI_REJECT_DATA_DIGEST_ERROR:
return "Data Digest Error";
case ISCSI_REJECT_SNACK_REJECT:
return "SNACK Reject";
case ISCSI_REJECT_PROTOCOL_ERROR:
return "Protocol Error";
case ISCSI_REJECT_COMMAND_NOT_SUPPORTED:
return "Command Not Supported";
case ISCSI_REJECT_IMMEDIATE_COMMAND_REJECT:
return "Immediate Command Reject";
case ISCSI_REJECT_TASK_IN_PROCESS:
return "Task In Process";
case ISCSI_REJECT_INVALID_DATA_ACK:
return "Invalid Data ACK";
case ISCSI_REJECT_INVALID_PDU_FIELD:
return "Invalid PDU Field";
case ISCSI_REJECT_LONG_OPERATION_REJECT:
return "Long Operation Reject";
case ISCSI_REJECT_NEGOTIATION_RESET:
return "Negotiation Reset";
case ISCSI_REJECT_WAITING_FOR_LOGOUT:
return "Waiting For Logout";
}
return "Unknown";
}
int iscsi_process_target_nop_in(struct iscsi_context *iscsi,
struct iscsi_in_pdu *in)
{
uint32_t ttt;
ttt = scsi_get_uint32(&in->hdr[20]);
iscsi_adjust_statsn(iscsi, in);
/* if the server does not want a response */
if (ttt == 0xffffffff) {
return 0;
}
iscsi_send_target_nop_out(iscsi, ttt);
return 0;
}
static void iscsi_reconnect_after_logout(struct iscsi_context *iscsi, int status,
void *command_data _U_, void *opaque _U_)
{
if (status) {
ISCSI_LOG(iscsi, 1, "logout failed: %s", iscsi_get_error(iscsi));
} else {
ISCSI_LOG(iscsi, 2, "logout was successful");
}
iscsi->pending_reconnect = 1;
}
int iscsi_process_reject(struct iscsi_context *iscsi,
struct iscsi_in_pdu *in)
{
int size = in->data_pos;
uint32_t itt;
struct iscsi_pdu *pdu;
uint8_t reason = in->hdr[2];
if (size < ISCSI_RAW_HEADER_SIZE) {
iscsi_set_error(iscsi, "size of REJECT payload is too small."
"Need >= %d bytes but got %d.",
ISCSI_RAW_HEADER_SIZE, (int)size);
return -1;
}
if (reason == ISCSI_REJECT_WAITING_FOR_LOGOUT) {
ISCSI_LOG(iscsi, 1, "target rejects request with reason: %s", iscsi_reject_reason_str(reason));
iscsi_logout_async_internal(iscsi, iscsi_reconnect_after_logout, NULL, ISCSI_PDU_DROP_ON_RECONNECT|ISCSI_PDU_URGENT_DELIVERY);
return 0;
}
iscsi_set_error(iscsi, "Request was rejected with reason: 0x%02x (%s)", reason, iscsi_reject_reason_str(reason));
itt = scsi_get_uint32(&in->data[16]);
if (iscsi->log_level > 1) {
iscsi_dump_pdu_header(iscsi, in->data);
}
for (pdu = iscsi->waitpdu; pdu; pdu = pdu->next) {
if (pdu->itt == itt) {
break;
}
}
if (pdu == NULL) {
iscsi_set_error(iscsi, "Can not match REJECT with"
"any outstanding pdu with itt:0x%08x",
itt);
return -1;
}
if (pdu->callback) {
pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL,
pdu->private_data);
}
ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
return 0;
}
int
iscsi_process_pdu(struct iscsi_context *iscsi, struct iscsi_in_pdu *in)
{
uint32_t itt;
enum iscsi_opcode opcode;
struct iscsi_pdu *pdu;
uint8_t ahslen;
opcode = in->hdr[0] & 0x3f;
ahslen = in->hdr[4];
itt = scsi_get_uint32(&in->hdr[16]);
if (ahslen != 0) {
iscsi_set_error(iscsi, "cant handle expanded headers yet");
return -1;
}
if (opcode == ISCSI_PDU_ASYNC_MSG) {
uint8_t event = in->hdr[36];
uint16_t param1 = scsi_get_uint16(&in->hdr[38]);
uint16_t param2 = scsi_get_uint16(&in->hdr[40]);
uint16_t param3 = scsi_get_uint16(&in->hdr[42]);
switch (event) {
case 0x1:
ISCSI_LOG(iscsi, 2, "target requests logout within %u seconds", param3);
/* this is an ugly workaround for DELL Equallogic FW 7.x bugs:
* Bug_71409 - I/O errors during volume move (present before 7.0.7)
* Bug_73732 - I/O errors during volume move operation (still present in 7.0.9)
*/
if (getenv("LIBISCSI_DROP_CONN_ON_ASYNC_EVENT1") != NULL) {
ISCSI_LOG(iscsi, 2, "dropping connection to fix errors with broken DELL Equallogic firmware 7.x");
return -1;
}
iscsi_logout_async_internal(iscsi, iscsi_reconnect_after_logout, NULL, ISCSI_PDU_DROP_ON_RECONNECT|ISCSI_PDU_URGENT_DELIVERY);
return 0;
case 0x2:
ISCSI_LOG(iscsi, 2, "target will drop this connection. Time2Wait is %u seconds", param2);
iscsi->last_reconnect = time(NULL) + param2;
return 0;
case 0x3:
ISCSI_LOG(iscsi, 2, "target will drop all connections of this session. Time2Wait is %u seconds", param2);
iscsi->last_reconnect = time(NULL) + param2;
return 0;
case 0x4:
ISCSI_LOG(iscsi, 2, "target requests parameter renogitiation.");
iscsi_logout_async_internal(iscsi, iscsi_reconnect_after_logout, NULL, ISCSI_PDU_DROP_ON_RECONNECT);
return 0;
default:
ISCSI_LOG(iscsi, 1, "unhandled async event %u: param1 %u param2 %u param3 %u", event, param1, param2, param3);
return -1;
}
}
if (opcode == ISCSI_PDU_REJECT) {
return iscsi_process_reject(iscsi, in);
}
if (opcode == ISCSI_PDU_NOP_IN && itt == 0xffffffff) {
if (iscsi_process_target_nop_in(iscsi, in) != 0) {
return -1;
}
return 0;
}
for (pdu = iscsi->waitpdu; pdu; pdu = pdu->next) {
enum iscsi_opcode expected_response = pdu->response_opcode;
int is_finished = 1;
if (pdu->itt != itt) {
continue;
}
/* we have a special case with scsi-command opcodes,
* they are replied to by either a scsi-response
* or a data-in, or a combination of both.
*/
if (opcode == ISCSI_PDU_DATA_IN
&& expected_response == ISCSI_PDU_SCSI_RESPONSE) {
expected_response = ISCSI_PDU_DATA_IN;
}
/* Another special case is if we get a R2T.
* In this case we should find the original request and just send an additional
* DATAOUT segment for this task.
*/
if (opcode == ISCSI_PDU_R2T) {
expected_response = ISCSI_PDU_R2T;
}
if (opcode != expected_response) {
iscsi_set_error(iscsi, "Got wrong opcode back for "
"itt:%d got:%d expected %d",
itt, opcode, pdu->response_opcode);
return -1;
}
switch (opcode) {
case ISCSI_PDU_LOGIN_RESPONSE:
if (iscsi_process_login_reply(iscsi, pdu, in) != 0) {
ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
iscsi_set_error(iscsi, "iscsi login reply "
"failed");
return -1;
}
break;
case ISCSI_PDU_TEXT_RESPONSE:
if (iscsi_process_text_reply(iscsi, pdu, in) != 0) {
ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
iscsi_set_error(iscsi, "iscsi text reply "
"failed");
return -1;
}
break;
case ISCSI_PDU_LOGOUT_RESPONSE:
if (iscsi_process_logout_reply(iscsi, pdu, in) != 0) {
ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
iscsi_set_error(iscsi, "iscsi logout reply "
"failed");
return -1;
}
break;
case ISCSI_PDU_SCSI_RESPONSE:
if (iscsi_process_scsi_reply(iscsi, pdu, in) != 0) {
ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
iscsi_set_error(iscsi, "iscsi response reply "
"failed");
return -1;
}
break;
case ISCSI_PDU_DATA_IN:
if (iscsi_process_scsi_data_in(iscsi, pdu, in,
&is_finished) != 0) {
ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
iscsi_set_error(iscsi, "iscsi data in "
"failed");
return -1;
}
break;
case ISCSI_PDU_NOP_IN:
if (iscsi_process_nop_out_reply(iscsi, pdu, in) != 0) {
ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
iscsi_set_error(iscsi, "iscsi nop-in failed");
return -1;
}
break;
case ISCSI_PDU_SCSI_TASK_MANAGEMENT_RESPONSE:
if (iscsi_process_task_mgmt_reply(iscsi, pdu,
in) != 0) {
ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
iscsi_set_error(iscsi, "iscsi task-mgmt failed");
return -1;
}
break;
case ISCSI_PDU_R2T:
if (iscsi_process_r2t(iscsi, pdu, in) != 0) {
ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
iscsi_set_error(iscsi, "iscsi r2t "
"failed");
return -1;
}
is_finished = 0;
break;
default:
iscsi_set_error(iscsi, "Don't know how to handle "
"opcode 0x%02x", opcode);
return -1;
}
if (is_finished) {
ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
}
return 0;
}
return 0;
}
void
iscsi_pdu_set_itt(struct iscsi_pdu *pdu, uint32_t itt)
{
scsi_set_uint32(&pdu->outdata.data[16], itt);
}
void
iscsi_pdu_set_ritt(struct iscsi_pdu *pdu, uint32_t ritt)
{
scsi_set_uint32(&pdu->outdata.data[20], ritt);
}
void
iscsi_pdu_set_pduflags(struct iscsi_pdu *pdu, unsigned char flags)
{
pdu->outdata.data[1] = flags;
}
void
iscsi_pdu_set_immediate(struct iscsi_pdu *pdu)
{
pdu->outdata.data[0] |= ISCSI_PDU_IMMEDIATE;
}
void
iscsi_pdu_set_ttt(struct iscsi_pdu *pdu, uint32_t ttt)
{
scsi_set_uint32(&pdu->outdata.data[20], ttt);
}
void
iscsi_pdu_set_cmdsn(struct iscsi_pdu *pdu, uint32_t cmdsn)
{
scsi_set_uint32(&pdu->outdata.data[24], cmdsn);
}
void
iscsi_pdu_set_rcmdsn(struct iscsi_pdu *pdu, uint32_t rcmdsn)
{
scsi_set_uint32(&pdu->outdata.data[32], rcmdsn);
}
void
iscsi_pdu_set_datasn(struct iscsi_pdu *pdu, uint32_t datasn)
{
scsi_set_uint32(&pdu->outdata.data[36], datasn);
}
void
iscsi_pdu_set_expstatsn(struct iscsi_pdu *pdu, uint32_t expstatsnsn)
{
scsi_set_uint32(&pdu->outdata.data[28], expstatsnsn);
}
void
iscsi_pdu_set_bufferoffset(struct iscsi_pdu *pdu, uint32_t bufferoffset)
{
scsi_set_uint32(&pdu->outdata.data[40], bufferoffset);
}
void
iscsi_pdu_set_cdb(struct iscsi_pdu *pdu, struct scsi_task *task)
{
memset(&pdu->outdata.data[32], 0, 16);
memcpy(&pdu->outdata.data[32], task->cdb, task->cdb_size);
}
void
iscsi_pdu_set_lun(struct iscsi_pdu *pdu, uint32_t lun)
{
pdu->outdata.data[8] = lun >> 8;
pdu->outdata.data[9] = lun & 0xff;
}
void
iscsi_pdu_set_expxferlen(struct iscsi_pdu *pdu, uint32_t expxferlen)
{
scsi_set_uint32(&pdu->outdata.data[20], expxferlen);
}