boot_net: Add iSCSI initiator support

Add support for both discovery and regular iSCSI sessions. Command and status
sequence numbers do differentiate between session and connection but only
one connection per session is currently supported.

Code is Big Endian for now, so compile it for ppc only.

Based on RFC 3720 ff. Tested against OpenSolaris 2009.06.

Resolves most of ticket #5319.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@38536 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Andreas Färber 2010-09-05 22:30:41 +00:00
parent eb5e456002
commit 5b086e27e8
4 changed files with 936 additions and 1 deletions

View File

@ -0,0 +1,108 @@
/*
* Copyright 2010, Andreas Färber <andreas.faerber@web.de>
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef BOOT_NET_ISCSITARGET_H
#define BOOT_NET_ISCSITARGET_H
#include <SupportDefs.h>
#include <util/kernel_cpp.h>
#include <boot/platform.h>
#include <boot/stdio.h>
#include <boot/vfs.h>
#include <boot/net/NetDefs.h>
#define ISCSI_PORT 3260
class TCPSocket;
class iSCSISession;
struct iscsi_basic_header_segment;
#define kTcpTimeout 10000000ULL
class iSCSIConnection {
public:
iSCSIConnection();
virtual ~iSCSIConnection();
status_t Init(iSCSISession* session);
status_t Open(ip_addr_t address, uint16 port);
status_t Login(const char* targetName = NULL, char** targetAlias = NULL);
status_t Logout(bool closeSession = false);
status_t GetText(const char* request, size_t requestLength, char** response, size_t* responseLength);
status_t SendCommand(const void* command, size_t commandSize,
bool r, bool w, uint32 expectedDataTransferLength,
void* response, uint32 responseOffset, size_t responseLength);
bool Active() const { return fConnected; }
private:
status_t _ReadResponse(iscsi_basic_header_segment* buffer, bigtime_t timeout = kTcpTimeout);
status_t _Read(void* buffer, size_t bufferSize, bigtime_t timeout = kTcpTimeout);
iSCSISession* fSession;
TCPSocket* fSocket;
bool fConnected;
uint16 fConnectionID;
uint32 fStatusSequenceNumber;
};
class iSCSISession {
public:
iSCSISession(const char* targetName = NULL);
virtual ~iSCSISession();
status_t Init(ip_addr_t address, uint16 port, char** targetAlias = NULL);
status_t Close();
uint32 CommandSequenceNumber() const { return fCommandSequenceNumber; }
uint32 NextCommandSequenceNumber() { return fCommandSequenceNumber++; }
iSCSIConnection* Connection() const { return fConnection; }
bool Active() { return fConnection != NULL && fConnection->Active(); }
private:
bool fDiscovery;
const char* fTargetName;
uint32 fCommandSequenceNumber;
iSCSIConnection* fConnection;
// XXX should allow for multiple
};
class iSCSITarget : public Node {
public:
iSCSITarget();
virtual ~iSCSITarget();
status_t Init(ip_addr_t address, uint16 port, const char* targetName);
virtual ssize_t ReadAt(void* cookie, off_t pos, void* buffer,
size_t bufferSize);
virtual ssize_t WriteAt(void* cookie, off_t pos, const void* buffer,
size_t bufferSize);
virtual status_t GetName(char* buffer, size_t bufferSize) const;
virtual off_t Size() const;
static bool DiscoverTargets(ip_addr_t address, uint16 port,
NodeList* devicesList);
static bool _AddDevice(ip_addr_t address, uint16 port,
const char* targetName, NodeList* devicesList);
private:
status_t _GetSize();
iSCSISession* fSession;
char* fTargetName;
char* fTargetAlias;
uint32 fLastLBA;
uint32 fBlockSize;
};
#endif

View File

@ -9,6 +9,7 @@ local librootFunctions =
memcmp.o
memmove.o
strdup.o
strndup.o
strlen.o
strnlen.o
strcmp.o

View File

@ -1,12 +1,15 @@
SubDir HAIKU_TOP src system boot loader net ;
UsePrivateHeaders kernel [ FDirName kernel boot platform $(TARGET_BOOT_PLATFORM) ] ;
UsePrivateHeaders drivers ;
SubDirC++Flags -fno-rtti ;
SubDirC++Flags -D_BOOT_MODE -fno-rtti ;
local tcp = ;
local iscsi = ;
if $(TARGET_ARCH) = ppc {
tcp = TCP.cpp ;
iscsi = iSCSITarget.cpp ;
}
KernelStaticLibrary boot_net :
@ -19,6 +22,7 @@ KernelStaticLibrary boot_net :
RemoteDisk.cpp
UDP.cpp
$(tcp)
$(iscsi)
: -fno-pic
;

View File

@ -0,0 +1,822 @@
/*
* Copyright 2010, Andreas Färber <andreas.faerber@web.de>
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include <boot/net/iSCSITarget.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <KernelExport.h>
#include <boot/net/ChainBuffer.h>
#include <boot/net/TCP.h>
#include <iscsi_cmds.h>
#include <scsi_cmds.h>
#include "real_time_clock.h"
//#define TRACE_ISCSI
#ifndef TRACE_ISCSI
#define TRACE(fmt, ...) ;
#else
#define TRACE(fmt, ...) printf("iSCSI: %s(): " fmt, __func__, ## __VA_ARGS__)
#endif
#define PANIC(fmt, ...) panic("iSCSI: %s(): " fmt, __func__, ## __VA_ARGS__)
#define ISCSI_ALIGNMENT 4
#define ISCSI_ALIGN(x) (((x) + ISCSI_ALIGNMENT - 1) & ~(ISCSI_ALIGNMENT - 1))
#define ISCSI_PADDING(x) ((((x) % ISCSI_ALIGNMENT) == 0) ? 0 \
: (ISCSI_ALIGNMENT - ((x) % ISCSI_ALIGNMENT)))
// derived from the schedulers
static int
_rand(void)
{
static int next = 0;
if (next == 0)
next = real_time_clock_usecs() / 1000;
next = next * 1103515245 + 12345;
return next;
}
bool
iSCSITarget::DiscoverTargets(ip_addr_t address, uint16 port, NodeList* devicesList)
{
iSCSISession* session = new(nothrow) iSCSISession();
if (session == NULL)
return false;
status_t error = session->Init(address, port);
if (error != B_OK) {
delete session;
return false;
}
const char* request = "SendTargets=All";
char* sendTargets = NULL;
size_t sendTargetsLength = 0;
error = session->Connection()->GetText(request, strlen(request) + 1,
&sendTargets, &sendTargetsLength);
session->Close();
delete session;
if (error != B_OK) {
return false;
}
bool addedDisk = false;
char* targetName = NULL;
bool seenAddress = false;
for (char* pair = sendTargets; pair < sendTargets + sendTargetsLength;
pair += strlen(pair) + 1) {
printf("%s\n", pair);
if (strncmp(pair, "TargetName=", strlen("TargetName=")) == 0) {
if (targetName != NULL && !seenAddress) {
if (_AddDevice(address, port, targetName, devicesList))
addedDisk = true;
}
seenAddress = false;
targetName = pair + strlen("TargetName=");
} else if (strncmp(pair, "TargetAddress=", strlen("TargetAddress="))
== 0) {
seenAddress = true;
char* targetAddress = pair + strlen("TargetAddress=");
char* comma = strrchr(targetAddress, ',');
int addressLength = comma - targetAddress;
targetAddress = strndup(targetAddress, addressLength);
uint16 targetPort = ISCSI_PORT;
char* colon = strrchr(targetAddress, ':');
if (colon != NULL) {
// colon could be part of an IPv6 address, e.g. [::1.2.3.4]
char* bracket = strrchr(targetAddress, ']');
if (bracket == NULL || bracket < colon) {
targetPort = strtol(colon + 1, NULL, 10);
colon[0] = '\0';
}
}
if (targetName != NULL && isdigit(targetAddress[0])) {
if (_AddDevice(ip_parse_address(targetAddress), targetPort,
targetName, devicesList))
addedDisk = true;
}
free(targetAddress);
}
}
if (targetName != NULL && !seenAddress) {
if (_AddDevice(address, port, targetName, devicesList))
addedDisk = true;
}
free(sendTargets);
return addedDisk;
}
bool
iSCSITarget::_AddDevice(ip_addr_t address, uint16 port,
const char* targetName, NodeList* devicesList)
{
TRACE("=> %s @ %08lx:%u\n", targetName, address, port);
iSCSITarget* disk = new(nothrow) iSCSITarget();
if (disk == NULL)
return false;
status_t error = disk->Init(address, port, targetName);
if (error != B_OK) {
delete disk;
return false;
}
TRACE("disk size = %llu\n", disk->Size());
devicesList->Add(disk);
return true;
}
iSCSITarget::iSCSITarget()
:
fSession(NULL),
fTargetName(NULL),
fTargetAlias(NULL)
{
}
iSCSITarget::~iSCSITarget()
{
free(fTargetName);
free(fTargetAlias);
delete fSession;
}
status_t
iSCSITarget::Init(ip_addr_t address, uint16 port, const char* targetName)
{
fTargetName = strdup(targetName);
if (fTargetName == NULL)
return B_NO_MEMORY;
fSession = new(nothrow) iSCSISession(fTargetName);
if (fSession == NULL)
return B_NO_MEMORY;
status_t error = fSession->Init(address, port, &fTargetAlias);
if (error != B_OK)
return error;
if (targetName == NULL)
return B_OK;
error = _GetSize();
if (error != B_OK)
return error;
return B_OK;
}
status_t
iSCSITarget::_GetSize()
{
scsi_cmd_read_capacity cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_OP_READ_CAPACITY;
cmd.relative_address = 0;
cmd.lun = 0;
cmd.lba = 0;
cmd.pmi = 0;
cmd.control = 0;
scsi_res_read_capacity resp;
status_t error = fSession->Connection()->SendCommand(&cmd, sizeof(cmd),
true, false, sizeof(resp),
&resp, 0, sizeof(resp));
if (error != B_OK) {
TRACE("error %lx sending command!\n", error);
return error;
}
TRACE("lba = %lu, block size = %lu\n", resp.lba, resp.block_size);
fLastLBA = resp.lba;
fBlockSize = resp.block_size;
return B_OK;
}
ssize_t
iSCSITarget::ReadAt(void* /*cookie*/, off_t pos, void* buffer, size_t bufferSize)
{
TRACE("pos=%llu, size = %lu\n", pos, bufferSize);
if (fSession == NULL)
return B_NO_INIT;
if (buffer == NULL || pos < 0 || pos >= Size())
return B_BAD_VALUE;
if (bufferSize == 0)
return 0;
uint32 blockOffset = pos % fBlockSize;
scsi_cmd_rw_16 cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_OP_READ_16;
cmd.force_unit_access = 0;
cmd.disable_page_out = 0;
cmd.lba = pos / fBlockSize;
cmd.length = (blockOffset + bufferSize + fBlockSize - 1) / fBlockSize;
cmd.control = 0;
status_t error = fSession->Connection()->SendCommand(&cmd, sizeof(cmd),
true, false, cmd.length * fBlockSize, buffer, blockOffset, bufferSize);
if (error != B_OK) {
TRACE("error %lx sending command!\n", error);
return error;
}
return bufferSize;
}
ssize_t
iSCSITarget::WriteAt(void* /*cookie*/, off_t pos, const void* buffer, size_t bufferSize)
{
return B_PERMISSION_DENIED;
}
status_t
iSCSITarget::GetName(char* buffer, size_t bufferSize) const
{
if (buffer == NULL)
return B_BAD_VALUE;
snprintf(buffer, bufferSize, "iSCSI:%s", fTargetName);
return B_OK;
}
off_t
iSCSITarget::Size() const
{
return (fLastLBA + 1) * fBlockSize;
}
iSCSISession::iSCSISession(const char* targetName)
:
fDiscovery(targetName == NULL),
fTargetName(targetName),
fConnection(NULL)
{
fCommandSequenceNumber = _rand();
}
iSCSISession::~iSCSISession()
{
if (Active())
Close();
delete fConnection;
}
status_t
iSCSISession::Init(ip_addr_t address, uint16 port, char** targetAlias)
{
fConnection = new(nothrow) iSCSIConnection();
if (fConnection == NULL)
return B_NO_MEMORY;
status_t error = fConnection->Init(this);
if (error != B_OK)
return error;
error = fConnection->Open(address, port);
if (error != B_OK)
return error;
error = fConnection->Login(fTargetName, targetAlias);
if (error != B_OK)
return error;
return B_OK;
}
status_t
iSCSISession::Close()
{
if (fConnection == NULL)
return B_NO_INIT;
status_t error = fConnection->Logout(true);
if (error != B_OK)
return error;
return B_OK;
}
iSCSIConnection::iSCSIConnection()
:
fSession(NULL),
fSocket(NULL),
fConnected(false),
fConnectionID(0)
{
}
iSCSIConnection::~iSCSIConnection()
{
if (fSocket != NULL && fConnected) {
if (Logout() != B_OK)
fSocket->Close();
}
delete fSocket;
}
status_t
iSCSIConnection::Init(iSCSISession* session)
{
fSession = session;
return B_OK;
}
status_t
iSCSIConnection::Open(ip_addr_t address, uint16 port)
{
fSocket = new(nothrow) TCPSocket();
if (fSocket == NULL)
return B_NO_MEMORY;
TRACE("connecting to target...\n");
status_t error = fSocket->Connect(address, port);
if (error != B_OK)
return error;
fConnected = true;
TRACE("connected.\n");
fConnectionID = _rand();
return B_OK;
}
status_t
iSCSIConnection::Login(const char* targetName, char** targetAlias)
{
char data[256];
size_t dataLength = 0;
const char* keyValue;
keyValue = "InitiatorName=iqn.2002-10.org.haiku-os:haiku.bootloader.hostX";
strcpy(data + dataLength, keyValue);
dataLength += strlen(keyValue) + 1;
if (targetName != NULL) {
dataLength += sprintf(data + dataLength, "TargetName=%s", targetName) + 1;
} else {
keyValue = "SessionType=Discovery";
strcpy(data + dataLength, keyValue);
dataLength += strlen(keyValue) + 1;
}
iscsi_login_request req;
req.transit = true;
req.c = false;
req.currentStage = ISCSI_SESSION_STAGE_LOGIN_OPERATIONAL_NEGOTIATION;
req.nextStage = ISCSI_SESSION_STAGE_FULL_FEATURE_PHASE;
req.versionMax = ISCSI_VERSION;
req.versionMin = ISCSI_VERSION;
req.totalAHSLength = 0;
req.dataSegmentLength = dataLength;
TRACE("data segment length = %lu\n", req.dataSegmentLength);
req.isid.t = ISCSI_ISID_RANDOM;
req.isid.a = 0;
uint32 random = _rand();
req.isid.b = random >> 8;
req.isid.c = random & 0xff;
req.isid.d = _rand();
req.tsih = 0;
req.initiatorTaskTag = _rand();
req.cid = fConnectionID;
req.cmdSN = fSession->CommandSequenceNumber();
req.expStatSN = 0;
status_t error = fSocket->Write(&req, sizeof(req));
if (error != B_OK) {
return error;
}
for (unsigned int i = dataLength; i < ISCSI_ALIGN(dataLength); i++)
data[i] = '\0';
dataLength = ISCSI_ALIGN(dataLength);
TRACE("data length = %lu\n", dataLength);
error = fSocket->Write(data, dataLength);
if (error != B_OK) {
TRACE("write error\n");
return error;
}
TRACE("request sent.\n");
iscsi_login_response resp;
size_t bytesRead = 0;
error = fSocket->Read(&resp, sizeof(resp), &bytesRead, 10000000LL);
if (error != B_OK) {
TRACE("response read error\n");
return error;
}
TRACE("bytesRead = %lu\n", bytesRead);
TRACE("response: opcode = %x, status class = %u, status detail = %u, transit = %u, data length = %lu\n",
resp.opcode, resp.statusClass, resp.statusDetail, resp.transit, resp.dataSegmentLength);
if (resp.dataSegmentLength > 0) {
error = fSocket->Read(data, resp.dataSegmentLength, &bytesRead, 1000000LL);
if (error != B_OK) {
TRACE("response data read error\n");
return error;
}
TRACE("bytesRead = %lu\n", bytesRead);
if (resp.dataSegmentLength % ISCSI_ALIGNMENT) {
error = fSocket->Read(NULL, ISCSI_PADDING(resp.dataSegmentLength), &bytesRead, 100000LL);
if (error != B_OK) {
TRACE("response padding read error\n");
return error;
}
TRACE("bytesRead = %lu\n", bytesRead);
}
for (char* pair = data; pair < data + resp.dataSegmentLength; pair += strlen(pair) + 1) {
TRACE("%s\n", pair);
if (targetAlias != NULL && strncmp(pair, "TargetAlias=", strlen("TargetAlias=")) == 0) {
*targetAlias = strdup(pair + strlen("TargetAlias="));
}
}
}
fStatusSequenceNumber = resp.statSN;
if (resp.statusClass != 0) {
TRACE("response indicates failure.\n");
return B_BAD_VALUE;
}
return B_OK;
}
status_t
iSCSIConnection::Logout(bool closeSession)
{
iscsi_logout_request req;
TRACE("req size = %lu\n", sizeof(req));
req.immediateDelivery = true;
req.reasonCode = closeSession ? ISCSI_LOGOUT_REASON_CLOSE_SESSION
: ISCSI_LOGOUT_REASON_CLOSE_CONNECTION;
req.totalAHSLength = 0;
req.dataSegmentLength = 0;
req.initiatorTaskTag = _rand();
req.cid = closeSession ? 0 : fConnectionID;
req.cmdSN = fSession->CommandSequenceNumber();
req.expStatSN = fStatusSequenceNumber;
status_t error = fSocket->Write(&req, sizeof(req));
if (error != B_OK) {
TRACE("request write error\n");
return error;
}
iscsi_logout_response resp;
TRACE("resp size = %lu\n", sizeof(resp));
size_t bytesRead = 0;
error = fSocket->Read(&resp, sizeof(resp), &bytesRead, 10000000LL);
if (error != B_OK) {
TRACE("response read error\n");
return error;
}
TRACE("bytesRead = %lu\n", bytesRead);
TRACE("response: opcode = %x, response = %u\n",
resp.opcode, resp.response);
fStatusSequenceNumber = resp.statSN;
if (resp.response != 0) {
TRACE("response indicates failure.\n");
return B_ERROR;
}
fSocket->Close();
fConnected = false;
return B_OK;
}
status_t
iSCSIConnection::GetText(const char* request, size_t requestLength, char** response, size_t* responseLength)
{
iscsi_text_request req;
req.immediateDelivery = true;
req.final = true;
req.c = false;
req.totalAHSLength = 0;
req.dataSegmentLength = requestLength;
req.lun = 0;
req.initiatorTaskTag = _rand();
req.targetTransferTag = 0xffffffffL;
req.cmdSN = fSession->NextCommandSequenceNumber();
req.expStatSN = fStatusSequenceNumber;
status_t error = fSocket->Write(&req, sizeof(req));
if (error != B_OK) {
TRACE("iSCSI text request write error\n");
return error;
}
error = fSocket->Write(request, requestLength);
if (error != B_OK) {
TRACE("iSCSI text request data write error\n");
return error;
}
if (requestLength % ISCSI_ALIGNMENT != 0) {
char buffer[3];
memset(buffer, 0, 3);
error = fSocket->Write(buffer, ISCSI_ALIGNMENT - (requestLength % ISCSI_ALIGNMENT));
if (error != B_OK) {
TRACE("iSCSI text request padding write error\n");
return error;
}
}
iscsi_logout_response resp;
size_t bytesRead = 0;
error = fSocket->Read(&resp, sizeof(resp), &bytesRead, 10000000LL);
if (error != B_OK) {
TRACE("response read error\n");
return error;
}
TRACE("bytesRead = %lu\n", bytesRead);
TRACE("response: opcode = %x\n",
resp.opcode);
*response = (char*)malloc(resp.dataSegmentLength);
if (*response == NULL)
return B_NO_MEMORY;
error = fSocket->Read(*response, resp.dataSegmentLength, &bytesRead, 10000000LL);
if (error != B_OK) {
TRACE("response read error\n");
return error;
}
TRACE("bytesRead = %lu\n", bytesRead);
*responseLength = resp.dataSegmentLength;
if ((resp.dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
error = fSocket->Read(NULL, ISCSI_PADDING(resp.dataSegmentLength), &bytesRead, 1000000LL);
if (error != B_OK) {
TRACE("response padding read error\n");
return error;
}
}
fStatusSequenceNumber = resp.statSN;
if (resp.opcode != ISCSI_OPCODE_TEXT_RESPONSE) {
PANIC("response opcode unexpected!");
return B_ERROR;
}
return B_OK;
}
status_t
iSCSIConnection::SendCommand(const void* command, size_t commandSize,
bool r, bool w, uint32 expectedDataTransferLength,
void* response, uint32 responseOffset, size_t responseLength)
{
TRACE("command size = %lu, offset = %lu, length = %lu\n", commandSize, responseOffset, responseLength);
iscsi_scsi_command req;
req.immediateDelivery = true;
req.final = true;
req.r = r;
req.w = w;
req.attr = 0;
req.totalAHSLength = 0;
req.dataSegmentLength = (commandSize > 16) ? commandSize - 16 : 0;
req.lun = 0;
req.initiatorTaskTag = _rand();
req.expectedDataTransferLength = expectedDataTransferLength;
req.cmdSN = fSession->NextCommandSequenceNumber();
req.expStatSN = fStatusSequenceNumber;
if (commandSize < 16)
memset(req.cdb, 0, 16);
memcpy(req.cdb, command, commandSize <= 16 ? commandSize : 16);
status_t error = fSocket->Write(&req, sizeof(req));
if (error != B_OK) {
TRACE("write error\n");
return error;
}
if (commandSize > 16) {
error = fSocket->Write((uint8*)command + 16, commandSize - 16);
if (error != B_OK) {
TRACE("write error\n");
return error;
}
if ((req.dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
char buffer[3];
memset(buffer, 0, 3);
error = fSocket->Write(buffer, ISCSI_ALIGNMENT - (req.dataSegmentLength % ISCSI_ALIGNMENT));
if (error != B_OK) {
TRACE("request padding write error\n");
return error;
}
}
}
struct iscsi_basic_header_segment resp;
error = _ReadResponse(&resp);
if (error != B_OK) {
TRACE("response read error\n");
return error;
}
while (r && resp.opcode != ISCSI_OPCODE_SCSI_RESPONSE) {
if (resp.opcode != ISCSI_OPCODE_SCSI_DATA_IN)
PANIC("response opcode %x unexpected\n", resp.opcode);
iscsi_scsi_data_in* dataIn = (iscsi_scsi_data_in*)&resp;
TRACE("A=%u, S=%u, bufferOffset=%lu, length = %lu\n", dataIn->acknowledge, dataIn->S, dataIn->bufferOffset, resp.dataSegmentLength);
if (resp.dataSegmentLength > 0) {
size_t toRead = resp.dataSegmentLength;
size_t toSkip = 0;
if (dataIn->bufferOffset < responseOffset) {
toSkip = MIN(responseOffset - dataIn->bufferOffset, resp.dataSegmentLength);
error = _Read(NULL, toSkip);
if (error != B_OK) {
TRACE("response skip error\n");
return error;
}
TRACE("skipped %lu response bytes\n", toSkip);
toRead -= toSkip;
}
if (toRead > 0) {
if (dataIn->bufferOffset + resp.dataSegmentLength > responseOffset + responseLength) {
size_t cutOff = expectedDataTransferLength - (responseOffset + responseLength);
toRead = MIN(expectedDataTransferLength - cutOff - dataIn->bufferOffset, resp.dataSegmentLength) - toSkip;
}
error = _Read((uint8*)response + dataIn->bufferOffset - responseOffset, toRead);
if (error != B_OK) {
TRACE("response read error: %lx\n", error);
return error;
}
}
size_t toCutOff = resp.dataSegmentLength - toSkip - toRead;
if (toCutOff > 0) {
error = _Read(NULL, toCutOff);
if (error != B_OK) {
TRACE("response cut-off error\n");
return error;
}
}
if (toSkip + toRead + toCutOff != resp.dataSegmentLength)
PANIC("inconcistency while reading detected!\n");
}
if ((resp.dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
error = _Read(NULL, ISCSI_PADDING(resp.dataSegmentLength));
if (error != B_OK) {
TRACE("response padding read error\n");
return error;
}
}
fStatusSequenceNumber = dataIn->statSN;
if (dataIn->S) {
// TODO check status
return B_OK;
}
error = _ReadResponse(&resp);
if (error != B_OK) {
TRACE("response read error\n");
return error;
}
}
TRACE("response: opcode = %x, AHS length = %x, data length = %lu\n",
resp.opcode, resp.totalAHSLength, resp.dataSegmentLength);
if (resp.opcode != ISCSI_OPCODE_SCSI_RESPONSE) {
PANIC("response opcode unexpected!\n");
return B_ERROR;
}
iscsi_scsi_response* scsiResp = (iscsi_scsi_response*)&resp;
TRACE("response: response = %x, status = %x, SNACK=%x\n",
scsiResp->response, scsiResp->status, scsiResp->snackTag);
if (resp.dataSegmentLength > 0) {
void* buffer = NULL;
if (scsiResp->status != SCSI_STATUS_GOOD)
buffer = malloc(resp.dataSegmentLength);
error = _Read(buffer, resp.dataSegmentLength);
if (error != B_OK) {
TRACE("response read error\n");
return error;
}
#ifdef TRACE_ISCSI
if (scsiResp->status != SCSI_STATUS_GOOD) {
scsi_sense* sense = (scsi_sense*)((uint16*)buffer + 1);
TRACE("error code = %u, sense key = %u, ILI = %u, asc = %x\n",
sense->error_code, sense->sense_key, sense->ILI, (sense->asc << 8) | sense->ascq);
}
#endif
free(buffer);
}
if ((resp.dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
error = _Read(NULL, ISCSI_PADDING(resp.dataSegmentLength));
if (error != B_OK) {
TRACE("response padding read error\n");
return error;
}
}
fStatusSequenceNumber = scsiResp->statSN;
if (scsiResp->response != 0x00 || scsiResp->status != SCSI_STATUS_GOOD)
return B_BAD_VALUE;
return B_OK;
}
status_t
iSCSIConnection::_ReadResponse(iscsi_basic_header_segment* resp, bigtime_t timeout)
{
if (resp == NULL)
return B_BAD_VALUE;
status_t error = _Read(resp, sizeof(iscsi_basic_header_segment), timeout);
if (error != B_OK) {
TRACE("initial read failed\n");
return error;
}
if (resp->reserved == true) {
TRACE("reserved bit is 1!\n");
return B_IO_ERROR;
}
// theoretically process header checksum here, if available
// At any time a target can send a NOP-In, requiring us to answer with a NOP-Out.
// TODO handle asynchronous event message
while (resp->opcode == ISCSI_OPCODE_NOP_IN) {
iscsi_nop_in* nopIn = (iscsi_nop_in*)resp;
// A NOP-In can also be the answer to our own NOP-Out though.
if (nopIn->targetTransferTag == 0xffffffff)
return B_OK;
TRACE("received NOP-In (ping data length = %lu)\n", nopIn->dataSegmentLength);
void* pingData = NULL;
if (nopIn->dataSegmentLength > 0) {
pingData = malloc(nopIn->dataSegmentLength);
if (pingData == NULL)
return B_NO_MEMORY;
error = _Read(pingData, nopIn->dataSegmentLength, timeout);
if (error != B_OK) {
TRACE("NOP-In ping data read error\n");
free(pingData);
return error;
}
}
if ((nopIn->dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
error = _Read(NULL, ISCSI_PADDING(nopIn->dataSegmentLength), timeout);
if (error != B_OK) {
TRACE("NOP-In padding read error\n");
free(pingData);
return error;
}
}
iscsi_nop_out nopOut;
nopOut.totalAHSLength = 0;
nopOut.dataSegmentLength = nopIn->dataSegmentLength;
nopOut.lun = nopIn->lun;
nopOut.initiatorTaskTag = 0xffffffff;
nopOut.targetTransferTag = nopIn->targetTransferTag;
nopOut.cmdSN = fSession->NextCommandSequenceNumber();
nopOut.expStatSN = fStatusSequenceNumber;
error = fSocket->Write(&nopOut, sizeof(nopOut));
if (error != B_OK) {
TRACE("NOP-Out write error\n");
free(pingData);
return error;
}
if (nopOut.dataSegmentLength > 0) {
error = fSocket->Write(pingData, nopOut.dataSegmentLength);
free(pingData);
if (error != B_OK) {
TRACE("NOP-Out ping data write error\n");
return error;
}
if ((nopOut.dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
char buffer[3];
memset(buffer, 0, 3);
error = fSocket->Write(buffer, ISCSI_ALIGNMENT - (nopOut.dataSegmentLength % ISCSI_ALIGNMENT));
if (error != B_OK) {
TRACE("NOP-Out padding write error\n");
return error;
}
}
}
error = _Read(resp, sizeof(iscsi_basic_header_segment), timeout);
if (error != B_OK) {
TRACE("read failed\n");
return error;
}
}
return B_OK;
}
status_t
iSCSIConnection::_Read(void* buffer, size_t bufferSize, bigtime_t timeout)
{
size_t bytesRead;
status_t error = fSocket->Read(buffer, bufferSize, &bytesRead, timeout);
if (error != B_OK)
return error;
if (bytesRead < bufferSize) {
dprintf("not enough data available: %lu vs. %lu\n", bytesRead, bufferSize);
return B_ERROR;
}
return B_OK;
}