diff options
Diffstat (limited to 'src/VBox/Storage/ISCSI.cpp')
-rw-r--r-- | src/VBox/Storage/ISCSI.cpp | 5552 |
1 files changed, 5552 insertions, 0 deletions
diff --git a/src/VBox/Storage/ISCSI.cpp b/src/VBox/Storage/ISCSI.cpp new file mode 100644 index 00000000..267172a5 --- /dev/null +++ b/src/VBox/Storage/ISCSI.cpp @@ -0,0 +1,5552 @@ +/* $Id: ISCSI.cpp $ */ +/** @file + * iSCSI initiator driver, VD backend. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_VD_ISCSI +#include <VBox/vd-plugin.h> +#include <VBox/err.h> + +#include <VBox/log.h> +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/uuid.h> +#include <iprt/string.h> +#include <iprt/asm.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include <iprt/md5.h> +#include <iprt/tcp.h> +#include <iprt/time.h> +#include <VBox/scsi.h> + +#include "VDBackends.h" +#include "VDBackendsInline.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** The maximum number of release log entries per image. */ +#define MAX_LOG_REL_ERRORS 1024 + +/** Default port number to use for iSCSI. */ +#define ISCSI_DEFAULT_PORT 3260 + + +/** Converts a number in the range of 0 - 15 into the corresponding hex char. */ +#define NUM_2_HEX(b) ('0' + (b) + (((b) > 9) ? 39 : 0)) +/** Converts a hex char into the corresponding number in the range 0-15. */ +#define HEX_2_NUM(c) (((c) <= '9') ? ((c) - '0') : (((c - 'A' + 10) & 0xf))) +/* Converts a base64 char into the corresponding number in the range 0-63. */ +#define B64_2_NUM(c) ((c >= 'A' && c <= 'Z') ? (c - 'A') : (c >= 'a' && c <= 'z') ? (c - 'a' + 26) : (c >= '0' && c <= '9') ? (c - '0' + 52) : (c == '+') ? 62 : (c == '/') ? 63 : -1) + + +/** Minimum CHAP_MD5 challenge length in bytes. */ +#define CHAP_MD5_CHALLENGE_MIN 16 +/** Maximum CHAP_MD5 challenge length in bytes. */ +#define CHAP_MD5_CHALLENGE_MAX 24 + + +/** + * SCSI peripheral device type. */ +typedef enum SCSIDEVTYPE +{ + /** direct-access device. */ + SCSI_DEVTYPE_DISK = 0, + /** sequential-access device. */ + SCSI_DEVTYPE_TAPE, + /** printer device. */ + SCSI_DEVTYPE_PRINTER, + /** processor device. */ + SCSI_DEVTYPE_PROCESSOR, + /** write-once device. */ + SCSI_DEVTYPE_WORM, + /** CD/DVD device. */ + SCSI_DEVTYPE_CDROM, + /** scanner device. */ + SCSI_DEVTYPE_SCANNER, + /** optical memory device. */ + SCSI_DEVTYPE_OPTICAL, + /** medium changer. */ + SCSI_DEVTYPE_CHANGER, + /** communications device. */ + SCSI_DEVTYPE_COMMUNICATION, + /** storage array controller device. */ + SCSI_DEVTYPE_RAIDCTL = 0x0c, + /** enclosure services device. */ + SCSI_DEVTYPE_ENCLOSURE, + /** simplified direct-access device. */ + SCSI_DEVTYPE_SIMPLEDISK, + /** optical card reader/writer device. */ + SCSI_DEVTYPE_OCRW, + /** bridge controller device. */ + SCSI_DEVTYPE_BRIDGE, + /** object-based storage device. */ + SCSI_DEVTYPE_OSD +} SCSIDEVTYPE; + +/** Mask for extracting the SCSI device type out of the first byte of the INQUIRY response. */ +#define SCSI_DEVTYPE_MASK 0x1f + +/** Mask to extract the CmdQue bit out of the seventh byte of the INQUIRY response. */ +#define SCSI_INQUIRY_CMDQUE_MASK 0x02 + +/** Maximum PDU payload size we can handle in one piece. Greater or equal than + * s_iscsiConfigDefaultWriteSplit. */ +#define ISCSI_DATA_LENGTH_MAX _256K + +/** Maximum PDU size we can handle in one piece. */ +#define ISCSI_RECV_PDU_BUFFER_SIZE (ISCSI_DATA_LENGTH_MAX + ISCSI_BHS_SIZE) + + +/** Version of the iSCSI standard which this initiator driver can handle. */ +#define ISCSI_MY_VERSION 0 + + +/** Length of ISCSI basic header segment. */ +#define ISCSI_BHS_SIZE 48 + + +/** Reserved task tag value. */ +#define ISCSI_TASK_TAG_RSVD 0xffffffff + + +/** + * iSCSI opcodes. */ +typedef enum ISCSIOPCODE +{ + /** NOP-Out. */ + ISCSIOP_NOP_OUT = 0x00000000, + /** SCSI command. */ + ISCSIOP_SCSI_CMD = 0x01000000, + /** SCSI task management request. */ + ISCSIOP_SCSI_TASKMGMT_REQ = 0x02000000, + /** Login request. */ + ISCSIOP_LOGIN_REQ = 0x03000000, + /** Text request. */ + ISCSIOP_TEXT_REQ = 0x04000000, + /** SCSI Data-Out. */ + ISCSIOP_SCSI_DATA_OUT = 0x05000000, + /** Logout request. */ + ISCSIOP_LOGOUT_REQ = 0x06000000, + /** SNACK request. */ + ISCSIOP_SNACK_REQ = 0x10000000, + + /** NOP-In. */ + ISCSIOP_NOP_IN = 0x20000000, + /** SCSI response. */ + ISCSIOP_SCSI_RES = 0x21000000, + /** SCSI Task Management response. */ + ISCSIOP_SCSI_TASKMGMT_RES = 0x22000000, + /** Login response. */ + ISCSIOP_LOGIN_RES = 0x23000000, + /** Text response. */ + ISCSIOP_TEXT_RES = 0x24000000, + /** SCSI Data-In. */ + ISCSIOP_SCSI_DATA_IN = 0x25000000, + /** Logout response. */ + ISCSIOP_LOGOUT_RES = 0x26000000, + /** Ready To Transfer (R2T). */ + ISCSIOP_R2T = 0x31000000, + /** Asynchronous message. */ + ISCSIOP_ASYN_MSG = 0x32000000, + /** Reject. */ + ISCSIOP_REJECT = 0x3f000000 +} ISCSIOPCODE; + +/** Mask for extracting the iSCSI opcode out of the first header word. */ +#define ISCSIOP_MASK 0x3f000000 + + +/** ISCSI BHS word 0: Request should be processed immediately. */ +#define ISCSI_IMMEDIATE_DELIVERY_BIT 0x40000000 + +/** ISCSI BHS word 0: This is the final PDU for this request/response. */ +#define ISCSI_FINAL_BIT 0x00800000 +/** ISCSI BHS word 0: Mask for extracting the CSG. */ +#define ISCSI_CSG_MASK 0x000c0000 +/** ISCSI BHS word 0: Shift offset for extracting the CSG. */ +#define ISCSI_CSG_SHIFT 18 +/** ISCSI BHS word 0: Mask for extracting the NSG. */ +#define ISCSI_NSG_MASK 0x00030000 +/** ISCSI BHS word 0: Shift offset for extracting the NSG. */ +#define ISCSI_NSG_SHIFT 16 + +/** ISCSI BHS word 0: task attribute untagged */ +#define ISCSI_TASK_ATTR_UNTAGGED 0x00000000 +/** ISCSI BHS word 0: task attribute simple */ +#define ISCSI_TASK_ATTR_SIMPLE 0x00010000 +/** ISCSI BHS word 0: task attribute ordered */ +#define ISCSI_TASK_ATTR_ORDERED 0x00020000 +/** ISCSI BHS word 0: task attribute head of queue */ +#define ISCSI_TASK_ATTR_HOQ 0x00030000 +/** ISCSI BHS word 0: task attribute ACA */ +#define ISCSI_TASK_ATTR_ACA 0x00040000 + +/** ISCSI BHS word 0: transit to next login phase. */ +#define ISCSI_TRANSIT_BIT 0x00800000 +/** ISCSI BHS word 0: continue with login negotiation. */ +#define ISCSI_CONTINUE_BIT 0x00400000 + +/** ISCSI BHS word 0: residual underflow. */ +#define ISCSI_RESIDUAL_UNFL_BIT 0x00020000 +/** ISCSI BHS word 0: residual overflow. */ +#define ISCSI_RESIDUAL_OVFL_BIT 0x00040000 +/** ISCSI BHS word 0: Bidirectional read residual underflow. */ +#define ISCSI_BI_READ_RESIDUAL_UNFL_BIT 0x00080000 +/** ISCSI BHS word 0: Bidirectional read residual overflow. */ +#define ISCSI_BI_READ_RESIDUAL_OVFL_BIT 0x00100000 + +/** ISCSI BHS word 0: SCSI response mask. */ +#define ISCSI_SCSI_RESPONSE_MASK 0x0000ff00 +/** ISCSI BHS word 0: SCSI status mask. */ +#define ISCSI_SCSI_STATUS_MASK 0x000000ff + +/** ISCSI BHS word 0: response includes status. */ +#define ISCSI_STATUS_BIT 0x00010000 + +/** Maximum number of scatter/gather segments needed to send a PDU. */ +#define ISCSI_SG_SEGMENTS_MAX 4 + +/** Number of entries in the command table. */ +#define ISCSI_CMD_WAITING_ENTRIES 32 + +/** + * iSCSI login status class. */ +typedef enum ISCSILOGINSTATUSCLASS +{ + /** Success. */ + ISCSI_LOGIN_STATUS_CLASS_SUCCESS = 0, + /** Redirection. */ + ISCSI_LOGIN_STATUS_CLASS_REDIRECTION, + /** Initiator error. */ + ISCSI_LOGIN_STATUS_CLASS_INITIATOR_ERROR, + /** Target error. */ + ISCSI_LOGIN_STATUS_CLASS_TARGET_ERROR +} ISCSILOGINSTATUSCLASS; + + +/** + * iSCSI connection state. */ +typedef enum ISCSISTATE +{ + /** Not having a connection/session at all. */ + ISCSISTATE_FREE, + /** Currently trying to login. */ + ISCSISTATE_IN_LOGIN, + /** Normal operation, corresponds roughly to the Full Feature Phase. */ + ISCSISTATE_NORMAL, + /** Currently trying to logout. */ + ISCSISTATE_IN_LOGOUT +} ISCSISTATE; + +/** + * iSCSI PDU send/receive flags (and maybe more in the future). */ +typedef enum ISCSIPDUFLAGS +{ + /** No special flags */ + ISCSIPDU_DEFAULT = 0, + /** Do not attempt to re-attach to the target if the connection is lost */ + ISCSIPDU_NO_REATTACH = RT_BIT(1) +} ISCSIPDUFLAGS; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * iSCSI login negotiation parameter + */ +typedef struct ISCSIPARAMETER +{ + /** Name of the parameter. */ + const char *pszParamName; + /** Value of the parameter. */ + const char *pszParamValue; + /** Length of the binary parameter. 0=zero-terminated string. */ + size_t cbParamValue; +} ISCSIPARAMETER; + + +/** + * iSCSI Response PDU buffer (scatter). + */ +typedef struct ISCSIRES +{ + /** Length of PDU segment. */ + size_t cbSeg; + /** Pointer to PDU segment. */ + void *pvSeg; +} ISCSIRES; +/** Pointer to an iSCSI Response PDU buffer. */ +typedef ISCSIRES *PISCSIRES; +/** Pointer to a const iSCSI Response PDU buffer. */ +typedef ISCSIRES const *PCISCSIRES; + + +/** + * iSCSI Request PDU buffer (gather). + */ +typedef struct ISCSIREQ +{ + /** Length of PDU segment in bytes. */ + size_t cbSeg; + /** Pointer to PDU segment. */ + const void *pcvSeg; +} ISCSIREQ; +/** Pointer to an iSCSI Request PDU buffer. */ +typedef ISCSIREQ *PISCSIREQ; +/** Pointer to a const iSCSI Request PDU buffer. */ +typedef ISCSIREQ const *PCISCSIREQ; + + +/** + * SCSI transfer directions. + */ +typedef enum SCSIXFER +{ + SCSIXFER_NONE = 0, + SCSIXFER_TO_TARGET, + SCSIXFER_FROM_TARGET, + SCSIXFER_TO_FROM_TARGET +} SCSIXFER, *PSCSIXFER; + +/** Forward declaration. */ +typedef struct ISCSIIMAGE *PISCSIIMAGE; + +/** + * SCSI request structure. + */ +typedef struct SCSIREQ +{ + /** I/O context associated with this request. */ + PVDIOCTX pIoCtx; + /** Transfer direction. */ + SCSIXFER enmXfer; + /** Length of command block. */ + size_t cbCDB; + /** Length of Initiator2Target data buffer. */ + size_t cbI2TData; + /** Length of Target2Initiator data buffer. */ + size_t cbT2IData; + /** Length of sense buffer + * This contains the number of sense bytes received upon completion. */ + size_t cbSense; + /** Completion status of the command. */ + uint8_t status; + /** The CDB. */ + uint8_t abCDB[16]; + /** The sense buffer. */ + uint8_t abSense[96]; + /** Status code to return if we got sense data. */ + int rcSense; + /** Pointer to the Initiator2Target S/G list. */ + PRTSGSEG paI2TSegs; + /** Number of entries in the I2T S/G list. */ + unsigned cI2TSegs; + /** Pointer to the Target2Initiator S/G list. */ + PRTSGSEG paT2ISegs; + /** Number of entries in the T2I S/G list. */ + unsigned cT2ISegs; + /** S/G buffer for the target to initiator bits. */ + RTSGBUF SgBufT2I; + /** Number of retries if the command completes with sense + * data before we return with an error. + */ + unsigned cSenseRetries; + /** The S/G list - variable in size. + * This array holds both the I2T and T2I segments. + * The I2T segments are first and the T2I are second. + */ + RTSGSEG aSegs[1]; +} SCSIREQ, *PSCSIREQ; + +typedef enum ISCSICMDTYPE +{ + /** Process a SCSI request. */ + ISCSICMDTYPE_REQ = 0, + /** Call a function in the I/O thread. */ + ISCSICMDTYPE_EXEC, + /** Usual 32bit hack. */ + ISCSICMDTYPE_32BIT_HACK = 0x7fffffff +} ISCSICMDTYPE; + + +/** The command completion function. */ +typedef DECLCALLBACKTYPE(void, FNISCSICMDCOMPLETED,(PISCSIIMAGE pImage, int rcReq, void *pvUser)); +/** Pointer to a command completion function. */ +typedef FNISCSICMDCOMPLETED *PFNISCSICMDCOMPLETED; + +/** The command execution function. */ +typedef DECLCALLBACKTYPE(int, FNISCSIEXEC,(void *pvUser)); +/** Pointer to a command execution function. */ +typedef FNISCSIEXEC *PFNISCSIEXEC; + +/** + * Structure used to complete a synchronous request. + */ +typedef struct ISCSICMDSYNC +{ + /** Event semaphore to wakeup the waiting thread. */ + RTSEMEVENT EventSem; + /** Status code of the command. */ + int rcCmd; +} ISCSICMDSYNC, *PISCSICMDSYNC; + +/** + * iSCSI command. + * Used to forward requests to the I/O thread + * if existing. + */ +typedef struct ISCSICMD +{ + /** Next one in the list. */ + struct ISCSICMD *pNext; + /** Assigned ITT. */ + uint32_t Itt; + /** Completion callback. */ + PFNISCSICMDCOMPLETED pfnComplete; + /** Opaque user data. */ + void *pvUser; + /** Command to execute. */ + ISCSICMDTYPE enmCmdType; + /** Command type dependent data. */ + union + { + /** Process a SCSI request. */ + struct + { + /** The SCSI request to process. */ + PSCSIREQ pScsiReq; + } ScsiReq; + /** Call a function in the I/O thread. */ + struct + { + /** The method to execute. */ + PFNISCSIEXEC pfnExec; + /** User data. */ + void *pvUser; + } Exec; + } CmdType; +} ISCSICMD, *PISCSICMD; + +/** + * Send iSCSI PDU. + * Contains all necessary data to send a PDU. + */ +typedef struct ISCSIPDUTX +{ + /** Pointer to the next PDu to send. */ + struct ISCSIPDUTX *pNext; + /** The BHS. */ + uint32_t aBHS[12]; + /** Assigned CmdSN for this PDU. */ + uint32_t CmdSN; + /** The S/G buffer used for sending. */ + RTSGBUF SgBuf; + /** Number of bytes to send until the PDU completed. */ + size_t cbSgLeft; + /** The iSCSI command this PDU belongs to. */ + PISCSICMD pIScsiCmd; + /** Number of segments in the request segments array. */ + unsigned cISCSIReq; + /** The request segments - variable in size. */ + RTSGSEG aISCSIReq[1]; +} ISCSIPDUTX, *PISCSIPDUTX; + +/** + * Block driver instance data. + */ +typedef struct ISCSIIMAGE +{ + /** Pointer to the filename (location). Not really used. */ + const char *pszFilename; + /** Pointer to the initiator name. */ + char *pszInitiatorName; + /** Pointer to the target name. */ + char *pszTargetName; + /** Pointer to the target address. */ + char *pszTargetAddress; + /** Pointer to the user name for authenticating the Initiator. */ + char *pszInitiatorUsername; + /** Pointer to the secret for authenticating the Initiator. */ + uint8_t *pbInitiatorSecret; + /** Length of the secret for authenticating the Initiator. */ + size_t cbInitiatorSecret; + /** Pointer to the user name for authenticating the Target. */ + char *pszTargetUsername; + /** Pointer to the secret for authenticating the Initiator. */ + uint8_t *pbTargetSecret; + /** Length of the secret for authenticating the Initiator. */ + size_t cbTargetSecret; + /** Limit for iSCSI writes, essentially limiting the amount of data + * written in a single write. This is negotiated with the target, so + * the actual size might be smaller. */ + uint32_t cbWriteSplit; + /** Initiator session identifier. */ + uint64_t ISID; + /** SCSI Logical Unit Number. */ + uint64_t LUN; + /** Pointer to the per-disk VD interface list. */ + PVDINTERFACE pVDIfsDisk; + /** Pointer to the per-image VD interface list. */ + PVDINTERFACE pVDIfsImage; + /** Error interface. */ + PVDINTERFACEERROR pIfError; + /** Config interface. */ + PVDINTERFACECONFIG pIfConfig; + /** I/O interface. */ + PVDINTERFACEIOINT pIfIo; + /** TCP network stack interface. */ + PVDINTERFACETCPNET pIfNet; + /** Image open flags. */ + unsigned uOpenFlags; + /** Number of re-login retries when a connection fails. */ + uint32_t cISCSIRetries; + /** Sector size on volume. */ + uint32_t cbSector; + /** Size of volume in sectors. */ + uint64_t cVolume; + /** Total volume size in bytes. Easier than multiplying the above values all the time. */ + uint64_t cbSize; + + /** Negotiated maximum data length when sending to target. */ + uint32_t cbSendDataLength; + /** Negotiated maximum data length when receiving from target. */ + uint32_t cbRecvDataLength; + + /** Current state of the connection/session. */ + ISCSISTATE state; + /** Flag whether the first Login Response PDU has been seen. */ + bool FirstRecvPDU; + /** Initiator Task Tag of the last iSCSI request PDU. */ + uint32_t ITT; + /** Sequence number of the last command. */ + uint32_t CmdSN; + /** Sequence number of the next command expected by the target. */ + uint32_t ExpCmdSN; + /** Maximum sequence number accepted by the target (determines size of window). */ + uint32_t MaxCmdSN; + /** Expected sequence number of next status. */ + uint32_t ExpStatSN; + /** Currently active request. */ + PISCSIREQ paCurrReq; + /** Segment number of currently active request. */ + uint32_t cnCurrReq; + /** Pointer to receive PDU buffer. (Freed by RT) */ + void *pvRecvPDUBuf; + /** Length of receive PDU buffer. */ + size_t cbRecvPDUBuf; + /** Mutex protecting against concurrent use from several threads. */ + RTSEMMUTEX Mutex; + + /** Pointer to the target hostname. */ + char *pszHostname; + /** Port to use on the target host. */ + uint32_t uPort; + /** Socket handle of the TCP connection. */ + VDSOCKET Socket; + /** Timeout for read operations on the TCP connection (in milliseconds). */ + uint32_t uReadTimeout; + /** Flag whether to automatically generate the initiator name. */ + bool fAutomaticInitiatorName; + /** Flag whether to automatically determine the LUN. */ + bool fAutomaticLUN; + /** Flag whether to use the host IP stack or DevINIP. */ + bool fHostIP; + /** Flag whether to dump malformed packets in the release log. */ + bool fDumpMalformedPackets; + /** Flag whtether the target is readonly. */ + bool fTargetReadOnly; + /** Flag whether to retry the connection before processing new requests. */ + bool fTryReconnect; + + /** Head of request queue */ + PISCSICMD pScsiReqQueue; + /** Mutex protecting the request queue from concurrent access. */ + RTSEMMUTEX MutexReqQueue; + /** I/O thread. */ + RTTHREAD hThreadIo; + /** Flag whether the thread should be still running. */ + volatile bool fRunning; + /* Flag whether the target supports command queuing. */ + bool fCmdQueuingSupported; + /** Flag whether extended select is supported. */ + bool fExtendedSelectSupported; + /** Padding used for aligning the PDUs. */ + uint8_t aPadding[4]; + /** Socket events to poll for. */ + uint32_t fPollEvents; + /** Number of bytes to read to complete the current PDU. */ + size_t cbRecvPDUResidual; + /** Current position in the PDU buffer. */ + uint8_t *pbRecvPDUBufCur; + /** Flag whether we are currently reading the BHS. */ + bool fRecvPDUBHS; + /** List of PDUs waiting to get transmitted. */ + PISCSIPDUTX pIScsiPDUTxHead; + /** Tail of PDUs waiting to get transmitted. */ + PISCSIPDUTX pIScsiPDUTxTail; + /** PDU we are currently transmitting. */ + PISCSIPDUTX pIScsiPDUTxCur; + /** Number of commands waiting for an answer from the target. + * Used for timeout handling for poll. + */ + unsigned cCmdsWaiting; + /** Table of commands waiting for a response from the target. */ + PISCSICMD aCmdsWaiting[ISCSI_CMD_WAITING_ENTRIES]; + /** Number of logins since last successful I/O. + * Used to catch the case where logging succeeds but + * processing read/write/flushes cause a disconnect. + */ + volatile uint32_t cLoginsSinceIo; + + /** Release log counter. */ + unsigned cLogRelErrors; + /** The static region list. */ + VDREGIONLIST RegionList; +} ISCSIIMAGE; + + +/********************************************************************************************************************************* +* Static Variables * +*********************************************************************************************************************************/ + +/** Default initiator basename. */ +static const char *s_iscsiDefaultInitiatorBasename = "iqn.2009-08.com.sun.virtualbox.initiator"; + +/** Default LUN. */ +static const char *s_iscsiConfigDefaultLUN = "0"; + +/** Default timeout, 10 seconds. */ +static const char *s_iscsiConfigDefaultTimeout = "10000"; + +/** Default write split value, less or equal to ISCSI_DATA_LENGTH_MAX. */ +static const char *s_iscsiConfigDefaultWriteSplit = "262144"; + +/** Default host IP stack. */ +static const char *s_iscsiConfigDefaultHostIPStack = "1"; + +/** Default dump malformed packet configuration value. */ +static const char *s_iscsiConfigDefaultDumpMalformedPackets = "0"; + +/** Description of all accepted config parameters. */ +static const VDCONFIGINFO s_iscsiConfigInfo[] = +{ + { "TargetName", NULL, VDCFGVALUETYPE_STRING, VD_CFGKEY_MANDATORY }, + /* LUN is defined of string type to handle the "enc" prefix. */ + { "LUN", s_iscsiConfigDefaultLUN, VDCFGVALUETYPE_STRING, VD_CFGKEY_MANDATORY }, + { "TargetAddress", NULL, VDCFGVALUETYPE_STRING, VD_CFGKEY_MANDATORY }, + { "InitiatorName", NULL, VDCFGVALUETYPE_STRING, 0 }, + { "InitiatorUsername", NULL, VDCFGVALUETYPE_STRING, 0 }, + { "InitiatorSecret", NULL, VDCFGVALUETYPE_BYTES, 0 }, + { "TargetUsername", NULL, VDCFGVALUETYPE_STRING, VD_CFGKEY_EXPERT }, + { "TargetSecret", NULL, VDCFGVALUETYPE_BYTES, VD_CFGKEY_EXPERT }, + { "WriteSplit", s_iscsiConfigDefaultWriteSplit, VDCFGVALUETYPE_INTEGER, VD_CFGKEY_EXPERT }, + { "Timeout", s_iscsiConfigDefaultTimeout, VDCFGVALUETYPE_INTEGER, VD_CFGKEY_EXPERT }, + { "HostIPStack", s_iscsiConfigDefaultHostIPStack, VDCFGVALUETYPE_INTEGER, VD_CFGKEY_EXPERT }, + { "DumpMalformedPackets", s_iscsiConfigDefaultDumpMalformedPackets, VDCFGVALUETYPE_INTEGER, VD_CFGKEY_EXPERT }, + { NULL, NULL, VDCFGVALUETYPE_INTEGER, 0 } +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/* iSCSI low-level functions (only to be used from the iSCSI high-level functions). */ +static uint32_t iscsiNewITT(PISCSIIMAGE pImage); +static int iscsiSendPDU(PISCSIIMAGE pImage, PISCSIREQ paReq, uint32_t cnReq, uint32_t uFlags); +static int iscsiRecvPDU(PISCSIIMAGE pImage, uint32_t itt, PISCSIRES paRes, uint32_t cnRes, uint32_t fFlags); +static int iscsiRecvPDUAsync(PISCSIIMAGE pImage); +static int iscsiSendPDUAsync(PISCSIIMAGE pImage); +static int iscsiValidatePDU(PISCSIRES paRes, uint32_t cnRes); +static int iscsiRecvPDUProcess(PISCSIIMAGE pImage, PISCSIRES paRes, uint32_t cnRes); +static int iscsiPDUTxPrepare(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd); +static int iscsiRecvPDUUpdateRequest(PISCSIIMAGE pImage, PISCSIRES paRes, uint32_t cnRes); +static void iscsiCmdComplete(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd, int rcCmd); +static int iscsiTextAddKeyValue(uint8_t *pbBuf, size_t cbBuf, size_t *pcbBufCurr, const char *pcszKey, const char *pcszValue, size_t cbValue); +static int iscsiTextGetKeyValue(const uint8_t *pbBuf, size_t cbBuf, const char *pcszKey, const char **ppcszValue); +static int iscsiStrToBinary(const char *pcszValue, uint8_t *pbValue, size_t *pcbValue); +static int iscsiUpdateParameters(PISCSIIMAGE pImage, const uint8_t *pbBuf, size_t cbBuf); + +/* Serial number arithmetic comparison. */ +static bool serial_number_less(uint32_t sn1, uint32_t sn2); +static bool serial_number_greater(uint32_t sn1, uint32_t sn2); + +/* CHAP-MD5 functions. */ +#ifdef IMPLEMENT_TARGET_AUTH +static void chap_md5_generate_challenge(uint8_t *pbChallenge, size_t *pcbChallenge); +#endif +static void chap_md5_compute_response(uint8_t *pbResponse, uint8_t id, const uint8_t *pbChallenge, size_t cbChallenge, + const uint8_t *pbSecret, size_t cbSecret); + +/** + * Internal: release log wrapper limiting the number of entries. + */ +DECLINLINE(void) iscsiLogRel(PISCSIIMAGE pImage, const char *pcszFormat, ...) +{ + if (pImage->cLogRelErrors++ < MAX_LOG_REL_ERRORS) + { + va_list va; + + va_start(va, pcszFormat); + LogRel(("%N\n", pcszFormat, &va)); + va_end(va); + } +} + +DECLINLINE(bool) iscsiIsClientConnected(PISCSIIMAGE pImage) +{ + return pImage->Socket != NIL_VDSOCKET + && pImage->pIfNet->pfnIsClientConnected(pImage->Socket); +} + +/** + * Calculates the hash for the given ITT used + * to look up the command in the table. + */ +DECLINLINE(uint32_t) iscsiIttHash(uint32_t Itt) +{ + return Itt % ISCSI_CMD_WAITING_ENTRIES; +} + +static PISCSICMD iscsiCmdGetFromItt(PISCSIIMAGE pImage, uint32_t Itt) +{ + PISCSICMD pIScsiCmd = NULL; + + pIScsiCmd = pImage->aCmdsWaiting[iscsiIttHash(Itt)]; + + while ( pIScsiCmd + && pIScsiCmd->Itt != Itt) + pIScsiCmd = pIScsiCmd->pNext; + + return pIScsiCmd; +} + +static void iscsiCmdInsert(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd) +{ + PISCSICMD pIScsiCmdOld; + uint32_t idx = iscsiIttHash(pIScsiCmd->Itt); + + Assert(!pIScsiCmd->pNext); + + pIScsiCmdOld = pImage->aCmdsWaiting[idx]; + pIScsiCmd->pNext = pIScsiCmdOld; + pImage->aCmdsWaiting[idx] = pIScsiCmd; + pImage->cCmdsWaiting++; +} + +static PISCSICMD iscsiCmdRemove(PISCSIIMAGE pImage, uint32_t Itt) +{ + PISCSICMD pIScsiCmd = NULL; + PISCSICMD pIScsiCmdPrev = NULL; + uint32_t idx = iscsiIttHash(Itt); + + pIScsiCmd = pImage->aCmdsWaiting[idx]; + + while ( pIScsiCmd + && pIScsiCmd->Itt != Itt) + { + pIScsiCmdPrev = pIScsiCmd; + pIScsiCmd = pIScsiCmd->pNext; + } + + if (pIScsiCmd) + { + if (pIScsiCmdPrev) + { + AssertPtrNull(pIScsiCmd->pNext); + pIScsiCmdPrev->pNext = pIScsiCmd->pNext; + } + else + { + pImage->aCmdsWaiting[idx] = pIScsiCmd->pNext; + AssertPtrNull(pImage->aCmdsWaiting[idx]); + } + pImage->cCmdsWaiting--; + } + + return pIScsiCmd; +} + +/** + * Removes all commands from the table and returns the + * list head + * + * @returns Pointer to the head of the command list. + * @param pImage iSCSI connection to use. + */ +static PISCSICMD iscsiCmdRemoveAll(PISCSIIMAGE pImage) +{ + PISCSICMD pIScsiCmdHead = NULL; + + for (unsigned idx = 0; idx < RT_ELEMENTS(pImage->aCmdsWaiting); idx++) + { + PISCSICMD pHead; + PISCSICMD pTail; + + pHead = pImage->aCmdsWaiting[idx]; + pImage->aCmdsWaiting[idx] = NULL; + + if (pHead) + { + /* Get the tail. */ + pTail = pHead; + while (pTail->pNext) + pTail = pTail->pNext; + + /* Concatenate. */ + pTail->pNext = pIScsiCmdHead; + pIScsiCmdHead = pHead; + } + } + pImage->cCmdsWaiting = 0; + + return pIScsiCmdHead; +} + +/** + * Dumps an iSCSI packet if enabled. + * + * @param pImage The iSCSI image instance data. + * @param paISCSISegs Pointer to the segments array. + * @param cnISCSISegs Number of segments in the array. + * @param rc Status code for this packet. + * @param fRequest Flag whether this is request or response packet. + */ +static void iscsiDumpPacket(PISCSIIMAGE pImage, PISCSIREQ paISCSISegs, unsigned cnISCSISegs, int rc, bool fRequest) +{ + if (pImage->fDumpMalformedPackets) + { + LogRel(("iSCSI{%s}: Dumping %s packet completed with status code %Rrc\n", pImage->pszTargetName, fRequest ? "request" : "response", rc)); + for (unsigned i = 0; i < cnISCSISegs; i++) + { + if (paISCSISegs[i].cbSeg) + { + LogRel(("iSCSI{%s}: Segment %u, size %zu\n" + "%.*Rhxd\n", + pImage->pszTargetName, i, paISCSISegs[i].cbSeg, + paISCSISegs[i].cbSeg, paISCSISegs[i].pcvSeg)); + } + } + } +} + +static int iscsiTransportConnect(PISCSIIMAGE pImage) +{ + int rc; + if (!pImage->pszHostname) + return VERR_NET_DEST_ADDRESS_REQUIRED; + + rc = pImage->pIfNet->pfnClientConnect(pImage->Socket, pImage->pszHostname, pImage->uPort, pImage->uReadTimeout); + if (RT_FAILURE(rc)) + { + if ( rc == VERR_NET_CONNECTION_REFUSED + || rc == VERR_NET_CONNECTION_RESET + || rc == VERR_NET_UNREACHABLE + || rc == VERR_NET_HOST_UNREACHABLE + || rc == VERR_NET_CONNECTION_TIMED_OUT) + { + /* Standardize return value for no connection. */ + rc = VERR_NET_CONNECTION_REFUSED; + } + return rc; + } + + /* Disable Nagle algorithm, we want things to be sent immediately. */ + pImage->pIfNet->pfnSetSendCoalescing(pImage->Socket, false); + + /* Make initiator name and ISID unique on this host. */ + RTNETADDR LocalAddr; + rc = pImage->pIfNet->pfnGetLocalAddress(pImage->Socket, &LocalAddr); + if (RT_FAILURE(rc)) + return rc; + if ( LocalAddr.uPort == RTNETADDR_PORT_NA + || LocalAddr.uPort > 65535) + return VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED; + pImage->ISID &= ~65535ULL; + pImage->ISID |= LocalAddr.uPort; + /* Eliminate the port so that it isn't included below. */ + LocalAddr.uPort = RTNETADDR_PORT_NA; + if (pImage->fAutomaticInitiatorName) + { + if (pImage->pszInitiatorName) + RTStrFree(pImage->pszInitiatorName); + RTStrAPrintf(&pImage->pszInitiatorName, "%s:01:%RTnaddr", + s_iscsiDefaultInitiatorBasename, &LocalAddr); + if (!pImage->pszInitiatorName) + return VERR_NO_MEMORY; + } + LogRel(("iSCSI: connect from initiator %s with source port %u\n", pImage->pszInitiatorName, pImage->ISID & 65535)); + return VINF_SUCCESS; +} + + +static int iscsiTransportClose(PISCSIIMAGE pImage) +{ + int rc; + + LogFlowFunc(("(%s:%d)\n", pImage->pszHostname, pImage->uPort)); + if (iscsiIsClientConnected(pImage)) + { + LogRel(("iSCSI: disconnect from initiator %s with source port %u\n", pImage->pszInitiatorName, pImage->ISID & 65535)); + rc = pImage->pIfNet->pfnClientClose(pImage->Socket); + } + else + rc = VINF_SUCCESS; + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +static int iscsiTransportRead(PISCSIIMAGE pImage, PISCSIRES paResponse, unsigned int cnResponse) +{ + int rc = VINF_SUCCESS; + unsigned int i = 0; + size_t cbToRead, cbActuallyRead, residual, cbSegActual = 0, cbAHSLength, cbDataLength; + char *pDst; + + LogFlowFunc(("cnResponse=%d (%s:%d)\n", cnResponse, pImage->pszHostname, pImage->uPort)); + if (!iscsiIsClientConnected(pImage)) + { + /* Reconnecting makes no sense in this case, as there will be nothing + * to receive. We would just run into a timeout. */ + rc = VERR_BROKEN_PIPE; + } + + if (RT_SUCCESS(rc) && paResponse[0].cbSeg >= ISCSI_BHS_SIZE) + { + cbToRead = 0; + residual = ISCSI_BHS_SIZE; /* Do not read more than the BHS length before the true PDU length is known. */ + cbSegActual = residual; + pDst = (char *)paResponse[i].pvSeg; + uint64_t u64Timeout = RTTimeMilliTS() + pImage->uReadTimeout; + do + { + int64_t cMilliesRemaining = u64Timeout - RTTimeMilliTS(); + if (cMilliesRemaining <= 0) + { + rc = VERR_TIMEOUT; + break; + } + Assert(cMilliesRemaining < 1000000); + rc = pImage->pIfNet->pfnSelectOne(pImage->Socket, cMilliesRemaining); + if (RT_FAILURE(rc)) + break; + rc = pImage->pIfNet->pfnRead(pImage->Socket, pDst, residual, &cbActuallyRead); + if (RT_FAILURE(rc)) + break; + if (cbActuallyRead == 0) + { + /* The other end has closed the connection. */ + iscsiTransportClose(pImage); + pImage->state = ISCSISTATE_FREE; + rc = VERR_NET_CONNECTION_RESET; + break; + } + if (cbToRead == 0) + { + /* Currently reading the BHS. */ + residual -= cbActuallyRead; + pDst += cbActuallyRead; + if (residual <= 40) + { + /* Enough data read to figure out the actual PDU size. */ + uint32_t word1 = RT_N2H_U32(((uint32_t *)(paResponse[0].pvSeg))[1]); + cbAHSLength = (word1 & 0xff000000) >> 24; + cbAHSLength = ((cbAHSLength - 1) | 3) + 1; /* Add padding. */ + cbDataLength = word1 & 0x00ffffff; + cbDataLength = ((cbDataLength - 1) | 3) + 1; /* Add padding. */ + cbToRead = residual + cbAHSLength + cbDataLength; + residual += paResponse[0].cbSeg - ISCSI_BHS_SIZE; + if (residual > cbToRead) + residual = cbToRead; + cbSegActual = ISCSI_BHS_SIZE + cbAHSLength + cbDataLength; + /* Check whether we are already done with this PDU (no payload). */ + if (cbToRead == 0) + break; + } + } + else + { + cbToRead -= cbActuallyRead; + if (cbToRead == 0) + break; + pDst += cbActuallyRead; + residual -= cbActuallyRead; + } + if (residual == 0) + { + i++; + if (i >= cnResponse) + { + /* No space left in receive buffers. */ + rc = VERR_BUFFER_OVERFLOW; + break; + } + pDst = (char *)paResponse[i].pvSeg; + residual = paResponse[i].cbSeg; + if (residual > cbToRead) + residual = cbToRead; + cbSegActual = residual; + } + LogFlowFunc(("cbToRead=%u residual=%u cbSegActual=%u cbActuallRead=%u\n", + cbToRead, residual, cbSegActual, cbActuallyRead)); + } while (true); + } + else + { + if (RT_SUCCESS(rc)) + rc = VERR_BUFFER_OVERFLOW; + } + if (RT_SUCCESS(rc)) + { + paResponse[i].cbSeg = cbSegActual; + for (i++; i < cnResponse; i++) + paResponse[i].cbSeg = 0; + } + + if (RT_UNLIKELY( RT_FAILURE(rc) + && ( rc == VERR_NET_CONNECTION_RESET + || rc == VERR_NET_CONNECTION_ABORTED + || rc == VERR_NET_CONNECTION_RESET_BY_PEER + || rc == VERR_NET_CONNECTION_REFUSED + || rc == VERR_BROKEN_PIPE))) + { + /* Standardize return value for broken connection. */ + rc = VERR_BROKEN_PIPE; + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +static int iscsiTransportWrite(PISCSIIMAGE pImage, PISCSIREQ paRequest, unsigned int cnRequest) +{ + int rc = VINF_SUCCESS; + unsigned int i; + + LogFlowFunc(("cnRequest=%d (%s:%d)\n", cnRequest, pImage->pszHostname, pImage->uPort)); + if (!iscsiIsClientConnected(pImage)) + { + /* Attempt to reconnect if the connection was previously broken. */ + rc = iscsiTransportConnect(pImage); + } + + if (RT_SUCCESS(rc)) + { + /* Construct scatter/gather buffer for entire request, worst case + * needs twice as many entries to allow for padding. */ + unsigned cBuf = 0; + for (i = 0; i < cnRequest; i++) + { + cBuf++; + if (paRequest[i].cbSeg & 3) + cBuf++; + } + Assert(cBuf < ISCSI_SG_SEGMENTS_MAX); + RTSGBUF buf; + RTSGSEG aSeg[ISCSI_SG_SEGMENTS_MAX]; + static char aPad[4] = { 0, 0, 0, 0 }; + RTSgBufInit(&buf, &aSeg[0], cBuf); + unsigned iBuf = 0; + for (i = 0; i < cnRequest; i++) + { + /* Actual data chunk. */ + aSeg[iBuf].pvSeg = (void *)paRequest[i].pcvSeg; + aSeg[iBuf].cbSeg = paRequest[i].cbSeg; + iBuf++; + /* Insert proper padding before the next chunk. */ + if (paRequest[i].cbSeg & 3) + { + aSeg[iBuf].pvSeg = &aPad[0]; + aSeg[iBuf].cbSeg = 4 - (paRequest[i].cbSeg & 3); + iBuf++; + } + } + /* Send out the request, the socket is set to send data immediately, + * avoiding unnecessary delays. */ + rc = pImage->pIfNet->pfnSgWrite(pImage->Socket, &buf); + + } + + if (RT_UNLIKELY( RT_FAILURE(rc) + && ( rc == VERR_NET_CONNECTION_RESET + || rc == VERR_NET_CONNECTION_ABORTED + || rc == VERR_NET_CONNECTION_RESET_BY_PEER + || rc == VERR_NET_CONNECTION_REFUSED + || rc == VERR_BROKEN_PIPE))) + { + /* Standardize return value for broken connection. */ + rc = VERR_BROKEN_PIPE; + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +static int iscsiTransportOpen(PISCSIIMAGE pImage) +{ + int rc = VINF_SUCCESS; + size_t cbHostname = 0; /* shut up gcc */ + const char *pcszPort = NULL; /* shut up gcc */ + char *pszPortEnd; + uint16_t uPort; + + /* Clean up previous connection data. */ + iscsiTransportClose(pImage); + if (pImage->pszHostname) + { + RTMemFree(pImage->pszHostname); + pImage->pszHostname = NULL; + pImage->uPort = 0; + } + + /* Locate the port number via the colon separating the hostname from the port. */ + if (*pImage->pszTargetAddress) + { + if (*pImage->pszTargetAddress != '[') + { + /* Normal hostname or IPv4 dotted decimal. */ + pcszPort = strchr(pImage->pszTargetAddress, ':'); + if (pcszPort != NULL) + { + cbHostname = pcszPort - pImage->pszTargetAddress; + pcszPort++; + } + else + cbHostname = strlen(pImage->pszTargetAddress); + } + else + { + /* IPv6 literal address. Contains colons, so skip to closing square bracket. */ + pcszPort = strchr(pImage->pszTargetAddress, ']'); + if (pcszPort != NULL) + { + pcszPort++; + cbHostname = pcszPort - pImage->pszTargetAddress; + if (*pcszPort == '\0') + pcszPort = NULL; + else if (*pcszPort != ':') + rc = VERR_PARSE_ERROR; + else + pcszPort++; + } + else + rc = VERR_PARSE_ERROR; + } + } + else + rc = VERR_PARSE_ERROR; + + /* Now split address into hostname and port. */ + if (RT_SUCCESS(rc)) + { + pImage->pszHostname = (char *)RTMemAlloc(cbHostname + 1); + if (!pImage->pszHostname) + rc = VERR_NO_MEMORY; + else + { + if (pImage->pszTargetAddress[0] == '[') + memcpy(pImage->pszHostname, pImage->pszTargetAddress + 1, cbHostname); + else + memcpy(pImage->pszHostname, pImage->pszTargetAddress, cbHostname); + pImage->pszHostname[cbHostname] = '\0'; + if (pcszPort != NULL) + { + rc = RTStrToUInt16Ex(pcszPort, &pszPortEnd, 0, &uPort); + /* Note that RT_SUCCESS() macro to check the rc value is not strict enough in this case. */ + if (rc == VINF_SUCCESS && *pszPortEnd == '\0' && uPort != 0) + { + pImage->uPort = uPort; + } + else + { + rc = VERR_PARSE_ERROR; + } + } + else + pImage->uPort = ISCSI_DEFAULT_PORT; + } + } + + if (RT_SUCCESS(rc)) + { + if (!iscsiIsClientConnected(pImage)) + rc = iscsiTransportConnect(pImage); + } + else + { + if (pImage->pszHostname) + { + RTMemFree(pImage->pszHostname); + pImage->pszHostname = NULL; + } + pImage->uPort = 0; + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Returns a human readable version of the given initiator login error detail. + * + * @returns String with the error detail. + * @param u8Detail The detail indicator from the response. + */ +static const char *iscsiGetLoginErrorDetail(uint8_t u8Detail) +{ + const char *pszDetail = NULL; + + switch (u8Detail) + { + case 0x00: + pszDetail = "Miscelleanous iSCSI intiaitor error"; + break; + case 0x01: + pszDetail = "Authentication failure"; + break; + case 0x02: + pszDetail = "Authorization failure"; + break; + case 0x03: + pszDetail = "Not found"; + break; + case 0x04: + pszDetail = "Target removed"; + break; + case 0x05: + pszDetail = "Unsupported version"; + break; + case 0x06: + pszDetail = "Too many connections"; + break; + case 0x07: + pszDetail = "Missing parameter"; + break; + case 0x08: + pszDetail = "Can't include in session"; + break; + case 0x09: + pszDetail = "Session type not supported"; + break; + case 0x0a: + pszDetail = "Session does not exist"; + break; + case 0x0b: + pszDetail = "Invalid request type during login"; + break; + default: + pszDetail = "Unknown status detail"; + } + + return pszDetail; +} + +/** + * Attempts one login attempt to the given target. + * + * @returns VBox status code. + * @retval VINF_TRY_AGAIN when getting redirected and having to start over. + * @retval VERR_TRY_AGAIN in case the connection was lost while receiving a reply + * from the target and the login attempt can be repeated. + * @param pImage The iSCSI connection state to be used. + */ +static int iscsiLogin(PISCSIIMAGE pImage) +{ + int rc = VINF_SUCCESS; + uint32_t itt; + uint32_t csg, nsg, substate; + uint64_t isid_tsih; + uint8_t bBuf[4096]; /* Should be large enough even for large authentication values. */ + size_t cbBuf; + bool transit; + uint8_t pbChallenge[1024]; /* RFC3720 specifies this as maximum. */ + size_t cbChallenge = 0; /* shut up gcc */ + uint8_t bChapIdx = 0; /* (MSC is used uninitialized) */ + uint8_t aResponse[RTMD5HASHSIZE]; + uint32_t cnISCSIReq = 0; + ISCSIREQ aISCSIReq[4]; + uint32_t aReqBHS[12]; + uint32_t cnISCSIRes = 0; + ISCSIRES aISCSIRes[2]; + uint32_t aResBHS[12]; + char *pszNext; + bool fParameterNeg = true; + pImage->cbRecvDataLength = ISCSI_DATA_LENGTH_MAX; + pImage->cbSendDataLength = RT_MIN(ISCSI_DATA_LENGTH_MAX, pImage->cbWriteSplit); + char szMaxDataLength[16]; + RTStrPrintf(szMaxDataLength, sizeof(szMaxDataLength), "%u", ISCSI_DATA_LENGTH_MAX); + ISCSIPARAMETER aParameterNeg[] = + { + { "HeaderDigest", "None", 0 }, + { "DataDigest", "None", 0 }, + { "MaxConnections", "1", 0 }, + { "InitialR2T", "No", 0 }, + { "ImmediateData", "Yes", 0 }, + { "MaxRecvDataSegmentLength", szMaxDataLength, 0 }, + { "MaxBurstLength", szMaxDataLength, 0 }, + { "FirstBurstLength", szMaxDataLength, 0 }, + { "DefaultTime2Wait", "0", 0 }, + { "DefaultTime2Retain", "60", 0 }, + { "DataPDUInOrder", "Yes", 0 }, + { "DataSequenceInOrder", "Yes", 0 }, + { "ErrorRecoveryLevel", "0", 0 }, + { "MaxOutstandingR2T", "1", 0 } + }; + + if (!iscsiIsClientConnected(pImage)) + { + rc = iscsiTransportOpen(pImage); + if (RT_FAILURE(rc)) + return rc; + } + + pImage->state = ISCSISTATE_IN_LOGIN; + pImage->ITT = 1; + pImage->FirstRecvPDU = true; + pImage->CmdSN = 1; + pImage->ExpCmdSN = 0; + pImage->MaxCmdSN = 1; + pImage->ExpStatSN = 0; + + /* + * Send login request to target. + */ + itt = iscsiNewITT(pImage); + csg = 0; + nsg = 0; + substate = 0; + isid_tsih = pImage->ISID << 16; /* TSIH field currently always 0 */ + + do + { + transit = false; + cbBuf = 0; + /* Handle all cases with a single switch statement. */ + switch (csg << 8 | substate) + { + case 0x0000: /* security negotiation, step 0: propose authentication. */ + rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "SessionType", "Normal", 0); + if (RT_FAILURE(rc)) + break; + rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "InitiatorName", pImage->pszInitiatorName, 0); + if (RT_FAILURE(rc)) + break; + rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "TargetName", pImage->pszTargetName, 0); + if (RT_FAILURE(rc)) + break; + if (pImage->pszInitiatorUsername == NULL) + { + /* No authentication. Immediately switch to next phase. */ + rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "AuthMethod", "None", 0); + if (RT_FAILURE(rc)) + break; + nsg = 1; + transit = true; + } + else + rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "AuthMethod", "CHAP,None", 0); + break; + case 0x0001: /* security negotiation, step 1: propose CHAP_MD5 variant. */ + rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "CHAP_A", "5", 0); + break; + case 0x0002: /* security negotiation, step 2: send authentication info. */ + rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "CHAP_N", pImage->pszInitiatorUsername, 0); + if (RT_FAILURE(rc)) + break; + chap_md5_compute_response(aResponse, bChapIdx, pbChallenge, cbChallenge, + pImage->pbInitiatorSecret, pImage->cbInitiatorSecret); + rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, "CHAP_R", (const char *)aResponse, RTMD5HASHSIZE); + if (RT_FAILURE(rc)) + break; + nsg = 1; + transit = true; + break; + case 0x0100: /* login operational negotiation, step 0: set parameters. */ + if (fParameterNeg) + { + for (unsigned i = 0; i < RT_ELEMENTS(aParameterNeg); i++) + { + rc = iscsiTextAddKeyValue(bBuf, sizeof(bBuf), &cbBuf, + aParameterNeg[i].pszParamName, + aParameterNeg[i].pszParamValue, + aParameterNeg[i].cbParamValue); + if (RT_FAILURE(rc)) + break; + } + fParameterNeg = false; + } + + nsg = 3; + transit = true; + break; + case 0x0300: /* full feature phase. */ + default: + /* Should never come here. */ + AssertMsgFailed(("send: Undefined login state %d substate %d\n", csg, substate)); + break; + } + + if (RT_FAILURE(rc)) + break; + + aReqBHS[0] = RT_H2N_U32( ISCSI_IMMEDIATE_DELIVERY_BIT + | (csg << ISCSI_CSG_SHIFT) + | (transit ? (nsg << ISCSI_NSG_SHIFT | ISCSI_TRANSIT_BIT) : 0) + | ISCSI_MY_VERSION /* Minimum version. */ + | (ISCSI_MY_VERSION << 8) /* Maximum version. */ + | ISCSIOP_LOGIN_REQ); /* C=0 */ + aReqBHS[1] = RT_H2N_U32((uint32_t)cbBuf); /* TotalAHSLength=0 */ + aReqBHS[2] = RT_H2N_U32(isid_tsih >> 32); + aReqBHS[3] = RT_H2N_U32(isid_tsih & 0xffffffff); + aReqBHS[4] = itt; + aReqBHS[5] = RT_H2N_U32(1 << 16); /* CID=1,reserved */ + aReqBHS[6] = RT_H2N_U32(pImage->CmdSN); + aReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN); + aReqBHS[8] = 0; /* reserved */ + aReqBHS[9] = 0; /* reserved */ + aReqBHS[10] = 0; /* reserved */ + aReqBHS[11] = 0; /* reserved */ + + cnISCSIReq = 0; + aISCSIReq[cnISCSIReq].pcvSeg = aReqBHS; + aISCSIReq[cnISCSIReq].cbSeg = sizeof(aReqBHS); + cnISCSIReq++; + + aISCSIReq[cnISCSIReq].pcvSeg = bBuf; + aISCSIReq[cnISCSIReq].cbSeg = cbBuf; + cnISCSIReq++; + + rc = iscsiSendPDU(pImage, aISCSIReq, cnISCSIReq, ISCSIPDU_NO_REATTACH); + if (RT_SUCCESS(rc)) + { + ISCSIOPCODE cmd; + ISCSILOGINSTATUSCLASS loginStatusClass; + + cnISCSIRes = 0; + aISCSIRes[cnISCSIRes].pvSeg = aResBHS; + aISCSIRes[cnISCSIRes].cbSeg = sizeof(aResBHS); + cnISCSIRes++; + aISCSIRes[cnISCSIRes].pvSeg = bBuf; + aISCSIRes[cnISCSIRes].cbSeg = sizeof(bBuf); + cnISCSIRes++; + + rc = iscsiRecvPDU(pImage, itt, aISCSIRes, cnISCSIRes, ISCSIPDU_NO_REATTACH); + if (RT_FAILURE(rc)) + { + /* + * We lost connection to the target while receiving the answer, + * start from the beginning. + */ + if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED) + rc = VERR_TRY_AGAIN; + break; + } + + /** @todo collect partial login responses with Continue bit set. */ + Assert(aISCSIRes[0].pvSeg == aResBHS); + Assert(aISCSIRes[0].cbSeg >= ISCSI_BHS_SIZE); + Assert((RT_N2H_U32(aResBHS[0]) & ISCSI_CONTINUE_BIT) == 0); + + cmd = (ISCSIOPCODE)(RT_N2H_U32(aResBHS[0]) & ISCSIOP_MASK); + if (cmd == ISCSIOP_LOGIN_RES) + { + if ((RT_N2H_U32(aResBHS[0]) & 0xff) != ISCSI_MY_VERSION) + { + iscsiTransportClose(pImage); + rc = VERR_PARSE_ERROR; + break; /* Give up immediately, as a RFC violation in version fields is very serious. */ + } + + loginStatusClass = (ISCSILOGINSTATUSCLASS)(RT_N2H_U32(aResBHS[9]) >> 24); + switch (loginStatusClass) + { + case ISCSI_LOGIN_STATUS_CLASS_SUCCESS: + uint32_t targetCSG; + uint32_t targetNSG; + bool targetTransit; + + if (pImage->FirstRecvPDU) + { + pImage->FirstRecvPDU = false; + pImage->ExpStatSN = RT_N2H_U32(aResBHS[6]) + 1; + } + + targetCSG = (RT_N2H_U32(aResBHS[0]) & ISCSI_CSG_MASK) >> ISCSI_CSG_SHIFT; + targetNSG = (RT_N2H_U32(aResBHS[0]) & ISCSI_NSG_MASK) >> ISCSI_NSG_SHIFT; + targetTransit = !!(RT_N2H_U32(aResBHS[0]) & ISCSI_TRANSIT_BIT); + + /* Handle all cases with a single switch statement. */ + switch (csg << 8 | substate) + { + case 0x0000: /* security negotiation, step 0: receive final authentication. */ + rc = iscsiUpdateParameters(pImage, bBuf, aISCSIRes[1].cbSeg); + if (RT_FAILURE(rc)) + break; + + const char *pcszAuthMethod; + + rc = iscsiTextGetKeyValue(bBuf, aISCSIRes[1].cbSeg, "AuthMethod", &pcszAuthMethod); + if (RT_FAILURE(rc)) + { + rc = VERR_PARSE_ERROR; + break; + } + if (strcmp(pcszAuthMethod, "None") == 0) + { + /* Authentication offered, but none required. Skip to operational parameters. */ + csg = 1; + nsg = 1; + transit = true; + substate = 0; + break; + } + else if (strcmp(pcszAuthMethod, "CHAP") == 0 && targetNSG == 0 && !targetTransit) + { + /* CHAP authentication required, continue with next substate. */ + substate++; + break; + } + + /* Unknown auth method or login response PDU headers incorrect. */ + rc = VERR_PARSE_ERROR; + break; + case 0x0001: /* security negotiation, step 1: receive final CHAP variant and challenge. */ + { + rc = iscsiUpdateParameters(pImage, bBuf, aISCSIRes[1].cbSeg); + if (RT_FAILURE(rc)) + break; + + const char *pcszChapAuthMethod; + const char *pcszChapIdxTarget; + const char *pcszChapChallengeStr; + + rc = iscsiTextGetKeyValue(bBuf, aISCSIRes[1].cbSeg, "CHAP_A", &pcszChapAuthMethod); + if (RT_FAILURE(rc)) + { + rc = VERR_PARSE_ERROR; + break; + } + if (strcmp(pcszChapAuthMethod, "5") != 0) + { + rc = VERR_PARSE_ERROR; + break; + } + rc = iscsiTextGetKeyValue(bBuf, aISCSIRes[1].cbSeg, "CHAP_I", &pcszChapIdxTarget); + if (RT_FAILURE(rc)) + { + rc = VERR_PARSE_ERROR; + break; + } + rc = RTStrToUInt8Ex(pcszChapIdxTarget, &pszNext, 0, &bChapIdx); +/** @todo r=bird: Unsafe use of pszNext on failure. The code should probably + * use RTStrToUInt8Full and check for rc != VINF_SUCCESS. */ + if (rc > VINF_SUCCESS || *pszNext != '\0') + { + rc = VERR_PARSE_ERROR; + break; + } + rc = iscsiTextGetKeyValue(bBuf, aISCSIRes[1].cbSeg, "CHAP_C", &pcszChapChallengeStr); + if (RT_FAILURE(rc)) + { + rc = VERR_PARSE_ERROR; + break; + } + cbChallenge = sizeof(pbChallenge); + rc = iscsiStrToBinary(pcszChapChallengeStr, pbChallenge, &cbChallenge); + if (RT_FAILURE(rc)) + break; + substate++; + transit = true; + break; + } + case 0x0002: /* security negotiation, step 2: check authentication success. */ + rc = iscsiUpdateParameters(pImage, bBuf, aISCSIRes[1].cbSeg); + if (RT_FAILURE(rc)) + break; + + if (targetCSG == 0 && targetNSG == 1 && targetTransit) + { + /* Target wants to continue in login operational state, authentication success. */ + csg = 1; + nsg = 3; + substate = 0; + break; + } + rc = VERR_PARSE_ERROR; + break; + case 0x0100: /* login operational negotiation, step 0: check results. */ + rc = iscsiUpdateParameters(pImage, bBuf, aISCSIRes[1].cbSeg); + if (RT_FAILURE(rc)) + break; + + if (targetCSG == 1 && targetNSG == 3 && targetTransit) + { + /* Target wants to continue in full feature phase, login finished. */ + csg = 3; + nsg = 3; + substate = 0; + break; + } + else if (targetCSG == 1 && (targetNSG == 1 || !targetTransit)) + { + /* Target wants to negotiate certain parameters and + * stay in login operational negotiation. */ + csg = 1; + nsg = 3; + substate = 0; + break; + } + rc = VERR_PARSE_ERROR; + break; + case 0x0300: /* full feature phase. */ + default: + AssertMsgFailed(("recv: Undefined login state %d substate %d\n", csg, substate)); + rc = VERR_PARSE_ERROR; + break; + } + break; + case ISCSI_LOGIN_STATUS_CLASS_REDIRECTION: + const char *pcszTargetRedir; + + /* Target has moved to some other location, as indicated in the TargetAddress key. */ + rc = iscsiTextGetKeyValue(bBuf, aISCSIRes[1].cbSeg, "TargetAddress", &pcszTargetRedir); + if (RT_FAILURE(rc)) + { + rc = VERR_PARSE_ERROR; + break; + } + if (pImage->pszTargetAddress) + RTMemFree(pImage->pszTargetAddress); + { + size_t cb = strlen(pcszTargetRedir) + 1; + pImage->pszTargetAddress = (char *)RTMemAlloc(cb); + if (!pImage->pszTargetAddress) + { + rc = VERR_NO_MEMORY; + break; + } + memcpy(pImage->pszTargetAddress, pcszTargetRedir, cb); + } + rc = VINF_TRY_AGAIN; + break; + case ISCSI_LOGIN_STATUS_CLASS_INITIATOR_ERROR: + { + LogRel(("iSCSI: login to target failed with: %s\n", + iscsiGetLoginErrorDetail((RT_N2H_U32(aResBHS[9]) >> 16) & 0xff))); + iscsiTransportClose(pImage); + rc = VERR_IO_GEN_FAILURE; + break; + } + case ISCSI_LOGIN_STATUS_CLASS_TARGET_ERROR: + iscsiTransportClose(pImage); + rc = VINF_EOF; + break; + default: + rc = VERR_PARSE_ERROR; + } + + if (RT_FAILURE(rc) || rc == VINF_TRY_AGAIN) + break; + + if (csg == 3) + { + /* + * Finished login, continuing with Full Feature Phase. + */ + rc = VINF_SUCCESS; + break; + } + } + else + AssertMsgFailed(("%s: ignoring unexpected PDU with first word = %#08x\n", __FUNCTION__, RT_N2H_U32(aResBHS[0]))); + } + else + break; + } while (true); + + if ( RT_FAILURE(rc) + && rc != VERR_TRY_AGAIN) + { + /* + * Dump the last request and response of we are supposed to do so and there is a request + * or response. + */ + if (cnISCSIReq) + iscsiDumpPacket(pImage, aISCSIReq, cnISCSIReq, VINF_SUCCESS, true /* fRequest */); + + if (cnISCSIRes) + iscsiDumpPacket(pImage, (PISCSIREQ)aISCSIRes, cnISCSIRes, rc, false /* fRequest */); + + /* + * Close connection to target. + */ + iscsiTransportClose(pImage); + pImage->state = ISCSISTATE_FREE; + } + else if (rc == VINF_SUCCESS) + pImage->state = ISCSISTATE_NORMAL; + + return rc; +} + +/** + * Attach to an iSCSI target. Performs all operations necessary to enter + * Full Feature Phase. + * + * @returns VBox status code. + * @param pvUser The iSCSI connection state to be used as opaque user data. + */ +static DECLCALLBACK(int) iscsiAttach(void *pvUser) +{ + int rc = VINF_SUCCESS; + unsigned cRetries = 5; + PISCSIIMAGE pImage = (PISCSIIMAGE)pvUser; + + LogFlowFunc(("entering\n")); + + Assert(pImage->state == ISCSISTATE_FREE); + + /* + * If there were too many logins without any successful I/O just fail + * and assume the target is not working properly. + */ + if (ASMAtomicReadU32(&pImage->cLoginsSinceIo) == 3) + return VERR_BROKEN_PIPE; + + RTSemMutexRequest(pImage->Mutex, RT_INDEFINITE_WAIT); + + /* Make 100% sure the connection isn't reused for a new login. */ + iscsiTransportClose(pImage); + + /* Try to log in a few number of times. */ + while (cRetries > 0) + { + rc = iscsiLogin(pImage); + if (rc == VINF_SUCCESS) /* Login succeeded, continue with full feature phase. */ + break; + else if (rc == VERR_TRY_AGAIN) /* Lost connection during receive. */ + cRetries--; + else if (RT_FAILURE(rc)) + break; + else /* For redirects try again. */ + AssertMsg(rc == VINF_TRY_AGAIN, ("Unexpected status code %Rrc\n", rc)); + } + + if (RT_SUCCESS(rc)) + ASMAtomicIncU32(&pImage->cLoginsSinceIo); + + RTSemMutexRelease(pImage->Mutex); + + LogFlowFunc(("returning %Rrc\n", rc)); + LogRel(("iSCSI: login to target %s %s (%Rrc)\n", pImage->pszTargetName, RT_SUCCESS(rc) ? "successful" : "failed", rc)); + return rc; +} + + +/** + * Detach from an iSCSI target. + * + * @returns VBox status code. + * @param pvUser The iSCSI connection state to be used as opaque user data. + */ +static DECLCALLBACK(int) iscsiDetach(void *pvUser) +{ + int rc; + uint32_t itt; + uint32_t cnISCSIReq = 0; + ISCSIREQ aISCSIReq[4]; + uint32_t aReqBHS[12]; + PISCSIIMAGE pImage = (PISCSIIMAGE)pvUser; + + LogFlowFunc(("entering\n")); + + RTSemMutexRequest(pImage->Mutex, RT_INDEFINITE_WAIT); + + if (pImage->state != ISCSISTATE_FREE && pImage->state != ISCSISTATE_IN_LOGOUT) + { + pImage->state = ISCSISTATE_IN_LOGOUT; + + /* + * Send logout request to target. + */ + itt = iscsiNewITT(pImage); + aReqBHS[0] = RT_H2N_U32(ISCSI_FINAL_BIT | ISCSIOP_LOGOUT_REQ); /* I=0,F=1,Reason=close session */ + aReqBHS[1] = RT_H2N_U32(0); /* TotalAHSLength=0,DataSementLength=0 */ + aReqBHS[2] = 0; /* reserved */ + aReqBHS[3] = 0; /* reserved */ + aReqBHS[4] = itt; + aReqBHS[5] = 0; /* reserved */ + aReqBHS[6] = RT_H2N_U32(pImage->CmdSN); + aReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN); + aReqBHS[8] = 0; /* reserved */ + aReqBHS[9] = 0; /* reserved */ + aReqBHS[10] = 0; /* reserved */ + aReqBHS[11] = 0; /* reserved */ + pImage->CmdSN++; + + aISCSIReq[cnISCSIReq].pcvSeg = aReqBHS; + aISCSIReq[cnISCSIReq].cbSeg = sizeof(aReqBHS); + cnISCSIReq++; + + rc = iscsiSendPDU(pImage, aISCSIReq, cnISCSIReq, ISCSIPDU_NO_REATTACH); + if (RT_SUCCESS(rc)) + { + /* + * Read logout response from target. + */ + ISCSIRES aISCSIRes; + uint32_t aResBHS[12]; + + aISCSIRes.pvSeg = aResBHS; + aISCSIRes.cbSeg = sizeof(aResBHS); + rc = iscsiRecvPDU(pImage, itt, &aISCSIRes, 1, ISCSIPDU_NO_REATTACH); + if (RT_SUCCESS(rc)) + { + if (RT_N2H_U32(aResBHS[0]) != (ISCSI_FINAL_BIT | ISCSIOP_LOGOUT_RES)) + AssertMsgFailed(("iSCSI Logout response invalid\n")); + } + else + AssertMsgFailed(("iSCSI Logout response error, rc=%Rrc\n", rc)); + } + else + AssertMsgFailed(("Could not send iSCSI Logout request, rc=%Rrc\n", rc)); + } + + if (pImage->state != ISCSISTATE_FREE) + { + /* + * Close connection to target. + */ + rc = iscsiTransportClose(pImage); + if (RT_FAILURE(rc)) + AssertMsgFailed(("Could not close connection to target, rc=%Rrc\n", rc)); + } + + pImage->state = ISCSISTATE_FREE; + + RTSemMutexRelease(pImage->Mutex); + + LogFlowFunc(("leaving\n")); + LogRel(("iSCSI: logout to target %s\n", pImage->pszTargetName)); + return VINF_SUCCESS; +} + + +/** + * Perform a command on an iSCSI target. Target must be already in + * Full Feature Phase. + * + * @returns VBox status code. + * @param pImage The iSCSI connection state to be used. + * @param pRequest Command descriptor. Contains all information about + * the command, its transfer directions and pointers + * to the buffer(s) used for transferring data and + * status information. + */ +static int iscsiCommand(PISCSIIMAGE pImage, PSCSIREQ pRequest) +{ + int rc; + uint32_t itt; + uint32_t cbData; + uint32_t cnISCSIReq = 0; + ISCSIREQ aISCSIReq[4]; + uint32_t aReqBHS[12]; + + uint32_t *pDst = NULL; + size_t cbBufLength; + uint32_t aStatus[256]; /**< Plenty of buffer for status information. */ + uint32_t ExpDataSN = 0; + bool final = false; + + + LogFlowFunc(("entering, CmdSN=%d\n", pImage->CmdSN)); + + Assert(pRequest->enmXfer != SCSIXFER_TO_FROM_TARGET); /**< @todo not yet supported, would require AHS. */ + Assert(pRequest->cbI2TData <= 0xffffff); /* larger transfers would require R2T support. */ + Assert(pRequest->cbCDB <= 16); /* would cause buffer overrun below. */ + + /* If not in normal state, then the transport connection was dropped. Try + * to reestablish by logging in, the target might be responsive again. */ + if (pImage->state == ISCSISTATE_FREE) + rc = iscsiAttach(pImage); + + /* If still not in normal state, then the underlying transport connection + * cannot be established. Get out before bad things happen (and make + * sure the caller suspends the VM again). */ + if (pImage->state == ISCSISTATE_NORMAL) + { + /* + * Send SCSI command to target with all I2T data included. + */ + cbData = 0; + if (pRequest->enmXfer == SCSIXFER_FROM_TARGET) + cbData = (uint32_t)pRequest->cbT2IData; + else + cbData = (uint32_t)pRequest->cbI2TData; + + RTSemMutexRequest(pImage->Mutex, RT_INDEFINITE_WAIT); + + itt = iscsiNewITT(pImage); + memset(aReqBHS, 0, sizeof(aReqBHS)); + aReqBHS[0] = RT_H2N_U32( ISCSI_FINAL_BIT | ISCSI_TASK_ATTR_SIMPLE | ISCSIOP_SCSI_CMD + | (pRequest->enmXfer << 21)); /* I=0,F=1,Attr=Simple */ + aReqBHS[1] = RT_H2N_U32(0x00000000 | ((uint32_t)pRequest->cbI2TData & 0xffffff)); /* TotalAHSLength=0 */ + aReqBHS[2] = RT_H2N_U32(pImage->LUN >> 32); + aReqBHS[3] = RT_H2N_U32(pImage->LUN & 0xffffffff); + aReqBHS[4] = itt; + aReqBHS[5] = RT_H2N_U32(cbData); + aReqBHS[6] = RT_H2N_U32(pImage->CmdSN); + aReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN); + memcpy(aReqBHS + 8, pRequest->abCDB, pRequest->cbCDB); + pImage->CmdSN++; + + aISCSIReq[cnISCSIReq].pcvSeg = aReqBHS; + aISCSIReq[cnISCSIReq].cbSeg = sizeof(aReqBHS); + cnISCSIReq++; + + if ( pRequest->enmXfer == SCSIXFER_TO_TARGET + || pRequest->enmXfer == SCSIXFER_TO_FROM_TARGET) + { + Assert(pRequest->cI2TSegs == 1); + aISCSIReq[cnISCSIReq].pcvSeg = pRequest->paI2TSegs[0].pvSeg; + aISCSIReq[cnISCSIReq].cbSeg = pRequest->paI2TSegs[0].cbSeg; /* Padding done by transport. */ + cnISCSIReq++; + } + + rc = iscsiSendPDU(pImage, aISCSIReq, cnISCSIReq, ISCSIPDU_DEFAULT); + if (RT_SUCCESS(rc)) + { + /* Place SCSI request in queue. */ + pImage->paCurrReq = aISCSIReq; + pImage->cnCurrReq = cnISCSIReq; + + /* + * Read SCSI response/data in PDUs from target. + */ + if ( pRequest->enmXfer == SCSIXFER_FROM_TARGET + || pRequest->enmXfer == SCSIXFER_TO_FROM_TARGET) + { + Assert(pRequest->cT2ISegs == 1); + pDst = (uint32_t *)pRequest->paT2ISegs[0].pvSeg; + cbBufLength = pRequest->paT2ISegs[0].cbSeg; + } + else + cbBufLength = 0; + + do + { + uint32_t cnISCSIRes = 0; + ISCSIRES aISCSIRes[4]; + uint32_t aResBHS[12]; + + aISCSIRes[cnISCSIRes].pvSeg = aResBHS; + aISCSIRes[cnISCSIRes].cbSeg = sizeof(aResBHS); + cnISCSIRes++; + if (cbBufLength != 0 && + ( pRequest->enmXfer == SCSIXFER_FROM_TARGET + || pRequest->enmXfer == SCSIXFER_TO_FROM_TARGET)) + { + aISCSIRes[cnISCSIRes].pvSeg = pDst; + aISCSIRes[cnISCSIRes].cbSeg = cbBufLength; + cnISCSIRes++; + } + /* Always reserve space for the status - it's impossible to tell + * beforehand whether this will be the final PDU or not. */ + aISCSIRes[cnISCSIRes].pvSeg = aStatus; + aISCSIRes[cnISCSIRes].cbSeg = sizeof(aStatus); + cnISCSIRes++; + + rc = iscsiRecvPDU(pImage, itt, aISCSIRes, cnISCSIRes, ISCSIPDU_DEFAULT); + if (RT_FAILURE(rc)) + break; + + final = !!(RT_N2H_U32(aResBHS[0]) & ISCSI_FINAL_BIT); + ISCSIOPCODE cmd = (ISCSIOPCODE)(RT_N2H_U32(aResBHS[0]) & ISCSIOP_MASK); + if (cmd == ISCSIOP_SCSI_RES) + { + /* This is the final PDU which delivers the status (and may be omitted if + * the last Data-In PDU included successful completion status). Note + * that ExpStatSN has been bumped already in iscsiRecvPDU. */ + if (!final || ((RT_N2H_U32(aResBHS[0]) & 0x0000ff00) != 0) || (RT_N2H_U32(aResBHS[6]) != pImage->ExpStatSN - 1)) + { + /* SCSI Response in the wrong place or with a (target) failure. */ + rc = VERR_PARSE_ERROR; + break; + } + /* The following is a bit tricky, as in error situations we may + * get the status only instead of the result data plus optional + * status. Thus the status may have ended up partially in the + * data area. */ + pRequest->status = RT_N2H_U32(aResBHS[0]) & 0x000000ff; + cbData = RT_N2H_U32(aResBHS[1]) & 0x00ffffff; + if (cbData >= 2) + { + uint32_t cbStat = RT_N2H_U32(((uint32_t *)aISCSIRes[1].pvSeg)[0]) >> 16; + if (cbStat + 2 > cbData) + { + rc = VERR_BUFFER_OVERFLOW; + break; + } + /* Truncate sense data if it doesn't fit into the buffer. */ + pRequest->cbSense = RT_MIN(cbStat, pRequest->cbSense); + memcpy(pRequest->abSense, + ((const char *)aISCSIRes[1].pvSeg) + 2, + RT_MIN(aISCSIRes[1].cbSeg - 2, pRequest->cbSense)); + if ( cnISCSIRes > 2 && aISCSIRes[2].cbSeg + && (ssize_t)pRequest->cbSense - aISCSIRes[1].cbSeg + 2 > 0) + { + memcpy((char *)pRequest->abSense + aISCSIRes[1].cbSeg - 2, + aISCSIRes[2].pvSeg, + pRequest->cbSense - aISCSIRes[1].cbSeg + 2); + } + } + else if (cbData == 1) + { + rc = VERR_PARSE_ERROR; + break; + } + else + pRequest->cbSense = 0; + break; + } + else if (cmd == ISCSIOP_SCSI_DATA_IN) + { + /* A Data-In PDU carries some data that needs to be added to the received + * data in response to the command. There may be both partial and complete + * Data-In PDUs, so collect data until the status is included or the status + * is sent in a separate SCSI Result frame (see above). */ + if (final && aISCSIRes[2].cbSeg != 0) + { + /* The received PDU is partially stored in the buffer for status. + * Must not happen under normal circumstances and is a target error. */ + rc = VERR_BUFFER_OVERFLOW; + break; + } + uint32_t len = RT_N2H_U32(aResBHS[1]) & 0x00ffffff; + pDst = (uint32_t *)((char *)pDst + len); + cbBufLength -= len; + ExpDataSN++; + if (final && (RT_N2H_U32(aResBHS[0]) & ISCSI_STATUS_BIT) != 0) + { + pRequest->status = RT_N2H_U32(aResBHS[0]) & 0x000000ff; + pRequest->cbSense = 0; + break; + } + } + else + { + rc = VERR_PARSE_ERROR; + break; + } + } while (true); + + /* Remove SCSI request from queue. */ + pImage->paCurrReq = NULL; + pImage->cnCurrReq = 0; + } + + if (rc == VERR_TIMEOUT) + { + /* Drop connection in case the target plays dead. Much better than + * delaying the next requests until the timed out command actually + * finishes. Also keep in mind that command shouldn't take longer than + * about 30-40 seconds, or the guest will lose its patience. */ + iscsiTransportClose(pImage); + pImage->state = ISCSISTATE_FREE; + rc = VERR_BROKEN_PIPE; + } + RTSemMutexRelease(pImage->Mutex); + } + else + rc = VERR_NET_CONNECTION_REFUSED; + + if (RT_SUCCESS(rc)) + ASMAtomicWriteU32(&pImage->cLoginsSinceIo, 0); + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * Generate a new Initiator Task Tag. + * + * @returns Initiator Task Tag. + * @param pImage The iSCSI connection state to be used. + */ +static uint32_t iscsiNewITT(PISCSIIMAGE pImage) +{ + uint32_t next_itt; + + next_itt = pImage->ITT++; + if (pImage->ITT == ISCSI_TASK_TAG_RSVD) + pImage->ITT = 0; + return RT_H2N_U32(next_itt); +} + + +/** + * Send an iSCSI request. The request can consist of several segments, which + * are padded to 4 byte boundaries and concatenated. + * + * @returns VBOX status + * @param pImage The iSCSI connection state to be used. + * @param paReq Pointer to array of iSCSI request sections. + * @param cnReq Number of valid iSCSI request sections in the array. + * @param uFlags Flags controlling the exact send semantics. + */ +static int iscsiSendPDU(PISCSIIMAGE pImage, PISCSIREQ paReq, uint32_t cnReq, + uint32_t uFlags) +{ + int rc = VINF_SUCCESS; + /** @todo return VERR_VD_ISCSI_INVALID_STATE in the appropriate situations, + * needs cleaning up of timeout/disconnect handling a bit, as otherwise + * too many incorrect errors are signalled. */ + Assert(cnReq >= 1); + Assert(paReq[0].cbSeg >= ISCSI_BHS_SIZE); + + for (uint32_t i = 0; i < pImage->cISCSIRetries; i++) + { + rc = iscsiTransportWrite(pImage, paReq, cnReq); + if (RT_SUCCESS(rc)) + break; + if ( (uFlags & ISCSIPDU_NO_REATTACH) + || (rc != VERR_BROKEN_PIPE && rc != VERR_NET_CONNECTION_REFUSED)) + break; + /* No point in reestablishing the connection for a logout */ + if (pImage->state == ISCSISTATE_IN_LOGOUT) + break; + RTThreadSleep(500); + if (pImage->state != ISCSISTATE_IN_LOGIN) + { + /* Attempt to re-login when a connection fails, but only when not + * currently logging in. */ + rc = iscsiAttach(pImage); + if (RT_FAILURE(rc)) + break; + } + } + return rc; +} + + +/** + * Wait for an iSCSI response with a matching Initiator Target Tag. The response is + * split into several segments, as requested by the caller-provided buffer specification. + * Remember that the response can be split into several PDUs by the sender, so make + * sure that all parts are collected and processed appropriately by the caller. + * + * @returns VBOX status + * @param pImage The iSCSI connection state to be used. + * @param itt The initiator task tag. + * @param paRes Pointer to array of iSCSI response sections. + * @param cnRes Number of valid iSCSI response sections in the array. + * @param fRecvFlags PDU receive flags. + */ +static int iscsiRecvPDU(PISCSIIMAGE pImage, uint32_t itt, PISCSIRES paRes, uint32_t cnRes, + uint32_t fRecvFlags) +{ + int rc = VINF_SUCCESS; + ISCSIRES aResBuf; + + for (uint32_t i = 0; i < pImage->cISCSIRetries; i++) + { + aResBuf.pvSeg = pImage->pvRecvPDUBuf; + aResBuf.cbSeg = pImage->cbRecvPDUBuf; + rc = iscsiTransportRead(pImage, &aResBuf, 1); + if (RT_FAILURE(rc)) + { + if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED) + { + /* No point in reestablishing the connection for a logout */ + if (pImage->state == ISCSISTATE_IN_LOGOUT) + break; + /* Connection broken while waiting for a response - wait a while and + * try to restart by re-sending the original request (if any). + * This also handles the connection reestablishment (login etc.). */ + RTThreadSleep(500); + if ( pImage->state != ISCSISTATE_IN_LOGIN + && !(fRecvFlags & ISCSIPDU_NO_REATTACH)) + { + /* Attempt to re-login when a connection fails, but only when not + * currently logging in. */ + rc = iscsiAttach(pImage); + if (RT_FAILURE(rc)) + break; + + if (pImage->paCurrReq != NULL) + { + rc = iscsiSendPDU(pImage, pImage->paCurrReq, pImage->cnCurrReq, ISCSIPDU_DEFAULT); + if (RT_FAILURE(rc)) + break; + } + } + } + else + { + /* Signal other errors (VERR_BUFFER_OVERFLOW etc.) to the caller. */ + break; + } + } + else + { + ISCSIOPCODE cmd; + const uint32_t *pcvResSeg = (const uint32_t *)aResBuf.pvSeg; + + /* Check whether the received PDU is valid, and update the internal state of + * the iSCSI connection/session. */ + rc = iscsiValidatePDU(&aResBuf, 1); + if (RT_FAILURE(rc)) + { + iscsiDumpPacket(pImage, (PISCSIREQ)&aResBuf, 1, rc, false /* fRequest */); + continue; + } + cmd = (ISCSIOPCODE)(RT_N2H_U32(pcvResSeg[0]) & ISCSIOP_MASK); + switch (cmd) + { + case ISCSIOP_SCSI_RES: + case ISCSIOP_SCSI_TASKMGMT_RES: + case ISCSIOP_SCSI_DATA_IN: + case ISCSIOP_R2T: + case ISCSIOP_ASYN_MSG: + case ISCSIOP_TEXT_RES: + case ISCSIOP_LOGIN_RES: + case ISCSIOP_LOGOUT_RES: + case ISCSIOP_REJECT: + case ISCSIOP_NOP_IN: + if (serial_number_less(pImage->MaxCmdSN, RT_N2H_U32(pcvResSeg[8]))) + pImage->MaxCmdSN = RT_N2H_U32(pcvResSeg[8]); + if (serial_number_less(pImage->ExpCmdSN, RT_N2H_U32(pcvResSeg[7]))) + pImage->ExpCmdSN = RT_N2H_U32(pcvResSeg[7]); + break; + default: + rc = VERR_PARSE_ERROR; + iscsiDumpPacket(pImage, (PISCSIREQ)&aResBuf, 1, rc, false /* fRequest */); + } + if (RT_FAILURE(rc)) + continue; + if ( !pImage->FirstRecvPDU + && (cmd != ISCSIOP_SCSI_DATA_IN || (RT_N2H_U32(pcvResSeg[0]) & ISCSI_STATUS_BIT)) + && ( cmd != ISCSIOP_LOGIN_RES + || (ISCSILOGINSTATUSCLASS)((RT_N2H_U32(pcvResSeg[9]) >> 24) == ISCSI_LOGIN_STATUS_CLASS_SUCCESS))) + { + if (pImage->ExpStatSN == RT_N2H_U32(pcvResSeg[6])) + { + /* StatSN counter is not advanced on R2T and on a target SN update NOP-In. */ + if ( (cmd != ISCSIOP_R2T) + && ((cmd != ISCSIOP_NOP_IN) || (RT_N2H_U32(pcvResSeg[4]) != ISCSI_TASK_TAG_RSVD))) + pImage->ExpStatSN++; + } + else + { + rc = VERR_PARSE_ERROR; + iscsiDumpPacket(pImage, (PISCSIREQ)&aResBuf, 1, rc, false /* fRequest */); + continue; + } + } + /* Finally check whether the received PDU matches what the caller wants. */ + if ( itt == pcvResSeg[4] + && itt != ISCSI_TASK_TAG_RSVD) + { + /* Copy received PDU (one segment) to caller-provided buffers. */ + uint32_t j; + size_t cbSeg; + const uint8_t *pSrc; + + pSrc = (const uint8_t *)aResBuf.pvSeg; + cbSeg = aResBuf.cbSeg; + for (j = 0; j < cnRes; j++) + { + if (cbSeg > paRes[j].cbSeg) + { + memcpy(paRes[j].pvSeg, pSrc, paRes[j].cbSeg); + pSrc += paRes[j].cbSeg; + cbSeg -= paRes[j].cbSeg; + } + else + { + memcpy(paRes[j].pvSeg, pSrc, cbSeg); + paRes[j].cbSeg = cbSeg; + cbSeg = 0; + break; + } + } + if (cbSeg != 0) + { + rc = VERR_BUFFER_OVERFLOW; + break; + } + for (j++; j < cnRes; j++) + paRes[j].cbSeg = 0; + break; + } + else if ( cmd == ISCSIOP_NOP_IN + && RT_N2H_U32(pcvResSeg[5]) != ISCSI_TASK_TAG_RSVD) + { + uint32_t cnISCSIReq; + ISCSIREQ aISCSIReq[4]; + uint32_t aReqBHS[12]; + + aReqBHS[0] = RT_H2N_U32(ISCSI_IMMEDIATE_DELIVERY_BIT | ISCSI_FINAL_BIT | ISCSIOP_NOP_OUT); + aReqBHS[1] = RT_H2N_U32(0); /* TotalAHSLength=0,DataSementLength=0 */ + aReqBHS[2] = pcvResSeg[2]; /* copy LUN from NOP-In */ + aReqBHS[3] = pcvResSeg[3]; /* copy LUN from NOP-In */ + aReqBHS[4] = RT_H2N_U32(ISCSI_TASK_TAG_RSVD); /* ITT, reply */ + aReqBHS[5] = pcvResSeg[5]; /* copy TTT from NOP-In */ + aReqBHS[6] = RT_H2N_U32(pImage->CmdSN); + aReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN); + aReqBHS[8] = 0; /* reserved */ + aReqBHS[9] = 0; /* reserved */ + aReqBHS[10] = 0; /* reserved */ + aReqBHS[11] = 0; /* reserved */ + + cnISCSIReq = 0; + aISCSIReq[cnISCSIReq].pcvSeg = aReqBHS; + aISCSIReq[cnISCSIReq].cbSeg = sizeof(aReqBHS); + cnISCSIReq++; + + iscsiSendPDU(pImage, aISCSIReq, cnISCSIReq, ISCSIPDU_NO_REATTACH); + /* Break if the caller wanted to process the NOP-in only. */ + if (itt == ISCSI_TASK_TAG_RSVD) + break; + } + } + } + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Reset the PDU buffer + * + * @param pImage The iSCSI connection state to be used. + */ +static void iscsiRecvPDUReset(PISCSIIMAGE pImage) +{ + pImage->cbRecvPDUResidual = ISCSI_BHS_SIZE; + pImage->fRecvPDUBHS = true; + pImage->pbRecvPDUBufCur = (uint8_t *)pImage->pvRecvPDUBuf; +} + +static void iscsiPDUTxAdd(PISCSIIMAGE pImage, PISCSIPDUTX pIScsiPDUTx, bool fFront) +{ + if (!fFront) + { + /* Insert PDU at the tail of the list. */ + if (!pImage->pIScsiPDUTxHead) + pImage->pIScsiPDUTxHead = pIScsiPDUTx; + else + pImage->pIScsiPDUTxTail->pNext = pIScsiPDUTx; + pImage->pIScsiPDUTxTail = pIScsiPDUTx; + } + else + { + /* Insert PDU at the beginning of the list. */ + pIScsiPDUTx->pNext = pImage->pIScsiPDUTxHead; + pImage->pIScsiPDUTxHead = pIScsiPDUTx; + if (!pImage->pIScsiPDUTxTail) + pImage->pIScsiPDUTxTail = pIScsiPDUTx; + } +} + +/** + * Receives a PDU in a non blocking way. + * + * @returns VBOX status code. + * @param pImage The iSCSI connection state to be used. + */ +static int iscsiRecvPDUAsync(PISCSIIMAGE pImage) +{ + size_t cbActuallyRead = 0; + int rc = VINF_SUCCESS; + + LogFlowFunc(("pImage=%#p\n", pImage)); + + /* Check if we are in the middle of a PDU receive. */ + if (pImage->cbRecvPDUResidual == 0) + { + /* + * We are receiving a new PDU, don't read more than the BHS initially + * until we know the real size of the PDU. + */ + iscsiRecvPDUReset(pImage); + LogFlow(("Receiving new PDU\n")); + } + + rc = pImage->pIfNet->pfnReadNB(pImage->Socket, pImage->pbRecvPDUBufCur, + pImage->cbRecvPDUResidual, &cbActuallyRead); + if (RT_SUCCESS(rc) && cbActuallyRead == 0) + rc = VERR_BROKEN_PIPE; + + if (RT_SUCCESS(rc)) + { + LogFlow(("Received %zu bytes\n", cbActuallyRead)); + pImage->cbRecvPDUResidual -= cbActuallyRead; + pImage->pbRecvPDUBufCur += cbActuallyRead; + + /* Check if we received everything we wanted. */ + if ( !pImage->cbRecvPDUResidual + && pImage->fRecvPDUBHS) + { + size_t cbAHSLength, cbDataLength; + + /* If we were reading the BHS first get the actual PDU size now. */ + uint32_t word1 = RT_N2H_U32(((uint32_t *)(pImage->pvRecvPDUBuf))[1]); + cbAHSLength = (word1 & 0xff000000) >> 24; + cbAHSLength = ((cbAHSLength - 1) | 3) + 1; /* Add padding. */ + cbDataLength = word1 & 0x00ffffff; + cbDataLength = ((cbDataLength - 1) | 3) + 1; /* Add padding. */ + pImage->cbRecvPDUResidual = cbAHSLength + cbDataLength; + pImage->fRecvPDUBHS = false; /* Start receiving the rest of the PDU. */ + } + + if (!pImage->cbRecvPDUResidual) + { + /* We received the complete PDU with or without any payload now. */ + LogFlow(("Received complete PDU\n")); + ISCSIRES aResBuf; + aResBuf.pvSeg = pImage->pvRecvPDUBuf; + aResBuf.cbSeg = pImage->cbRecvPDUBuf; + rc = iscsiRecvPDUProcess(pImage, &aResBuf, 1); + } + } + else + LogFlowFunc(("Reading from the socket returned with rc=%Rrc\n", rc)); + + return rc; +} + +static int iscsiSendPDUAsync(PISCSIIMAGE pImage) +{ + size_t cbSent = 0; + int rc = VINF_SUCCESS; + + LogFlowFunc(("pImage=%#p\n", pImage)); + + do + { + /* + * If there is no PDU active, get the first one from the list. + * Check that we are allowed to transfer the PDU by comparing the + * command sequence number and the maximum sequence number allowed by the target. + */ + if (!pImage->pIScsiPDUTxCur) + { + if ( !pImage->pIScsiPDUTxHead + || serial_number_greater(pImage->pIScsiPDUTxHead->CmdSN, pImage->MaxCmdSN)) + break; + + pImage->pIScsiPDUTxCur = pImage->pIScsiPDUTxHead; + pImage->pIScsiPDUTxHead = pImage->pIScsiPDUTxCur->pNext; + if (!pImage->pIScsiPDUTxHead) + pImage->pIScsiPDUTxTail = NULL; + } + + /* Send as much as we can. */ + rc = pImage->pIfNet->pfnSgWriteNB(pImage->Socket, &pImage->pIScsiPDUTxCur->SgBuf, &cbSent); + LogFlow(("SgWriteNB returned rc=%Rrc cbSent=%zu\n", rc, cbSent)); + if (RT_SUCCESS(rc)) + { + LogFlow(("Sent %zu bytes for PDU %#p\n", cbSent, pImage->pIScsiPDUTxCur)); + pImage->pIScsiPDUTxCur->cbSgLeft -= cbSent; + RTSgBufAdvance(&pImage->pIScsiPDUTxCur->SgBuf, cbSent); + if (!pImage->pIScsiPDUTxCur->cbSgLeft) + { + /* PDU completed, free it and place the command on the waiting for response list. */ + if (pImage->pIScsiPDUTxCur->pIScsiCmd) + { + LogFlow(("Sent complete PDU, placing on waiting list\n")); + iscsiCmdInsert(pImage, pImage->pIScsiPDUTxCur->pIScsiCmd); + } + RTMemFree(pImage->pIScsiPDUTxCur); + pImage->pIScsiPDUTxCur = NULL; + } + } + } while ( RT_SUCCESS(rc) + && !pImage->pIScsiPDUTxCur); + + if (rc == VERR_TRY_AGAIN) + rc = VINF_SUCCESS; + + /* Add the write poll flag if we still have something to send, clear it otherwise. */ + if (pImage->pIScsiPDUTxCur) + pImage->fPollEvents |= VD_INTERFACETCPNET_EVT_WRITE; + else + pImage->fPollEvents &= ~VD_INTERFACETCPNET_EVT_WRITE; + + LogFlowFunc(("rc=%Rrc pIScsiPDUTxCur=%#p\n", rc, pImage->pIScsiPDUTxCur)); + return rc; +} + +/** + * Process a received PDU. + * + * @return VBOX status code. + * @param pImage The iSCSI connection state to be used. + * @param paRes Pointer to the array of iSCSI response sections. + * @param cnRes Number of valid iSCSI response sections in the array. + */ +static int iscsiRecvPDUProcess(PISCSIIMAGE pImage, PISCSIRES paRes, uint32_t cnRes) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("pImage=%#p paRes=%#p cnRes=%u\n", pImage, paRes, cnRes)); + + /* Validate the PDU first. */ + rc = iscsiValidatePDU(paRes, cnRes); + if (RT_SUCCESS(rc)) + { + ISCSIOPCODE cmd; + const uint32_t *pcvResSeg = (const uint32_t *)paRes[0].pvSeg; + + Assert(paRes[0].cbSeg > 9 * sizeof(uint32_t)); + + do + { + cmd = (ISCSIOPCODE)(RT_N2H_U32(pcvResSeg[0]) & ISCSIOP_MASK); + switch (cmd) + { + case ISCSIOP_SCSI_RES: + case ISCSIOP_SCSI_TASKMGMT_RES: + case ISCSIOP_SCSI_DATA_IN: + case ISCSIOP_R2T: + case ISCSIOP_ASYN_MSG: + case ISCSIOP_TEXT_RES: + case ISCSIOP_LOGIN_RES: + case ISCSIOP_LOGOUT_RES: + case ISCSIOP_REJECT: + case ISCSIOP_NOP_IN: + if (serial_number_less(pImage->MaxCmdSN, RT_N2H_U32(pcvResSeg[8]))) + pImage->MaxCmdSN = RT_N2H_U32(pcvResSeg[8]); + if (serial_number_less(pImage->ExpCmdSN, RT_N2H_U32(pcvResSeg[7]))) + pImage->ExpCmdSN = RT_N2H_U32(pcvResSeg[7]); + break; + default: + rc = VERR_PARSE_ERROR; + iscsiDumpPacket(pImage, (PISCSIREQ)paRes, cnRes, rc, false /* fRequest */); + } + + if (RT_FAILURE(rc)) + break; + + if ( !pImage->FirstRecvPDU + && (cmd != ISCSIOP_SCSI_DATA_IN || (RT_N2H_U32(pcvResSeg[0]) & ISCSI_STATUS_BIT))) + { + if (pImage->ExpStatSN == RT_N2H_U32(pcvResSeg[6])) + { + /* StatSN counter is not advanced on R2T and on a target SN update NOP-In. */ + if ( (cmd != ISCSIOP_R2T) + && ((cmd != ISCSIOP_NOP_IN) || (RT_N2H_U32(pcvResSeg[4]) != ISCSI_TASK_TAG_RSVD))) + pImage->ExpStatSN++; + } + else + { + rc = VERR_PARSE_ERROR; + iscsiDumpPacket(pImage, (PISCSIREQ)paRes, cnRes, rc, false /* fRequest */); + break; + } + } + + if (pcvResSeg[4] != ISCSI_TASK_TAG_RSVD) + { + /* + * This is a response from the target for a request from the initiator. + * Get the request and update its state. + */ + rc = iscsiRecvPDUUpdateRequest(pImage, paRes, cnRes); + /* Try to send more PDUs now that we updated the MaxCmdSN field */ + if ( RT_SUCCESS(rc) + && !pImage->pIScsiPDUTxCur) + rc = iscsiSendPDUAsync(pImage); + } + else + { + /* This is a target initiated request (we handle only NOP-In request at the moment). */ + if ( cmd == ISCSIOP_NOP_IN + && RT_N2H_U32(pcvResSeg[5]) != ISCSI_TASK_TAG_RSVD) + { + PISCSIPDUTX pIScsiPDUTx; + uint32_t cnISCSIReq; + uint32_t *paReqBHS; + + LogFlowFunc(("Sending NOP-Out\n")); + + /* Allocate a new PDU initialize it and put onto the waiting list. */ + pIScsiPDUTx = (PISCSIPDUTX)RTMemAllocZ(sizeof(ISCSIPDUTX)); + if (!pIScsiPDUTx) + { + rc = VERR_NO_MEMORY; + break; + } + paReqBHS = &pIScsiPDUTx->aBHS[0]; + paReqBHS[0] = RT_H2N_U32(ISCSI_IMMEDIATE_DELIVERY_BIT | ISCSI_FINAL_BIT | ISCSIOP_NOP_OUT); + paReqBHS[1] = RT_H2N_U32(0); /* TotalAHSLength=0,DataSementLength=0 */ + paReqBHS[2] = pcvResSeg[2]; /* copy LUN from NOP-In */ + paReqBHS[3] = pcvResSeg[3]; /* copy LUN from NOP-In */ + paReqBHS[4] = RT_H2N_U32(ISCSI_TASK_TAG_RSVD); /* ITT, reply */ + paReqBHS[5] = pcvResSeg[5]; /* copy TTT from NOP-In */ + paReqBHS[6] = RT_H2N_U32(pImage->CmdSN); + paReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN); + paReqBHS[8] = 0; /* reserved */ + paReqBHS[9] = 0; /* reserved */ + paReqBHS[10] = 0; /* reserved */ + paReqBHS[11] = 0; /* reserved */ + + cnISCSIReq = 0; + pIScsiPDUTx->aISCSIReq[cnISCSIReq].pvSeg = paReqBHS; + pIScsiPDUTx->aISCSIReq[cnISCSIReq].cbSeg = sizeof(pIScsiPDUTx->aBHS); + cnISCSIReq++; + pIScsiPDUTx->cbSgLeft = sizeof(pIScsiPDUTx->aBHS); + RTSgBufInit(&pIScsiPDUTx->SgBuf, pIScsiPDUTx->aISCSIReq, cnISCSIReq); + + /* + * Link the PDU to the list. + * Insert at the front of the list to send the response as soon as possible + * to avoid frequent reconnects for a slow connection when there are many PDUs + * waiting. + */ + iscsiPDUTxAdd(pImage, pIScsiPDUTx, true /* fFront */); + + /* Start transfer of a PDU if there is no one active at the moment. */ + if (!pImage->pIScsiPDUTxCur) + rc = iscsiSendPDUAsync(pImage); + } + } + } while (0); + } + else + iscsiDumpPacket(pImage, (PISCSIREQ)paRes, cnRes, rc, false /* fRequest */); + + return rc; +} + +/** + * Check the static (not dependent on the connection/session state) validity of an iSCSI response PDU. + * + * @returns VBOX status + * @param paRes Pointer to array of iSCSI response sections. + * @param cnRes Number of valid iSCSI response sections in the array. + */ +static int iscsiValidatePDU(PISCSIRES paRes, uint32_t cnRes) +{ + RT_NOREF1(cnRes); + const uint32_t *pcrgResBHS; + uint32_t hw0; + Assert(cnRes >= 1); + Assert(paRes[0].cbSeg >= ISCSI_BHS_SIZE); + + LogFlowFunc(("paRes=%#p cnRes=%u\n", paRes, cnRes)); + + pcrgResBHS = (const uint32_t *)(paRes[0].pvSeg); + hw0 = RT_N2H_U32(pcrgResBHS[0]); + switch (hw0 & ISCSIOP_MASK) + { + case ISCSIOP_NOP_IN: + /* NOP-In responses must not be split into several PDUs nor it may contain + * ping data for target-initiated pings nor may both task tags be valid task tags. */ + if ( (hw0 & ISCSI_FINAL_BIT) == 0 + || ( RT_N2H_U32(pcrgResBHS[4]) == ISCSI_TASK_TAG_RSVD + && RT_N2H_U32(pcrgResBHS[1]) != 0) + || ( RT_N2H_U32(pcrgResBHS[4]) != ISCSI_TASK_TAG_RSVD + && RT_N2H_U32(pcrgResBHS[5]) != ISCSI_TASK_TAG_RSVD)) + return VERR_PARSE_ERROR; + break; + case ISCSIOP_SCSI_RES: + /* SCSI responses must not be split into several PDUs nor must the residual + * bits be contradicting each other nor may the residual bits be set for PDUs + * containing anything else but a completed command response. Underflow + * is no reason for declaring a PDU as invalid, as the target may choose + * to return less data than we assume to get. */ + if ( (hw0 & ISCSI_FINAL_BIT) == 0 + || ((hw0 & ISCSI_BI_READ_RESIDUAL_OVFL_BIT) && (hw0 & ISCSI_BI_READ_RESIDUAL_UNFL_BIT)) + || ((hw0 & ISCSI_RESIDUAL_OVFL_BIT) && (hw0 & ISCSI_RESIDUAL_UNFL_BIT)) + || ( ((hw0 & ISCSI_SCSI_RESPONSE_MASK) == 0) + && ((hw0 & ISCSI_SCSI_STATUS_MASK) == SCSI_STATUS_OK) + && (hw0 & ( ISCSI_BI_READ_RESIDUAL_OVFL_BIT | ISCSI_BI_READ_RESIDUAL_UNFL_BIT + | ISCSI_RESIDUAL_OVFL_BIT)))) + return VERR_PARSE_ERROR; + else + LogFlowFunc(("good SCSI response, first word %#08x\n", RT_N2H_U32(pcrgResBHS[0]))); + break; + case ISCSIOP_LOGIN_RES: + /* Login responses must not contain contradicting transit and continue bits. */ + if ((hw0 & ISCSI_CONTINUE_BIT) && ((hw0 & ISCSI_TRANSIT_BIT) != 0)) + return VERR_PARSE_ERROR; + break; + case ISCSIOP_TEXT_RES: + /* Text responses must not contain contradicting final and continue bits nor + * may the final bit be set for PDUs containing a target transfer tag other than + * the reserved transfer tag (and vice versa). */ + if ( (((hw0 & ISCSI_CONTINUE_BIT) && (hw0 & ISCSI_FINAL_BIT) != 0)) + || (((hw0 & ISCSI_FINAL_BIT) && (RT_N2H_U32(pcrgResBHS[5]) != ISCSI_TASK_TAG_RSVD))) + || (((hw0 & ISCSI_FINAL_BIT) == 0) && (RT_N2H_U32(pcrgResBHS[5]) == ISCSI_TASK_TAG_RSVD))) + return VERR_PARSE_ERROR; + break; + case ISCSIOP_SCSI_DATA_IN: + /* SCSI Data-in responses must not contain contradicting residual bits when + * status bit is set. */ + if ((hw0 & ISCSI_STATUS_BIT) && (hw0 & ISCSI_RESIDUAL_OVFL_BIT) && (hw0 & ISCSI_RESIDUAL_UNFL_BIT)) + return VERR_PARSE_ERROR; + break; + case ISCSIOP_LOGOUT_RES: + /* Logout responses must not have the final bit unset and may not contain any + * data or additional header segments. */ + if ( ((hw0 & ISCSI_FINAL_BIT) == 0) + || (RT_N2H_U32(pcrgResBHS[1]) != 0)) + return VERR_PARSE_ERROR; + break; + case ISCSIOP_ASYN_MSG: + /* Asynchronous Messages must not have the final bit unset and may not contain + * an initiator task tag. */ + if ( ((hw0 & ISCSI_FINAL_BIT) == 0) + || (RT_N2H_U32(pcrgResBHS[4]) != ISCSI_TASK_TAG_RSVD)) + return VERR_PARSE_ERROR; + break; + case ISCSIOP_SCSI_TASKMGMT_RES: + case ISCSIOP_R2T: + case ISCSIOP_REJECT: + default: + /* Do some logging, ignore PDU. */ + LogFlowFunc(("ignore unhandled PDU, first word %#08x\n", RT_N2H_U32(pcrgResBHS[0]))); + return VERR_PARSE_ERROR; + } + /* A target must not send PDUs with MaxCmdSN less than ExpCmdSN-1. */ + + if (serial_number_less(RT_N2H_U32(pcrgResBHS[8]), RT_N2H_U32(pcrgResBHS[7])-1)) + return VERR_PARSE_ERROR; + + return VINF_SUCCESS; +} + + +/** + * Prepares a PDU to transfer for the given command and adds it to the list. + */ +static int iscsiPDUTxPrepare(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd) +{ + int rc = VINF_SUCCESS; + uint32_t *paReqBHS; + size_t cbData = 0; + size_t cbSegs = 0; + PSCSIREQ pScsiReq; + PISCSIPDUTX pIScsiPDU = NULL; + + LogFlowFunc(("pImage=%#p pIScsiCmd=%#p\n", pImage, pIScsiCmd)); + + Assert(pIScsiCmd->enmCmdType == ISCSICMDTYPE_REQ); + + pIScsiCmd->Itt = iscsiNewITT(pImage); + pScsiReq = pIScsiCmd->CmdType.ScsiReq.pScsiReq; + + if (pScsiReq->cT2ISegs) + RTSgBufInit(&pScsiReq->SgBufT2I, pScsiReq->paT2ISegs, pScsiReq->cT2ISegs); + + /* + * Allocate twice as much entries as required for padding (worst case). + * The additional segment is for the BHS. + */ + size_t cI2TSegs = 2*(pScsiReq->cI2TSegs + 1); + pIScsiPDU = (PISCSIPDUTX)RTMemAllocZ(RT_UOFFSETOF_DYN(ISCSIPDUTX, aISCSIReq[cI2TSegs])); + if (!pIScsiPDU) + return VERR_NO_MEMORY; + + pIScsiPDU->pIScsiCmd = pIScsiCmd; + + if (pScsiReq->enmXfer == SCSIXFER_FROM_TARGET) + cbData = (uint32_t)pScsiReq->cbT2IData; + else + cbData = (uint32_t)pScsiReq->cbI2TData; + + paReqBHS = pIScsiPDU->aBHS; + + /* Setup the BHS. */ + paReqBHS[0] = RT_H2N_U32( ISCSI_FINAL_BIT | ISCSI_TASK_ATTR_SIMPLE | ISCSIOP_SCSI_CMD + | (pScsiReq->enmXfer << 21)); /* I=0,F=1,Attr=Simple */ + paReqBHS[1] = RT_H2N_U32(0x00000000 | ((uint32_t)pScsiReq->cbI2TData & 0xffffff)); /* TotalAHSLength=0 */ + paReqBHS[2] = RT_H2N_U32(pImage->LUN >> 32); + paReqBHS[3] = RT_H2N_U32(pImage->LUN & 0xffffffff); + paReqBHS[4] = pIScsiCmd->Itt; + paReqBHS[5] = RT_H2N_U32((uint32_t)cbData); Assert((uint32_t)cbData == cbData); + paReqBHS[6] = RT_H2N_U32(pImage->CmdSN); + paReqBHS[7] = RT_H2N_U32(pImage->ExpStatSN); + memcpy(paReqBHS + 8, pScsiReq->abCDB, pScsiReq->cbCDB); + + pIScsiPDU->CmdSN = pImage->CmdSN; + pImage->CmdSN++; + + /* Setup the S/G buffers. */ + uint32_t cnISCSIReq = 0; + pIScsiPDU->aISCSIReq[cnISCSIReq].cbSeg = sizeof(pIScsiPDU->aBHS); + pIScsiPDU->aISCSIReq[cnISCSIReq].pvSeg = pIScsiPDU->aBHS; + cnISCSIReq++; + cbSegs = sizeof(pIScsiPDU->aBHS); + /* Padding is not necessary for the BHS. */ + + if (pScsiReq->cbI2TData) + { + for (unsigned cSeg = 0; cSeg < pScsiReq->cI2TSegs; cSeg++) + { + Assert(cnISCSIReq < cI2TSegs); + pIScsiPDU->aISCSIReq[cnISCSIReq].cbSeg = pScsiReq->paI2TSegs[cSeg].cbSeg; + pIScsiPDU->aISCSIReq[cnISCSIReq].pvSeg = pScsiReq->paI2TSegs[cSeg].pvSeg; + cbSegs += pScsiReq->paI2TSegs[cSeg].cbSeg; + cnISCSIReq++; + + /* Add padding if necessary. */ + if (pScsiReq->paI2TSegs[cSeg].cbSeg & 3) + { + Assert(cnISCSIReq < cI2TSegs); + pIScsiPDU->aISCSIReq[cnISCSIReq].pvSeg = &pImage->aPadding[0]; + pIScsiPDU->aISCSIReq[cnISCSIReq].cbSeg = 4 - (pScsiReq->paI2TSegs[cSeg].cbSeg & 3); + cbSegs += pIScsiPDU->aISCSIReq[cnISCSIReq].cbSeg; + cnISCSIReq++; + } + } + } + + pIScsiPDU->cISCSIReq = cnISCSIReq; + pIScsiPDU->cbSgLeft = cbSegs; + RTSgBufInit(&pIScsiPDU->SgBuf, pIScsiPDU->aISCSIReq, cnISCSIReq); + + /* Link the PDU to the list. */ + iscsiPDUTxAdd(pImage, pIScsiPDU, false /* fFront */); + + /* Start transfer of a PDU if there is no one active at the moment. */ + if (!pImage->pIScsiPDUTxCur) + rc = iscsiSendPDUAsync(pImage); + + return rc; +} + + +/** + * Updates the state of a request from the PDU we received. + * + * @return VBox status code. + * @param pImage iSCSI connection state to use. + * @param paRes Pointer to array of iSCSI response sections. + * @param cnRes Number of valid iSCSI response sections in the array. + */ +static int iscsiRecvPDUUpdateRequest(PISCSIIMAGE pImage, PISCSIRES paRes, uint32_t cnRes) +{ + int rc = VINF_SUCCESS; + PISCSICMD pIScsiCmd; + uint32_t *paResBHS; + + LogFlowFunc(("pImage=%#p paRes=%#p cnRes=%u\n", pImage, paRes, cnRes)); + + Assert(cnRes == 1); + Assert(paRes[0].cbSeg >= ISCSI_BHS_SIZE); + + paResBHS = (uint32_t *)paRes[0].pvSeg; + + pIScsiCmd = iscsiCmdGetFromItt(pImage, paResBHS[4]); + + if (pIScsiCmd) + { + bool final = false; + PSCSIREQ pScsiReq; + + LogFlow(("Found SCSI command %#p for Itt=%#u\n", pIScsiCmd, paResBHS[4])); + + Assert(pIScsiCmd->enmCmdType == ISCSICMDTYPE_REQ); + pScsiReq = pIScsiCmd->CmdType.ScsiReq.pScsiReq; + + final = !!(RT_N2H_U32(paResBHS[0]) & ISCSI_FINAL_BIT); + ISCSIOPCODE cmd = (ISCSIOPCODE)(RT_N2H_U32(paResBHS[0]) & ISCSIOP_MASK); + if (cmd == ISCSIOP_SCSI_RES) + { + /* This is the final PDU which delivers the status (and may be omitted if + * the last Data-In PDU included successful completion status). Note + * that ExpStatSN has been bumped already in iscsiRecvPDU. */ + if (!final || ((RT_N2H_U32(paResBHS[0]) & 0x0000ff00) != 0) || (RT_N2H_U32(paResBHS[6]) != pImage->ExpStatSN - 1)) + { + /* SCSI Response in the wrong place or with a (target) failure. */ + LogFlow(("Wrong ExpStatSN value in PDU\n")); + rc = VERR_PARSE_ERROR; + } + else + { + pScsiReq->status = RT_N2H_U32(paResBHS[0]) & 0x000000ff; + size_t cbData = RT_N2H_U32(paResBHS[1]) & 0x00ffffff; + void *pvSense = (uint8_t *)paRes[0].pvSeg + ISCSI_BHS_SIZE; + + if (cbData >= 2) + { + uint32_t cbStat = RT_N2H_U32(((uint32_t *)pvSense)[0]) >> 16; + if (cbStat + 2 > cbData) + { + rc = VERR_BUFFER_OVERFLOW; + } + else + { + /* Truncate sense data if it doesn't fit into the buffer. */ + pScsiReq->cbSense = RT_MIN(cbStat, pScsiReq->cbSense); + memcpy(pScsiReq->abSense, (uint8_t *)pvSense + 2, + RT_MIN(paRes[0].cbSeg - ISCSI_BHS_SIZE - 2, pScsiReq->cbSense)); + } + } + else if (cbData == 1) + rc = VERR_PARSE_ERROR; + else + pScsiReq->cbSense = 0; + } + iscsiCmdComplete(pImage, pIScsiCmd, rc); + } + else if (cmd == ISCSIOP_SCSI_DATA_IN) + { + /* A Data-In PDU carries some data that needs to be added to the received + * data in response to the command. There may be both partial and complete + * Data-In PDUs, so collect data until the status is included or the status + * is sent in a separate SCSI Result frame (see above). */ + size_t cbData = RT_N2H_U32(paResBHS[1]) & 0x00ffffff; + void *pvData = (uint8_t *)paRes[0].pvSeg + ISCSI_BHS_SIZE; + + if (final && cbData > pScsiReq->cbT2IData) + { + /* The received PDU is bigger than what we requested. + * Must not happen under normal circumstances and is a target error. */ + rc = VERR_BUFFER_OVERFLOW; + } + else + { + /* Copy data from the received PDU into the T2I segments. */ + size_t cbCopied = RTSgBufCopyFromBuf(&pScsiReq->SgBufT2I, pvData, cbData); + Assert(cbCopied == cbData); NOREF(cbCopied); + + if (final && (RT_N2H_U32(paResBHS[0]) & ISCSI_STATUS_BIT) != 0) + { + pScsiReq->status = RT_N2H_U32(paResBHS[0]) & 0x000000ff; + pScsiReq->cbSense = 0; + iscsiCmdComplete(pImage, pIScsiCmd, VINF_SUCCESS); + } + } + } + else + rc = VERR_PARSE_ERROR; + } + + /* Log any errors here but ignore the PDU. */ + if (RT_FAILURE(rc)) + { + LogRel(("iSCSI: Received malformed PDU from target %s (rc=%Rrc), ignoring\n", pImage->pszTargetName, rc)); + iscsiDumpPacket(pImage, (PISCSIREQ)paRes, cnRes, rc, false /* fRequest */); + rc = VINF_SUCCESS; + } + + return rc; +} + +/** + * Appends a key-value pair to the buffer. Normal ASCII strings (cbValue == 0) and large binary values + * of a given length (cbValue > 0) are directly supported. Other value types must be converted to ASCII + * by the caller. Strings must be in UTF-8 encoding. + * + * @returns VBOX status + * @param pbBuf Pointer to the key-value buffer. + * @param cbBuf Length of the key-value buffer. + * @param pcbBufCurr Currently used portion of the key-value buffer. + * @param pcszKey Pointer to a string containing the key. + * @param pcszValue Pointer to either a string containing the value or to a large binary value. + * @param cbValue Length of the binary value if applicable. + */ +static int iscsiTextAddKeyValue(uint8_t *pbBuf, size_t cbBuf, size_t *pcbBufCurr, const char *pcszKey, + const char *pcszValue, size_t cbValue) +{ + size_t cbBufTmp = *pcbBufCurr; + size_t cbKey = strlen(pcszKey); + size_t cbValueEnc; + uint8_t *pbCurr; + + if (cbValue == 0) + cbValueEnc = strlen(pcszValue); + else + cbValueEnc = cbValue * 2 + 2; /* 2 hex bytes per byte, 2 bytes prefix */ + + if (cbBuf < cbBufTmp + cbKey + 1 + cbValueEnc + 1) + { + /* Buffer would overflow, signal error. */ + return VERR_BUFFER_OVERFLOW; + } + + /* + * Append a key=value pair (zero terminated string) to the end of the buffer. + */ + pbCurr = pbBuf + cbBufTmp; + memcpy(pbCurr, pcszKey, cbKey); + pbCurr += cbKey; + *pbCurr++ = '='; + if (cbValue == 0) + { + memcpy(pbCurr, pcszValue, cbValueEnc); + pbCurr += cbValueEnc; + } + else + { + *pbCurr++ = '0'; + *pbCurr++ = 'x'; + for (uint32_t i = 0; i < cbValue; i++) + { + uint8_t b; + b = pcszValue[i]; + *pbCurr++ = NUM_2_HEX(b >> 4); + *pbCurr++ = NUM_2_HEX(b & 0xf); + } + } + *pbCurr = '\0'; + *pcbBufCurr = cbBufTmp + cbKey + 1 + cbValueEnc + 1; + + return VINF_SUCCESS; +} + + +/** + * Retrieve the value for a given key from the key=value buffer. + * + * @returns VBox status code. + * @param pbBuf Buffer containing key=value pairs. + * @param cbBuf Length of buffer with key=value pairs. + * @param pcszKey Pointer to key for which to retrieve the value. + * @param ppcszValue Pointer to value string pointer. + */ +static int iscsiTextGetKeyValue(const uint8_t *pbBuf, size_t cbBuf, const char *pcszKey, const char **ppcszValue) +{ + size_t cbKey = strlen(pcszKey); + + while (cbBuf != 0) + { + size_t cbKeyValNull = strlen((const char *)pbBuf) + 1; + + if (strncmp(pcszKey, (const char *)pbBuf, cbKey) == 0 && pbBuf[cbKey] == '=') + { + *ppcszValue = (const char *)(pbBuf + cbKey + 1); + return VINF_SUCCESS; + } + pbBuf += cbKeyValNull; + cbBuf -= cbKeyValNull; + } + return VERR_INVALID_NAME; +} + + +/** + * Convert a long-binary value from a value string to the binary representation. + * + * @returns VBOX status + * @param pcszValue Pointer to a string containing the textual value representation. + * @param pbValue Pointer to the value buffer for the binary value. + * @param pcbValue In: length of value buffer, out: actual length of binary value. + */ +static int iscsiStrToBinary(const char *pcszValue, uint8_t *pbValue, size_t *pcbValue) +{ + size_t cbValue = *pcbValue; + char c1, c2, c3, c4; + Assert(cbValue >= 1); + + if (strlen(pcszValue) < 3) + return VERR_PARSE_ERROR; + if (*pcszValue++ != '0') + return VERR_PARSE_ERROR; + switch (*pcszValue++) + { + case 'x': + case 'X': + if (strlen(pcszValue) & 1) + { + c1 = *pcszValue++; + *pbValue++ = HEX_2_NUM(c1); + cbValue--; + } + while (*pcszValue != '\0') + { + if (cbValue == 0) + return VERR_BUFFER_OVERFLOW; + c1 = *pcszValue++; + if ((c1 < '0' || c1 > '9') && (c1 < 'a' || c1 > 'f') && (c1 < 'A' || c1 > 'F')) + return VERR_PARSE_ERROR; + c2 = *pcszValue++; + if ((c2 < '0' || c2 > '9') && (c2 < 'a' || c2 > 'f') && (c2 < 'A' || c2 > 'F')) + return VERR_PARSE_ERROR; + *pbValue++ = (HEX_2_NUM(c1) << 4) | HEX_2_NUM(c2); + cbValue--; + } + *pcbValue -= cbValue; + break; + case 'b': + case 'B': + if ((strlen(pcszValue) & 3) != 0) + return VERR_PARSE_ERROR; + while (*pcszValue != '\0') + { + uint32_t temp; + if (cbValue == 0) + return VERR_BUFFER_OVERFLOW; + c1 = *pcszValue++; + if ((c1 < 'A' || c1 > 'Z') && (c1 < 'a' || c1 >'z') && (c1 < '0' || c1 > '9') && (c1 != '+') && (c1 != '/')) + return VERR_PARSE_ERROR; + c2 = *pcszValue++; + if ((c2 < 'A' || c2 > 'Z') && (c2 < 'a' || c2 >'z') && (c2 < '0' || c2 > '9') && (c2 != '+') && (c2 != '/')) + return VERR_PARSE_ERROR; + c3 = *pcszValue++; + if ((c3 < 'A' || c3 > 'Z') && (c3 < 'a' || c3 >'z') && (c3 < '0' || c3 > '9') && (c3 != '+') && (c3 != '/') && (c3 != '=')) + return VERR_PARSE_ERROR; + c4 = *pcszValue++; + if ( (c3 == '=' && c4 != '=') + || ((c4 < 'A' || c4 > 'Z') && (c4 < 'a' || c4 >'z') && (c4 < '0' || c4 > '9') && (c4 != '+') && (c4 != '/') && (c4 != '='))) + return VERR_PARSE_ERROR; + temp = (B64_2_NUM(c1) << 18) | (B64_2_NUM(c2) << 12); + if (c3 == '=') { + if (*pcszValue != '\0') + return VERR_PARSE_ERROR; + *pbValue++ = temp >> 16; + cbValue--; + } else { + temp |= B64_2_NUM(c3) << 6; + if (c4 == '=') { + if (*pcszValue != '\0') + return VERR_PARSE_ERROR; + if (cbValue < 2) + return VERR_BUFFER_OVERFLOW; + *pbValue++ = temp >> 16; + *pbValue++ = (temp >> 8) & 0xff; + cbValue -= 2; + } + else + { + temp |= B64_2_NUM(c4); + if (cbValue < 3) + return VERR_BUFFER_OVERFLOW; + *pbValue++ = temp >> 16; + *pbValue++ = (temp >> 8) & 0xff; + *pbValue++ = temp & 0xff; + cbValue -= 3; + } + } + } + *pcbValue -= cbValue; + break; + default: + return VERR_PARSE_ERROR; + } + return VINF_SUCCESS; +} + + +/** + * Retrieve the relevant parameter values and update the initiator state. + * + * @returns VBox status code. + * @param pImage Current iSCSI initiator state. + * @param pbBuf Buffer containing key=value pairs. + * @param cbBuf Length of buffer with key=value pairs. + */ +static int iscsiUpdateParameters(PISCSIIMAGE pImage, const uint8_t *pbBuf, size_t cbBuf) +{ + int rc; + const char *pcszMaxRecvDataSegmentLength = NULL; + const char *pcszMaxBurstLength = NULL; + const char *pcszFirstBurstLength = NULL; + rc = iscsiTextGetKeyValue(pbBuf, cbBuf, "MaxRecvDataSegmentLength", &pcszMaxRecvDataSegmentLength); + if (rc == VERR_INVALID_NAME) + rc = VINF_SUCCESS; + if (RT_FAILURE(rc)) + return VERR_PARSE_ERROR; + rc = iscsiTextGetKeyValue(pbBuf, cbBuf, "MaxBurstLength", &pcszMaxBurstLength); + if (rc == VERR_INVALID_NAME) + rc = VINF_SUCCESS; + if (RT_FAILURE(rc)) + return VERR_PARSE_ERROR; + rc = iscsiTextGetKeyValue(pbBuf, cbBuf, "FirstBurstLength", &pcszFirstBurstLength); + if (rc == VERR_INVALID_NAME) + rc = VINF_SUCCESS; + if (RT_FAILURE(rc)) + return VERR_PARSE_ERROR; + if (pcszMaxRecvDataSegmentLength) + { + uint32_t cb = pImage->cbSendDataLength; + rc = RTStrToUInt32Full(pcszMaxRecvDataSegmentLength, 0, &cb); + AssertRC(rc); + pImage->cbSendDataLength = RT_MIN(pImage->cbSendDataLength, cb); + } + if (pcszMaxBurstLength) + { + uint32_t cb = pImage->cbSendDataLength; + rc = RTStrToUInt32Full(pcszMaxBurstLength, 0, &cb); + AssertRC(rc); + pImage->cbSendDataLength = RT_MIN(pImage->cbSendDataLength, cb); + } + if (pcszFirstBurstLength) + { + uint32_t cb = pImage->cbSendDataLength; + rc = RTStrToUInt32Full(pcszFirstBurstLength, 0, &cb); + AssertRC(rc); + pImage->cbSendDataLength = RT_MIN(pImage->cbSendDataLength, cb); + } + return VINF_SUCCESS; +} + + +static bool serial_number_less(uint32_t s1, uint32_t s2) +{ + return (s1 < s2 && s2 - s1 < 0x80000000) || (s1 > s2 && s1 - s2 > 0x80000000); +} + +static bool serial_number_greater(uint32_t s1, uint32_t s2) +{ + return (s1 < s2 && s2 - s1 > 0x80000000) || (s1 > s2 && s1 - s2 < 0x80000000); +} + + +#ifdef IMPLEMENT_TARGET_AUTH +static void chap_md5_generate_challenge(uint8_t *pbChallenge, size_t *pcbChallenge) +{ + uint8_t cbChallenge; + + cbChallenge = RTrand_U8(CHAP_MD5_CHALLENGE_MIN, CHAP_MD5_CHALLENGE_MAX); + RTrand_bytes(pbChallenge, cbChallenge); + *pcbChallenge = cbChallenge; +} +#endif + + +static void chap_md5_compute_response(uint8_t *pbResponse, uint8_t id, const uint8_t *pbChallenge, size_t cbChallenge, + const uint8_t *pbSecret, size_t cbSecret) +{ + RTMD5CONTEXT ctx; + uint8_t bId; + + bId = id; + RTMd5Init(&ctx); + RTMd5Update(&ctx, &bId, 1); + RTMd5Update(&ctx, pbSecret, cbSecret); + RTMd5Update(&ctx, pbChallenge, cbChallenge); + RTMd5Final(pbResponse, &ctx); +} + +/** + * Internal. - Wrapper around the extended select callback of the net interface. + */ +DECLINLINE(int) iscsiIoThreadWait(PISCSIIMAGE pImage, RTMSINTERVAL cMillies, uint32_t fEvents, uint32_t *pfEvents) +{ + return pImage->pIfNet->pfnSelectOneEx(pImage->Socket, fEvents, pfEvents, cMillies); +} + +/** + * Internal. - Pokes a thread waiting for I/O. + */ +DECLINLINE(int) iscsiIoThreadPoke(PISCSIIMAGE pImage) +{ + return pImage->pIfNet->pfnPoke(pImage->Socket); +} + +/** + * Internal. - Get the next request from the queue. + */ +DECLINLINE(PISCSICMD) iscsiCmdGet(PISCSIIMAGE pImage) +{ + int rc; + PISCSICMD pIScsiCmd = NULL; + + rc = RTSemMutexRequest(pImage->MutexReqQueue, RT_INDEFINITE_WAIT); + AssertRC(rc); + + pIScsiCmd = pImage->pScsiReqQueue; + if (pIScsiCmd) + { + pImage->pScsiReqQueue = pIScsiCmd->pNext; + pIScsiCmd->pNext = NULL; + } + + rc = RTSemMutexRelease(pImage->MutexReqQueue); + AssertRC(rc); + + return pIScsiCmd; +} + + +/** + * Internal. - Adds the given command to the queue. + */ +DECLINLINE(int) iscsiCmdPut(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd) +{ + int rc = RTSemMutexRequest(pImage->MutexReqQueue, RT_INDEFINITE_WAIT); + AssertRC(rc); + + pIScsiCmd->pNext = pImage->pScsiReqQueue; + pImage->pScsiReqQueue = pIScsiCmd; + + rc = RTSemMutexRelease(pImage->MutexReqQueue); + AssertRC(rc); + + iscsiIoThreadPoke(pImage); + + return rc; +} + +/** + * Internal. - Completes the request with the appropriate action. + * Synchronous requests are completed with waking up the thread + * and asynchronous ones by continuing the associated I/O context. + */ +static void iscsiCmdComplete(PISCSIIMAGE pImage, PISCSICMD pIScsiCmd, int rcCmd) +{ + LogFlowFunc(("pImage=%#p pIScsiCmd=%#p rcCmd=%Rrc\n", pImage, pIScsiCmd, rcCmd)); + + /* Remove from the table first. */ + iscsiCmdRemove(pImage, pIScsiCmd->Itt); + + /* Call completion callback. */ + pIScsiCmd->pfnComplete(pImage, rcCmd, pIScsiCmd->pvUser); + + /* Free command structure. */ +#ifdef DEBUG + memset(pIScsiCmd, 0xff, sizeof(ISCSICMD)); +#endif + RTMemFree(pIScsiCmd); +} + +/** + * Clears all RX/TX PDU states and returns the command for the current + * pending TX PDU if existing. + * + * @returns Pointer to the iSCSI command for the current PDU transmitted or NULL + * if none is waiting. + * @param pImage iSCSI connection state. + */ +static PISCSICMD iscsiPDURxTxClear(PISCSIIMAGE pImage) +{ + PISCSICMD pIScsiCmdHead = NULL; + PISCSIPDUTX pIScsiPDUTx = NULL; + + /* Reset PDU we are receiving. */ + iscsiRecvPDUReset(pImage); + + /* + * Abort all PDUs we are about to transmit, + * the command need a new Itt if the relogin is successful. + */ + while (pImage->pIScsiPDUTxHead) + { + pIScsiPDUTx = pImage->pIScsiPDUTxHead; + pImage->pIScsiPDUTxHead = pIScsiPDUTx->pNext; + + PISCSICMD pIScsiCmd = pIScsiPDUTx->pIScsiCmd; + if (pIScsiCmd) + { + /* Place on command list. */ + pIScsiCmd->pNext = pIScsiCmdHead; + pIScsiCmdHead = pIScsiCmd; + } + RTMemFree(pIScsiPDUTx); + } + + /* Clear the tail pointer (safety precaution). */ + pImage->pIScsiPDUTxTail = NULL; + + /* Clear the current PDU too. */ + if (pImage->pIScsiPDUTxCur) + { + pIScsiPDUTx = pImage->pIScsiPDUTxCur; + + pImage->pIScsiPDUTxCur = NULL; + PISCSICMD pIScsiCmd = pIScsiPDUTx->pIScsiCmd; + if (pIScsiCmd) + { + pIScsiCmd->pNext = pIScsiCmdHead; + pIScsiCmdHead = pIScsiCmd; + } + RTMemFree(pIScsiPDUTx); + } + + return pIScsiCmdHead; +} + +/** + * Rests the iSCSI connection state and returns a list of iSCSI commands pending + * when this was called. + * + * @returns Pointer to the head of the pending iSCSI command list. + * @param pImage iSCSI connection state. + */ +static PISCSICMD iscsiReset(PISCSIIMAGE pImage) +{ + PISCSICMD pIScsiCmdHead = NULL; + PISCSICMD pIScsiCmdCur = NULL; + + /* Clear all in flight PDUs. */ + pIScsiCmdHead = iscsiPDURxTxClear(pImage); + + /* + * Get all commands which are waiting for a response + * They need to be resend too after a successful reconnect. + */ + PISCSICMD pIScsiCmd = iscsiCmdRemoveAll(pImage); + if (pIScsiCmd) + { + pIScsiCmdCur = pIScsiCmd; + while (pIScsiCmdCur->pNext) + pIScsiCmdCur = pIScsiCmdCur->pNext; + + /* + * Place them in front of the list because they are the oldest requests + * and need to be processed first to minimize the risk to time out. + */ + pIScsiCmdCur->pNext = pIScsiCmdHead; + pIScsiCmdHead = pIScsiCmd; + } + + return pIScsiCmdHead; +} + +/** + * Reattaches the to the target after an error aborting + * pending commands and resending them. + * + * @param pImage iSCSI connection state. + */ +static void iscsiReattach(PISCSIIMAGE pImage) +{ + /* Close connection. */ + iscsiTransportClose(pImage); + pImage->state = ISCSISTATE_FREE; + + /* Reset the state and get the currently pending commands. */ + PISCSICMD pIScsiCmdHead = iscsiReset(pImage); + + /* Try to attach. */ + int rc = iscsiAttach(pImage); + if (RT_SUCCESS(rc)) + { + /* Phew, we have a connection again. + * Prepare new PDUs for the aborted commands. + */ + while (pIScsiCmdHead) + { + PISCSICMD pIScsiCmd = pIScsiCmdHead; + pIScsiCmdHead = pIScsiCmdHead->pNext; + + pIScsiCmd->pNext = NULL; + + rc = iscsiPDUTxPrepare(pImage, pIScsiCmd); + if (RT_FAILURE(rc)) + break; + } + + if (RT_FAILURE(rc)) + { + /* Another error, just give up and report an error. */ + PISCSICMD pIScsiCmd = iscsiReset(pImage); + + /* Concatenate both lists together so we can abort all requests below. */ + if (pIScsiCmd) + { + PISCSICMD pIScsiCmdCur = pIScsiCmd; + while (pIScsiCmdCur->pNext) + pIScsiCmdCur = pIScsiCmdCur->pNext; + + pIScsiCmdCur->pNext = pIScsiCmdHead; + pIScsiCmdHead = pIScsiCmd; + } + } + } + + if (RT_FAILURE(rc)) + { + /* + * Still no luck, complete commands with error so the caller + * has a chance to inform the user and maybe resend the command. + */ + while (pIScsiCmdHead) + { + PISCSICMD pIScsiCmd = pIScsiCmdHead; + pIScsiCmdHead = pIScsiCmdHead->pNext; + + iscsiCmdComplete(pImage, pIScsiCmd, VERR_BROKEN_PIPE); + } + } +} + +/** + * Internal. Main iSCSI I/O worker. + */ +static DECLCALLBACK(int) iscsiIoThreadWorker(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF1(hThreadSelf); + PISCSIIMAGE pImage = (PISCSIIMAGE)pvUser; + + /* Initialize the initial event mask. */ + pImage->fPollEvents = VD_INTERFACETCPNET_EVT_READ | VD_INTERFACETCPNET_EVT_ERROR; + + while (pImage->fRunning) + { + uint32_t fEvents; + int rc; + + fEvents = 0; + + /* Wait for work or for data from the target. */ + RTMSINTERVAL msWait; + + if (pImage->cCmdsWaiting) + { + pImage->fPollEvents &= ~VD_INTERFACETCPNET_HINT_INTERRUPT; + msWait = pImage->uReadTimeout; + } + else + { + pImage->fPollEvents |= VD_INTERFACETCPNET_HINT_INTERRUPT; + msWait = RT_INDEFINITE_WAIT; + } + + LogFlow(("Waiting for events fPollEvents=%#x\n", pImage->fPollEvents)); + rc = iscsiIoThreadWait(pImage, msWait, pImage->fPollEvents, &fEvents); + if (rc == VERR_INTERRUPTED) + { + /* Check the queue. */ + PISCSICMD pIScsiCmd = iscsiCmdGet(pImage); + + while (pIScsiCmd) + { + switch (pIScsiCmd->enmCmdType) + { + case ISCSICMDTYPE_REQ: + { + if ( !iscsiIsClientConnected(pImage) + && pImage->fTryReconnect) + { + pImage->fTryReconnect = false; + iscsiReattach(pImage); + } + + /* If there is no connection complete the command with an error. */ + if (RT_LIKELY(iscsiIsClientConnected(pImage))) + { + rc = iscsiPDUTxPrepare(pImage, pIScsiCmd); + if (RT_FAILURE(rc)) + iscsiReattach(pImage); + } + else + iscsiCmdComplete(pImage, pIScsiCmd, VERR_NET_CONNECTION_REFUSED); + break; + } + case ISCSICMDTYPE_EXEC: + { + rc = pIScsiCmd->CmdType.Exec.pfnExec(pIScsiCmd->CmdType.Exec.pvUser); + iscsiCmdComplete(pImage, pIScsiCmd, rc); + break; + } + default: + AssertMsgFailed(("Invalid command type %d\n", pIScsiCmd->enmCmdType)); + } + + pIScsiCmd = iscsiCmdGet(pImage); + } + } + else if (rc == VERR_TIMEOUT && pImage->cCmdsWaiting) + { + /* + * We are waiting for a response from the target but + * it didn't answered yet. + * We assume the connection is broken and try to reconnect. + */ + LogFlow(("Timed out while waiting for an answer from the target, reconnecting\n")); + iscsiReattach(pImage); + } + else if (RT_SUCCESS(rc) || rc == VERR_TIMEOUT) + { + Assert(pImage->state == ISCSISTATE_NORMAL); + LogFlow(("Got socket events %#x\n", fEvents)); + + if (fEvents & VD_INTERFACETCPNET_EVT_READ) + { + /* Continue or start a new PDU receive task */ + LogFlow(("There is data on the socket\n")); + rc = iscsiRecvPDUAsync(pImage); + if (rc == VERR_BROKEN_PIPE) + iscsiReattach(pImage); + else if (RT_FAILURE(rc)) + iscsiLogRel(pImage, "iSCSI: Handling incoming request failed %Rrc\n", rc); + } + + if (fEvents & VD_INTERFACETCPNET_EVT_WRITE) + { + LogFlow(("The socket is writable\n")); + rc = iscsiSendPDUAsync(pImage); + if (RT_FAILURE(rc)) + { + /* + * Something unexpected happened, log the error and try to reset everything + * by reattaching to the target. + */ + iscsiLogRel(pImage, "iSCSI: Sending PDU failed %Rrc\n", rc); + iscsiReattach(pImage); + } + } + + if (fEvents & VD_INTERFACETCPNET_EVT_ERROR) + { + LogFlow(("An error ocurred\n")); + iscsiReattach(pImage); + } + } + else + iscsiLogRel(pImage, "iSCSI: Waiting for I/O failed rc=%Rrc\n", rc); + } + + return VINF_SUCCESS; +} + +/** + * Internal. - Enqueues a request asynchronously. + */ +static int iscsiCommandAsync(PISCSIIMAGE pImage, PSCSIREQ pScsiReq, + PFNISCSICMDCOMPLETED pfnComplete, void *pvUser) +{ + int rc; + + if (pImage->fExtendedSelectSupported) + { + PISCSICMD pIScsiCmd = (PISCSICMD)RTMemAllocZ(sizeof(ISCSICMD)); + if (!pIScsiCmd) + return VERR_NO_MEMORY; + + /* Init the command structure. */ + pIScsiCmd->pNext = NULL; + pIScsiCmd->enmCmdType = ISCSICMDTYPE_REQ; + pIScsiCmd->pfnComplete = pfnComplete; + pIScsiCmd->pvUser = pvUser; + pIScsiCmd->CmdType.ScsiReq.pScsiReq = pScsiReq; + + rc = iscsiCmdPut(pImage, pIScsiCmd); + if (RT_FAILURE(rc)) + RTMemFree(pIScsiCmd); + } + else + rc = VERR_NOT_SUPPORTED; + + return rc; +} + +static DECLCALLBACK(void) iscsiCommandCompleteSync(PISCSIIMAGE pImage, int rcReq, void *pvUser) +{ + RT_NOREF1(pImage); + PISCSICMDSYNC pIScsiCmdSync = (PISCSICMDSYNC)pvUser; + + pIScsiCmdSync->rcCmd = rcReq; + int rc = RTSemEventSignal(pIScsiCmdSync->EventSem); + AssertRC(rc); +} + +/** + * Internal. - Enqueues a request in a synchronous fashion + * i.e. returns when the request completed. + */ +static int iscsiCommandSync(PISCSIIMAGE pImage, PSCSIREQ pScsiReq, bool fRetry, int rcSense) +{ + int rc; + + if (pImage->fExtendedSelectSupported) + { + ISCSICMDSYNC IScsiCmdSync; + + /* Create event semaphore. */ + rc = RTSemEventCreate(&IScsiCmdSync.EventSem); + if (RT_FAILURE(rc)) + return rc; + + if (fRetry) + { + for (unsigned i = 0; i < 10; i++) + { + rc = iscsiCommandAsync(pImage, pScsiReq, iscsiCommandCompleteSync, &IScsiCmdSync); + if (RT_FAILURE(rc)) + break; + + rc = RTSemEventWait(IScsiCmdSync.EventSem, RT_INDEFINITE_WAIT); + AssertRC(rc); + rc = IScsiCmdSync.rcCmd; + + if ( (RT_SUCCESS(rc) && !pScsiReq->cbSense) + || RT_FAILURE(rc)) + break; + rc = rcSense; + } + } + else + { + rc = iscsiCommandAsync(pImage, pScsiReq, iscsiCommandCompleteSync, &IScsiCmdSync); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventWait(IScsiCmdSync.EventSem, RT_INDEFINITE_WAIT); + AssertRC(rc); + rc = IScsiCmdSync.rcCmd; + + if (RT_FAILURE(rc) || pScsiReq->cbSense > 0) + rc = rcSense; + } + } + + RTSemEventDestroy(IScsiCmdSync.EventSem); + } + else + { + if (fRetry) + { + rc = VINF_SUCCESS; /* (MSC incorrectly thinks it can be uninitialized) */ + for (unsigned i = 0; i < 10; i++) + { + rc = iscsiCommand(pImage, pScsiReq); + if ( (RT_SUCCESS(rc) && !pScsiReq->cbSense) + || RT_FAILURE(rc)) + break; + rc = rcSense; + } + } + else + { + rc = iscsiCommand(pImage, pScsiReq); + if (RT_FAILURE(rc) || pScsiReq->cbSense > 0) + rc = rcSense; + } + } + + return rc; +} + + +/** + * Internal. - Executes a given function in a synchronous fashion + * on the I/O thread if available. + */ +static int iscsiExecSync(PISCSIIMAGE pImage, PFNISCSIEXEC pfnExec, void *pvUser) +{ + int rc; + + if (pImage->fExtendedSelectSupported) + { + ISCSICMDSYNC IScsiCmdSync; + PISCSICMD pIScsiCmd = (PISCSICMD)RTMemAllocZ(sizeof(ISCSICMD)); + if (!pIScsiCmd) + return VERR_NO_MEMORY; + + /* Create event semaphore. */ + rc = RTSemEventCreate(&IScsiCmdSync.EventSem); + if (RT_FAILURE(rc)) + { + RTMemFree(pIScsiCmd); + return rc; + } + + /* Init the command structure. */ + pIScsiCmd->pNext = NULL; + pIScsiCmd->enmCmdType = ISCSICMDTYPE_EXEC; + pIScsiCmd->pfnComplete = iscsiCommandCompleteSync; + pIScsiCmd->pvUser = &IScsiCmdSync; + pIScsiCmd->CmdType.Exec.pfnExec = pfnExec; + pIScsiCmd->CmdType.Exec.pvUser = pvUser; + + rc = iscsiCmdPut(pImage, pIScsiCmd); + if (RT_FAILURE(rc)) + RTMemFree(pIScsiCmd); + else + { + rc = RTSemEventWait(IScsiCmdSync.EventSem, RT_INDEFINITE_WAIT); + AssertRC(rc); + rc = IScsiCmdSync.rcCmd; + } + + RTSemEventDestroy(IScsiCmdSync.EventSem); + } + else + { + /* No I/O thread, execute in the current thread. */ + rc = pfnExec(pvUser); + } + + return rc; +} + + +static DECLCALLBACK(void) iscsiCommandAsyncComplete(PISCSIIMAGE pImage, int rcReq, void *pvUser) +{ + bool fComplete = true; + size_t cbTransfered = 0; + PSCSIREQ pScsiReq = (PSCSIREQ)pvUser; + + if (RT_SUCCESS(rcReq)) + ASMAtomicWriteU32(&pImage->cLoginsSinceIo, 0); + + if ( RT_SUCCESS(rcReq) + && pScsiReq->cbSense > 0) + { + /* Try again if possible. */ + if (pScsiReq->cSenseRetries > 0) + { + pScsiReq->cSenseRetries--; + pScsiReq->cbSense = sizeof(pScsiReq->abSense); + int rc = iscsiCommandAsync(pImage, pScsiReq, iscsiCommandAsyncComplete, pScsiReq); + if (RT_SUCCESS(rc)) + fComplete = false; + else + rcReq = pScsiReq->rcSense; + } + else + rcReq = pScsiReq->rcSense; + } + + if (fComplete) + { + if (pScsiReq->enmXfer == SCSIXFER_FROM_TARGET) + cbTransfered = pScsiReq->cbT2IData; + else if (pScsiReq->enmXfer == SCSIXFER_TO_TARGET) + cbTransfered = pScsiReq->cbI2TData; + else + AssertMsg(pScsiReq->enmXfer == SCSIXFER_NONE, ("To/From transfers are not supported yet\n")); + + /* Continue I/O context. */ + pImage->pIfIo->pfnIoCtxCompleted(pImage->pIfIo->Core.pvUser, + pScsiReq->pIoCtx, rcReq, + cbTransfered); + + RTMemFree(pScsiReq); + } +} + + +/** + * Internal. Free all allocated space for representing an image, and optionally + * delete the image from disk. + */ +static int iscsiFreeImage(PISCSIIMAGE pImage, bool fDelete) +{ + int rc = VINF_SUCCESS; + Assert(!fDelete); NOREF(fDelete); /* This MUST be false, the flag isn't supported. */ + + /* Freeing a never allocated image (e.g. because the open failed) is + * not signalled as an error. After all nothing bad happens. */ + if (pImage) + { + if (pImage->Mutex != NIL_RTSEMMUTEX) + { + /* Detaching only makes sense when the mutex is there. Otherwise the + * failure happened long before we could attach to the target. */ + iscsiExecSync(pImage, iscsiDetach, pImage); + RTSemMutexDestroy(pImage->Mutex); + pImage->Mutex = NIL_RTSEMMUTEX; + } + if (pImage->hThreadIo != NIL_RTTHREAD) + { + ASMAtomicXchgBool(&pImage->fRunning, false); + rc = iscsiIoThreadPoke(pImage); + AssertRC(rc); + + /* Wait for the thread to terminate. */ + rc = RTThreadWait(pImage->hThreadIo, RT_INDEFINITE_WAIT, NULL); + AssertRC(rc); + } + /* Destroy the socket. */ + if (pImage->Socket != NIL_VDSOCKET) + { + pImage->pIfNet->pfnSocketDestroy(pImage->Socket); + } + if (pImage->MutexReqQueue != NIL_RTSEMMUTEX) + { + RTSemMutexDestroy(pImage->MutexReqQueue); + pImage->MutexReqQueue = NIL_RTSEMMUTEX; + } + if (pImage->pszTargetName) + { + RTMemFree(pImage->pszTargetName); + pImage->pszTargetName = NULL; + } + if (pImage->pszTargetAddress) + { + RTMemFree(pImage->pszTargetAddress); + pImage->pszTargetAddress = NULL; + } + if (pImage->pszInitiatorName) + { + if (pImage->fAutomaticInitiatorName) + RTStrFree(pImage->pszInitiatorName); + else + RTMemFree(pImage->pszInitiatorName); + pImage->pszInitiatorName = NULL; + } + if (pImage->pszInitiatorUsername) + { + RTMemFree(pImage->pszInitiatorUsername); + pImage->pszInitiatorUsername = NULL; + } + if (pImage->pbInitiatorSecret) + { + RTMemFree(pImage->pbInitiatorSecret); + pImage->pbInitiatorSecret = NULL; + } + if (pImage->pszTargetUsername) + { + RTMemFree(pImage->pszTargetUsername); + pImage->pszTargetUsername = NULL; + } + if (pImage->pbTargetSecret) + { + RTMemFree(pImage->pbTargetSecret); + pImage->pbTargetSecret = NULL; + } + if (pImage->pvRecvPDUBuf) + { + RTMemFree(pImage->pvRecvPDUBuf); + pImage->pvRecvPDUBuf = NULL; + } + if (pImage->pszHostname) + { + RTMemFree(pImage->pszHostname); + pImage->pszHostname = NULL; + } + + pImage->cbRecvPDUResidual = 0; + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** + * Inits the basic iSCSI image state, allocating vital resources. + * + * @returns VBox status code. + * @param pImage The iSCSI image instance. + */ +static int iscsiOpenImageInit(PISCSIIMAGE pImage) +{ + int rc = VINF_SUCCESS; + + /* Get error signalling interface. */ + pImage->pIfError = VDIfErrorGet(pImage->pVDIfsDisk); + + /* Get TCP network stack interface. */ + pImage->pIfNet = VDIfTcpNetGet(pImage->pVDIfsImage); + if (pImage->pIfNet) + { + /* Get configuration interface. */ + pImage->pIfConfig = VDIfConfigGet(pImage->pVDIfsImage); + if (pImage->pIfConfig) + { + /* Get I/O interface. */ + pImage->pIfIo = VDIfIoIntGet(pImage->pVDIfsImage); + if (pImage->pIfIo) + { + /* This ISID will be adjusted later to make it unique on this host. */ + pImage->pszHostname = NULL; + pImage->uPort = 0; + pImage->Socket = NIL_VDSOCKET; + pImage->ISID = 0x800000000000ULL | 0x001234560000ULL; + pImage->cISCSIRetries = 10; + pImage->state = ISCSISTATE_FREE; + pImage->cLoginsSinceIo = 0; + pImage->Mutex = NIL_RTSEMMUTEX; + pImage->MutexReqQueue = NIL_RTSEMMUTEX; + pImage->pszInitiatorUsername = NULL; + pImage->pbInitiatorSecret = NULL; + pImage->cbInitiatorSecret = 0; + pImage->pszTargetUsername = NULL; + pImage->pbTargetSecret = NULL; + pImage->cbTargetSecret = 0; + + memset(pImage->aCmdsWaiting, 0, sizeof(pImage->aCmdsWaiting)); + pImage->cbRecvPDUResidual = 0; + + pImage->pvRecvPDUBuf = RTMemAlloc(ISCSI_RECV_PDU_BUFFER_SIZE); + pImage->cbRecvPDUBuf = ISCSI_RECV_PDU_BUFFER_SIZE; + if (!pImage->pvRecvPDUBuf) + rc = VERR_NO_MEMORY; + + if (RT_SUCCESS(rc)) + rc = RTSemMutexCreate(&pImage->Mutex); + if (RT_SUCCESS(rc)) + rc = RTSemMutexCreate(&pImage->MutexReqQueue); + } + else + rc = vdIfError(pImage->pIfError, VERR_VD_UNKNOWN_INTERFACE, + RT_SRC_POS, N_("iSCSI: I/O interface missing")); + } + else + rc = vdIfError(pImage->pIfError, VERR_VD_UNKNOWN_INTERFACE, + RT_SRC_POS, N_("iSCSI: configuration interface missing")); + } + else + rc = vdIfError(pImage->pIfError, VERR_VD_UNKNOWN_INTERFACE, + RT_SRC_POS, N_("iSCSI: TCP network stack interface missing")); + + return rc; +} + +/** + * Parses the user supplied config before opening the connection to the target. + * + * @returns VBox status code. + * @param pImage The iSCSI image instance. + */ +static int iscsiOpenImageParseCfg(PISCSIIMAGE pImage) +{ + char *pszLUN = NULL, *pszLUNInitial = NULL; + bool fLunEncoded = false; + uint32_t uWriteSplitDef = 0; + uint32_t uTimeoutDef = 0; + uint64_t uCfgTmp = 0; + bool fHostIPDef = false; + bool fDumpMalformedPacketsDef = false; + + int rc = RTStrToUInt32Full(s_iscsiConfigDefaultWriteSplit, 0, &uWriteSplitDef); + AssertRC(rc); + rc = RTStrToUInt32Full(s_iscsiConfigDefaultTimeout, 0, &uTimeoutDef); + AssertRC(rc); + rc = RTStrToUInt64Full(s_iscsiConfigDefaultHostIPStack, 0, &uCfgTmp); + AssertRC(rc); + fHostIPDef = RT_BOOL(uCfgTmp); + rc = RTStrToUInt64Full(s_iscsiConfigDefaultDumpMalformedPackets, 0, &uCfgTmp); + AssertRC(rc); + fDumpMalformedPacketsDef = RT_BOOL(uCfgTmp); + + /* Validate configuration, detect unknown keys. */ + if (!VDCFGAreKeysValid(pImage->pIfConfig, + "TargetName\0" + "InitiatorName\0" + "LUN\0" + "TargetAddress\0" + "InitiatorUsername\0" + "InitiatorSecret\0" + "InitiatorSecretEncrypted\0" + "TargetUsername\0" + "TargetSecret\0" + "WriteSplit\0" + "Timeout\0" + "HostIPStack\0" + "DumpMalformedPackets\0")) + return vdIfError(pImage->pIfError, VERR_VD_UNKNOWN_CFG_VALUES, RT_SRC_POS, N_("iSCSI: configuration error: unknown configuration keys present")); + + /* Query the iSCSI upper level configuration. */ + rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "TargetName", &pImage->pszTargetName); + if (RT_FAILURE(rc)) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read TargetName as string")); + + rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "InitiatorName", &pImage->pszInitiatorName); + if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) + pImage->fAutomaticInitiatorName = true; + else if (RT_FAILURE(rc)) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read InitiatorName as string")); + + rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "LUN", &pszLUN); + if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) + { + rc = VINF_SUCCESS; + pImage->fAutomaticLUN = true; + } + else if (RT_FAILURE(rc)) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read LUN as string")); + + if (pImage->fAutomaticLUN) + pImage->LUN = 0; /* Default to LUN 0. */ + else + { + pszLUNInitial = pszLUN; + if (!strncmp(pszLUN, "enc", 3)) + { + fLunEncoded = true; + pszLUN += 3; + } + rc = RTStrToUInt64Full(pszLUN, 0, &pImage->LUN); + if (RT_FAILURE(rc)) + rc = vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to convert LUN to integer")); + + RTMemFree(pszLUNInitial); + } + if (RT_SUCCESS(rc) && !fLunEncoded) + { + if (pImage->LUN <= 255) + pImage->LUN = pImage->LUN << 48; /* uses peripheral device addressing method */ + else if (pImage->LUN <= 16383) + pImage->LUN = (pImage->LUN << 48) | RT_BIT_64(62); /* uses flat space addressing method */ + else + rc = vdIfError(pImage->pIfError, VERR_OUT_OF_RANGE, RT_SRC_POS, N_("iSCSI: configuration error: LUN number out of range (0-16383)")); + } + + if (RT_FAILURE(rc)) + return rc; + + rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "TargetAddress", &pImage->pszTargetAddress); + if (RT_FAILURE(rc)) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read TargetAddress as string")); + + rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "InitiatorUsername", &pImage->pszInitiatorUsername); + if (RT_FAILURE(rc) && rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read InitiatorUsername as string")); + + rc = VDCFGQueryBytesAlloc(pImage->pIfConfig, "InitiatorSecret", + (void **)&pImage->pbInitiatorSecret, &pImage->cbInitiatorSecret); + if (RT_FAILURE(rc) && rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read InitiatorSecret as byte string")); + + void *pvInitiatorSecretEncrypted; + size_t cbInitiatorSecretEncrypted; + rc = VDCFGQueryBytesAlloc(pImage->pIfConfig, "InitiatorSecretEncrypted", + &pvInitiatorSecretEncrypted, &cbInitiatorSecretEncrypted); + if (RT_SUCCESS(rc)) + { + RTMemFree(pvInitiatorSecretEncrypted); + if (!pImage->pbInitiatorSecret) + { + /* we have an encrypted initiator secret but not an unencrypted one */ + return vdIfError(pImage->pIfError, VERR_VD_ISCSI_SECRET_ENCRYPTED, RT_SRC_POS, N_("iSCSI: initiator secret not decrypted")); + } + } + + rc = VDCFGQueryStringAlloc(pImage->pIfConfig, "TargetUsername", &pImage->pszTargetUsername); + if (RT_FAILURE(rc) && rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read TargetUsername as string")); + + rc = VDCFGQueryBytesAlloc(pImage->pIfConfig, "TargetSecret", + (void **)&pImage->pbTargetSecret, &pImage->cbTargetSecret); + if (RT_FAILURE(rc) && rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read TargetSecret as byte string")); + + rc = VDCFGQueryU32Def(pImage->pIfConfig, "WriteSplit", &pImage->cbWriteSplit, uWriteSplitDef); + if (RT_FAILURE(rc)) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read WriteSplit as U32")); + + /* Query the iSCSI lower level configuration. */ + rc = VDCFGQueryU32Def(pImage->pIfConfig, "Timeout", &pImage->uReadTimeout, uTimeoutDef); + if (RT_FAILURE(rc)) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read Timeout as U32")); + + rc = VDCFGQueryBoolDef(pImage->pIfConfig, "HostIPStack", &pImage->fHostIP, fHostIPDef); + if (RT_FAILURE(rc)) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read HostIPStack as boolean")); + + rc = VDCFGQueryBoolDef(pImage->pIfConfig, "DumpMalformedPackets", + &pImage->fDumpMalformedPackets, fDumpMalformedPacketsDef); + if (RT_FAILURE(rc)) + return vdIfError(pImage->pIfError, rc, RT_SRC_POS, N_("iSCSI: configuration error: failed to read DumpMalformedPackets as boolean")); + + return VINF_SUCCESS; +} + +/** + * Creates the necessary socket structure. + * + * @returns VBox status code. + * @param pImage The iSCSI image instance. + */ +static int iscsiOpenImageSocketCreate(PISCSIIMAGE pImage) +{ + /* Create the socket structure. */ + int rc = pImage->pIfNet->pfnSocketCreate(VD_INTERFACETCPNET_CONNECT_EXTENDED_SELECT, + &pImage->Socket); + if (RT_SUCCESS(rc)) + { + pImage->fExtendedSelectSupported = true; + pImage->fRunning = true; + rc = RTThreadCreate(&pImage->hThreadIo, iscsiIoThreadWorker, pImage, 0, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "iSCSI-Io"); + if (RT_FAILURE(rc)) + LogFunc(("Creating iSCSI I/O thread failed rc=%Rrc\n", rc)); + } + else if (rc == VERR_NOT_SUPPORTED) + { + /* Async I/O is not supported without extended select. */ + if ((pImage->uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO)) + LogFunc(("Extended select is not supported by the interface but async I/O is requested -> %Rrc\n", rc)); + else + { + pImage->fExtendedSelectSupported = false; + rc = pImage->pIfNet->pfnSocketCreate(0, &pImage->Socket); + } + } + + if (RT_FAILURE(rc)) + LogFunc(("Creating socket failed -> %Rrc\n", rc)); + + return rc; +} + +/** + * Issues a REPORT LUNS to the target. + * + * @returns VBox status code. + * @param pImage The iSCSI image instance. + */ +static int iscsiOpenImageReportLuns(PISCSIIMAGE pImage) +{ + SCSIREQ sr; + RTSGSEG DataSeg; + uint8_t rlundata[16]; + + /* + * Inquire available LUNs. + */ + RT_ZERO(sr.abCDB); + sr.abCDB[0] = SCSI_REPORT_LUNS; + sr.abCDB[1] = 0; /* reserved */ + sr.abCDB[2] = 0; /* reserved */ + sr.abCDB[3] = 0; /* reserved */ + sr.abCDB[4] = 0; /* reserved */ + sr.abCDB[5] = 0; /* reserved */ + sr.abCDB[6] = sizeof(rlundata) >> 24; + sr.abCDB[7] = (sizeof(rlundata) >> 16) & 0xff; + sr.abCDB[8] = (sizeof(rlundata) >> 8) & 0xff; + sr.abCDB[9] = sizeof(rlundata) & 0xff; + sr.abCDB[10] = 0; /* reserved */ + sr.abCDB[11] = 0; /* control */ + + DataSeg.pvSeg = rlundata; + DataSeg.cbSeg = sizeof(rlundata); + + sr.enmXfer = SCSIXFER_FROM_TARGET; + sr.cbCDB = 12; + sr.cbI2TData = 0; + sr.paI2TSegs = NULL; + sr.cI2TSegs = 0; + sr.cbT2IData = DataSeg.cbSeg; + sr.paT2ISegs = &DataSeg; + sr.cT2ISegs = 1; + sr.cbSense = sizeof(sr.abSense); + int rc = iscsiCommandSync(pImage, &sr, false, VERR_INVALID_STATE); + if (RT_FAILURE(rc)) + LogRel(("iSCSI: Could not get LUN info for target %s, rc=%Rrc\n", pImage->pszTargetName, rc)); + + /* If there is a single LUN on the target, then either verify that it matches the explicitly + * configured LUN, or just use it if a LUN was not configured (defaulted to 0). For multi-LUN targets, + * require a correctly configured LUN. + */ + uint32_t cbLuns = (rlundata[0] << 24) | (rlundata[1] << 16) | (rlundata[2] << 8) | rlundata[3]; + unsigned cLuns = cbLuns / 8; + + /* Dig out the first LUN. */ + uint64_t uTgtLun = 0; + if ((rlundata[8] & 0xc0) == 0) + { + /* Single-byte LUN in 0-255 range. */ + uTgtLun = rlundata[9]; + } + else if ((rlundata[8] & 0xc0) == 0x40) + { + /* Two-byte LUN in 256-16383 range. */ + uTgtLun = rlundata[9] | ((rlundata[8] & 0x3f) << 8); + uTgtLun = (uTgtLun << 48) | RT_BIT_64(62); + } + else + rc = vdIfError(pImage->pIfError, VERR_OUT_OF_RANGE, RT_SRC_POS, N_("iSCSI: Reported LUN number out of range (0-16383)")); + if (RT_FAILURE(rc)) + return rc; + + LogRel(("iSCSI: %u LUN(s), first LUN %RX64\n", cLuns, uTgtLun)); + + /* Convert the LUN back into the 64-bit format. */ + if (uTgtLun <= 255) + uTgtLun = uTgtLun << 48; + else + { + Assert(uTgtLun <= 16383); + uTgtLun = (uTgtLun << 48) | RT_BIT_64(62); + } + + if (cLuns == 1) + { + /* NB: It is valid to have a single LUN other than zero, at least in SPC-3. */ + if (pImage->fAutomaticLUN) + pImage->LUN = uTgtLun; + else if (pImage->LUN != uTgtLun) + rc = vdIfError(pImage->pIfError, VERR_VD_ISCSI_INVALID_TYPE, RT_SRC_POS, N_("iSCSI: configuration error: Configured LUN does not match what target provides")); + } + + return rc; +} + +/** + * Issues the INQUIRY command to the target and checks for the correct device type. + * + * @returns VBox status code. + * @param pImage The iSCSI image instance. + */ +static int iscsiOpenImageInquiry(PISCSIIMAGE pImage) +{ + SCSIREQ sr; + RTSGSEG DataSeg; + uint8_t data8[8]; + + /* + * Inquire device characteristics - no tapes, scanners etc., please. + */ + RT_ZERO(sr.abCDB); + sr.abCDB[0] = SCSI_INQUIRY; + sr.abCDB[1] = 0; /* reserved */ + sr.abCDB[2] = 0; /* reserved */ + sr.abCDB[3] = 0; /* reserved */ + sr.abCDB[4] = sizeof(data8); + sr.abCDB[5] = 0; /* control */ + + DataSeg.pvSeg = data8; + DataSeg.cbSeg = sizeof(data8); + + sr.enmXfer = SCSIXFER_FROM_TARGET; + sr.cbCDB = 6; + sr.cbI2TData = 0; + sr.paI2TSegs = NULL; + sr.cI2TSegs = 0; + sr.cbT2IData = DataSeg.cbSeg; + sr.paT2ISegs = &DataSeg; + sr.cT2ISegs = 1; + sr.cbSense = sizeof(sr.abSense); + int rc = iscsiCommandSync(pImage, &sr, true /* fRetry */, VERR_INVALID_STATE); + if (RT_SUCCESS(rc)) + { + uint8_t devType = (sr.cbT2IData > 0) ? data8[0] & SCSI_DEVTYPE_MASK : 255; + if (devType == SCSI_DEVTYPE_DISK) + { + uint8_t uCmdQueue = (sr.cbT2IData >= 8) ? data8[7] & SCSI_INQUIRY_CMDQUE_MASK : 0; + if (uCmdQueue > 0) + pImage->fCmdQueuingSupported = true; + else if (pImage->uOpenFlags & VD_OPEN_FLAGS_ASYNC_IO) + rc = VERR_NOT_SUPPORTED; + else + LogRel(("iSCSI: target address %s, target name %s, %s command queuing\n", + pImage->pszTargetAddress, pImage->pszTargetName, + pImage->fCmdQueuingSupported ? "supports" : "doesn't support")); + } + else + { + rc = vdIfError(pImage->pIfError, VERR_VD_ISCSI_INVALID_TYPE, + RT_SRC_POS, N_("iSCSI: target address %s, target name %s, SCSI LUN %lld reports device type=%u"), + pImage->pszTargetAddress, pImage->pszTargetName, + pImage->LUN, devType); + LogRel(("iSCSI: Unsupported SCSI peripheral device type %d for target %s\n", devType & SCSI_DEVTYPE_MASK, pImage->pszTargetName)); + } + } + else + LogRel(("iSCSI: Could not get INQUIRY info for target %s, rc=%Rrc\n", pImage->pszTargetName, rc)); + + return rc; +} + +/** + * Checks that the target allows write access if the caller requested it. + * + * @returns VBox status code. + * @param pImage The iSCSI image instance. + */ +static int iscsiOpenImageCheckWriteAccess(PISCSIIMAGE pImage) +{ + SCSIREQ sr; + RTSGSEG DataSeg; + uint8_t data4[4]; + + /* + * Query write disable bit in the device specific parameter entry in the + * mode parameter header. Refuse read/write opening of read only disks. + */ + RT_ZERO(sr.abCDB); + sr.abCDB[0] = SCSI_MODE_SENSE_6; + sr.abCDB[1] = 0; /* dbd=0/reserved */ + sr.abCDB[2] = 0x3f; /* pc=0/page code=0x3f, ask for all pages */ + sr.abCDB[3] = 0; /* subpage code=0, return everything in page_0 format */ + sr.abCDB[4] = sizeof(data4); /* allocation length=4 */ + sr.abCDB[5] = 0; /* control */ + + DataSeg.pvSeg = data4; + DataSeg.cbSeg = sizeof(data4); + + sr.enmXfer = SCSIXFER_FROM_TARGET; + sr.cbCDB = 6; + sr.cbI2TData = 0; + sr.paI2TSegs = NULL; + sr.cI2TSegs = 0; + sr.cbT2IData = DataSeg.cbSeg; + sr.paT2ISegs = &DataSeg; + sr.cT2ISegs = 1; + sr.cbSense = sizeof(sr.abSense); + int rc = iscsiCommandSync(pImage, &sr, true /* fRetry */, VERR_INVALID_STATE); + if (RT_SUCCESS(rc)) + { + pImage->fTargetReadOnly = !!(data4[2] & 0x80); + if (!(pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) && pImage->fTargetReadOnly) + rc = VERR_VD_IMAGE_READ_ONLY; + } + else + LogRel(("iSCSI: Could not get MODE SENSE info for target %s, rc=%Rrc\n", pImage->pszTargetName, rc)); + + return rc; +} + +/** + * Queries the media and sector size of the target. + * + * @returns VBox status code. + * @param pImage The iSCSI image instance. + */ +static int iscsiOpenImageQueryTargetSizes(PISCSIIMAGE pImage) +{ + SCSIREQ sr; + RTSGSEG DataSeg; + uint8_t data12[12]; + + /* + * Determine sector size and capacity of the volume immediately. + */ + RT_ZERO(data12); + RT_ZERO(sr.abCDB); + sr.abCDB[0] = SCSI_SERVICE_ACTION_IN_16; + sr.abCDB[1] = SCSI_SVC_ACTION_IN_READ_CAPACITY_16; /* subcommand */ + sr.abCDB[10+3] = sizeof(data12); /* allocation length (dword) */ + + DataSeg.pvSeg = data12; + DataSeg.cbSeg = sizeof(data12); + + sr.enmXfer = SCSIXFER_FROM_TARGET; + sr.cbCDB = 16; + sr.cbI2TData = 0; + sr.paI2TSegs = NULL; + sr.cI2TSegs = 0; + sr.cbT2IData = DataSeg.cbSeg; + sr.paT2ISegs = &DataSeg; + sr.cT2ISegs = 1; + sr.cbSense = sizeof(sr.abSense); + + int rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + bool fEnd = false; + uint8_t cMaxRetries = 10; + do + { + switch (sr.status) + { + case SCSI_STATUS_OK: + { + pImage->cVolume = RT_BE2H_U64(*(uint64_t *)&data12[0]); + pImage->cVolume++; + pImage->cbSector = RT_BE2H_U32(*(uint32_t *)&data12[8]); + pImage->cbSize = pImage->cVolume * pImage->cbSector; + if (pImage->cVolume == 0 || pImage->cbSize < pImage->cVolume) + { + rc = vdIfError(pImage->pIfError, VERR_VD_ISCSI_INVALID_TYPE, + RT_SRC_POS, N_("iSCSI: target address %s, target name %s, SCSI LUN %lld reports media sector count=%llu sector size=%u"), + pImage->pszTargetAddress, pImage->pszTargetName, + pImage->LUN, pImage->cVolume, pImage->cbSector); + } + fEnd = true; + break; + } + case SCSI_STATUS_CHECK_CONDITION: + { + if((sr.abSense[2] & 0x0f) == SCSI_SENSE_UNIT_ATTENTION) + { + if( sr.abSense[12] == SCSI_ASC_POWER_ON_RESET_BUS_DEVICE_RESET_OCCURRED + && sr.abSense[13] == SCSI_ASCQ_POWER_ON_RESET_BUS_DEVICE_RESET_OCCURRED) + { + /** @todo for future: prepare and send command "REQUEST SENSE" which will + * return the status of target and will clear any unit attention + * condition that it reports */ + rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS); + if (RT_FAILURE(rc)) + fEnd = true; + cMaxRetries--; + break; + } + } + break; + } + default: + { + rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS); + if (RT_FAILURE(rc)) + fEnd = true; + cMaxRetries--; + break; + } + } + if (!cMaxRetries) + fEnd = true; + } while(!fEnd); + } + else + { + uint8_t data8[8]; + + RT_ZERO(data8); + sr.abCDB[0] = SCSI_READ_CAPACITY; + sr.abCDB[1] = 0; /* reserved */ + sr.abCDB[2] = 0; /* reserved */ + sr.abCDB[3] = 0; /* reserved */ + sr.abCDB[4] = 0; /* reserved */ + sr.abCDB[5] = 0; /* reserved */ + sr.abCDB[6] = 0; /* reserved */ + sr.abCDB[7] = 0; /* reserved */ + sr.abCDB[8] = 0; /* reserved */ + sr.abCDB[9] = 0; /* control */ + + DataSeg.pvSeg = data8; + DataSeg.cbSeg = sizeof(data8); + + sr.enmXfer = SCSIXFER_FROM_TARGET; + sr.cbCDB = 10; + sr.cbI2TData = 0; + sr.paI2TSegs = NULL; + sr.cI2TSegs = 0; + sr.cbT2IData = DataSeg.cbSeg; + sr.paT2ISegs = &DataSeg; + sr.cT2ISegs = 1; + sr.cbSense = sizeof(sr.abSense); + rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + bool fEnd = false; + uint8_t cMaxRetries = 10; + do + { + switch (sr.status) + { + case SCSI_STATUS_OK: + { + pImage->cVolume = (data8[0] << 24) | (data8[1] << 16) | (data8[2] << 8) | data8[3]; + pImage->cVolume++; + pImage->cbSector = (data8[4] << 24) | (data8[5] << 16) | (data8[6] << 8) | data8[7]; + pImage->cbSize = pImage->cVolume * pImage->cbSector; + if (pImage->cVolume == 0) + { + rc = vdIfError(pImage->pIfError, VERR_VD_ISCSI_INVALID_TYPE, + RT_SRC_POS, N_("iSCSI: fallback capacity detection for target address %s, target name %s, SCSI LUN %lld reports media sector count=%llu sector size=%u"), + pImage->pszTargetAddress, pImage->pszTargetName, + pImage->LUN, pImage->cVolume, pImage->cbSector); + } + + fEnd = true; + break; + } + case SCSI_STATUS_CHECK_CONDITION: + { + if((sr.abSense[2] & 0x0f) == SCSI_SENSE_UNIT_ATTENTION) + { + if( sr.abSense[12] == SCSI_ASC_POWER_ON_RESET_BUS_DEVICE_RESET_OCCURRED + && sr.abSense[13] == SCSI_ASCQ_POWER_ON_RESET_BUS_DEVICE_RESET_OCCURRED) + { + /** @todo for future: prepare and send command "REQUEST SENSE" which will + * return the status of target and will clear any unit attention + * condition that it reports */ + rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS); + if (RT_FAILURE(rc)) + fEnd = true; + cMaxRetries--; + break; + + } + } + break; + } + default: + { + rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS); + if (RT_FAILURE(rc)) + fEnd = true; + cMaxRetries--; + break; + } + } + if (!cMaxRetries) + fEnd = true; + } while(!fEnd); + } + else + LogRel(("iSCSI: Could not determine capacity of target %s, rc=%Rrc\n", pImage->pszTargetName, rc)); + } + + return rc; +} + +/** + * Queries the state of the read/write caches and tries to enable them if disabled. + * + * @returns VBox status code. + * @param pImage The iSCSI image instance. + */ +static int iscsiOpenImageEnableReadWriteCache(PISCSIIMAGE pImage) +{ + /* + * Check the read and write cache bits. + * Try to enable the cache if it is disabled. + * + * We already checked that this is a block access device. No need + * to do it again. + */ + SCSIREQ sr; + RTSGSEG DataSeg; + uint8_t aCachingModePage[32]; + + memset(aCachingModePage, '\0', sizeof(aCachingModePage)); + sr.abCDB[0] = SCSI_MODE_SENSE_6; + sr.abCDB[1] = 0; + sr.abCDB[2] = (0x00 << 6) | (0x08 & 0x3f); /* Current values and caching mode page */ + sr.abCDB[3] = 0; /* Sub page code. */ + sr.abCDB[4] = sizeof(aCachingModePage) & 0xff; + sr.abCDB[5] = 0; + + DataSeg.pvSeg = aCachingModePage; + DataSeg.cbSeg = sizeof(aCachingModePage); + + sr.enmXfer = SCSIXFER_FROM_TARGET; + sr.cbCDB = 6; + sr.cbI2TData = 0; + sr.paI2TSegs = NULL; + sr.cI2TSegs = 0; + sr.cbT2IData = DataSeg.cbSeg; + sr.paT2ISegs = &DataSeg; + sr.cT2ISegs = 1; + sr.cbSense = sizeof(sr.abSense); + int rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS); + if ( RT_SUCCESS(rc) + && (sr.status == SCSI_STATUS_OK) + && (aCachingModePage[0] >= 15) + && (aCachingModePage[4 + aCachingModePage[3]] & 0x3f) == 0x08 + && (aCachingModePage[4 + aCachingModePage[3] + 1] > 3)) + { + uint32_t Offset = 4 + aCachingModePage[3]; + /* + * Check if the read and/or the write cache is disabled. + * The write cache is disabled if bit 2 (WCE) is zero and + * the read cache is disabled if bit 0 (RCD) is set. + */ + if (!ASMBitTest(&aCachingModePage[Offset + 2], 2) || ASMBitTest(&aCachingModePage[Offset + 2], 0)) + { + /* + * Write Cache Enable (WCE) bit is zero or the Read Cache Disable (RCD) is one + * So one of the caches is disabled. Enable both caches. + * The rest is unchanged. + */ + ASMBitSet(&aCachingModePage[Offset + 2], 2); + ASMBitClear(&aCachingModePage[Offset + 2], 0); + + sr.abCDB[0] = SCSI_MODE_SELECT_6; + sr.abCDB[1] = 0; /* Don't write the page into NV RAM. */ + sr.abCDB[2] = 0; + sr.abCDB[3] = 0; + sr.abCDB[4] = sizeof(aCachingModePage) & 0xff; + sr.abCDB[5] = 0; + + DataSeg.pvSeg = aCachingModePage; + DataSeg.cbSeg = sizeof(aCachingModePage); + + sr.enmXfer = SCSIXFER_TO_TARGET; + sr.cbCDB = 6; + sr.cbI2TData = DataSeg.cbSeg; + sr.paI2TSegs = &DataSeg; + sr.cI2TSegs = 1; + sr.cbT2IData = 0; + sr.paT2ISegs = NULL; + sr.cT2ISegs = 0; + sr.cbSense = sizeof(sr.abSense); + sr.status = 0; + rc = iscsiCommandSync(pImage, &sr, false /* fRetry */, VINF_SUCCESS); + if ( RT_SUCCESS(rc) + && (sr.status == SCSI_STATUS_OK)) + LogRel(("iSCSI: Enabled read and write cache of target %s\n", pImage->pszTargetName)); + else + { + /* Log failures but continue. */ + LogRel(("iSCSI: Could not enable read and write cache of target %s, rc=%Rrc status=%#x\n", + pImage->pszTargetName, rc, sr.status)); + LogRel(("iSCSI: Sense:\n%.*Rhxd\n", sr.cbSense, sr.abSense)); + rc = VINF_SUCCESS; + } + } + } + else + { + /* Log errors but continue. */ + LogRel(("iSCSI: Could not check write cache of target %s, rc=%Rrc, got mode page %#x\n", pImage->pszTargetName, rc, aCachingModePage[0] & 0x3f)); + LogRel(("iSCSI: Sense:\n%.*Rhxd\n", sr.cbSense, sr.abSense)); + rc = VINF_SUCCESS; + } + + return rc; +} + +/** + * Internal: Open an image, constructing all necessary data structures. + */ +static int iscsiOpenImage(PISCSIIMAGE pImage, unsigned uOpenFlags) +{ + pImage->uOpenFlags = uOpenFlags; + + int rc = iscsiOpenImageInit(pImage); + if (RT_SUCCESS(rc)) + rc = iscsiOpenImageParseCfg(pImage); + + if (RT_SUCCESS(rc)) + { + /* Don't actually establish iSCSI transport connection if this is just an + * open to query the image information and the host IP stack isn't used. + * Even trying is rather useless, as in this context the InTnet IP stack + * isn't present. Returning dummies is the best possible result anyway. */ + if ((uOpenFlags & VD_OPEN_FLAGS_INFO) && !pImage->fHostIP) + LogFunc(("Not opening the transport connection as IntNet IP stack is not available. Will return dummies\n")); + else + { + rc = iscsiOpenImageSocketCreate(pImage); + if (RT_SUCCESS(rc)) + { + /* + * Attach to the iSCSI target. This implicitly establishes the iSCSI + * transport connection. + */ + rc = iscsiExecSync(pImage, iscsiAttach, pImage); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("target '%s' opened successfully\n", pImage->pszTargetName)); + + rc = iscsiOpenImageReportLuns(pImage); + if (RT_SUCCESS(rc)) + rc = iscsiOpenImageInquiry(pImage); + if (RT_SUCCESS(rc)) + rc = iscsiOpenImageCheckWriteAccess(pImage); + if (RT_SUCCESS(rc)) + rc = iscsiOpenImageQueryTargetSizes(pImage); + if (RT_SUCCESS(rc)) + rc = iscsiOpenImageEnableReadWriteCache(pImage); + } + else + LogRel(("iSCSI: could not open target %s, rc=%Rrc\n", pImage->pszTargetName, rc)); + } + } + } + + if (RT_SUCCESS(rc)) + { + PVDREGIONDESC pRegion = &pImage->RegionList.aRegions[0]; + pImage->RegionList.fFlags = 0; + pImage->RegionList.cRegions = 1; + + pRegion->offRegion = 0; /* Disk start. */ + pRegion->cbBlock = pImage->cbSector; + pRegion->enmDataForm = VDREGIONDATAFORM_RAW; + pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE; + pRegion->cbData = pImage->cbSector; + pRegion->cbMetadata = 0; + pRegion->cRegionBlocksOrBytes = pImage->cbSize; + } + else + iscsiFreeImage(pImage, false); + return rc; +} + + +/** @copydoc VDIMAGEBACKEND::pfnProbe */ +static DECLCALLBACK(int) iscsiProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk, + PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType) +{ + RT_NOREF(pszFilename, pVDIfsDisk, pVDIfsImage, enmDesiredType, penmType); + LogFlowFunc(("pszFilename=\"%s\"\n", pszFilename)); + + /* iSCSI images can't be checked for validity this way, as the filename + * just can't supply enough configuration information. */ + int rc = VERR_VD_ISCSI_INVALID_HEADER; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnOpen */ +static DECLCALLBACK(int) iscsiOpen(const char *pszFilename, unsigned uOpenFlags, + PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage, + VDTYPE enmType, void **ppBackendData) +{ + RT_NOREF1(enmType); /**< @todo r=klaus make use of the type info. */ + + LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n", + pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData)); + int rc; + + /* Check open flags. All valid flags are supported. */ + AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertReturn(*pszFilename != '\0', VERR_INVALID_PARAMETER); + + PISCSIIMAGE pImage = (PISCSIIMAGE)RTMemAllocZ(RT_UOFFSETOF(ISCSIIMAGE, RegionList.aRegions[1])); + if (RT_LIKELY(pImage)) + { + pImage->pszFilename = pszFilename; + pImage->pszInitiatorName = NULL; + pImage->pszTargetName = NULL; + pImage->pszTargetAddress = NULL; + pImage->pszInitiatorUsername = NULL; + pImage->pbInitiatorSecret = NULL; + pImage->pszTargetUsername = NULL; + pImage->pbTargetSecret = NULL; + pImage->paCurrReq = NULL; + pImage->pvRecvPDUBuf = NULL; + pImage->pszHostname = NULL; + pImage->pVDIfsDisk = pVDIfsDisk; + pImage->pVDIfsImage = pVDIfsImage; + pImage->cLogRelErrors = 0; + + rc = iscsiOpenImage(pImage, uOpenFlags); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("target %s cVolume %d, cbSector %d\n", pImage->pszTargetName, pImage->cVolume, pImage->cbSector)); + LogRel(("iSCSI: target address %s, target name %s, SCSI LUN %lld\n", pImage->pszTargetAddress, pImage->pszTargetName, pImage->LUN)); + *ppBackendData = pImage; + } + else + RTMemFree(pImage); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnCreate */ +static DECLCALLBACK(int) iscsiCreate(const char *pszFilename, uint64_t cbSize, + unsigned uImageFlags, const char *pszComment, + PCVDGEOMETRY pPCHSGeometry, PCVDGEOMETRY pLCHSGeometry, + PCRTUUID pUuid, unsigned uOpenFlags, + unsigned uPercentStart, unsigned uPercentSpan, + PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage, + PVDINTERFACE pVDIfsOperation, VDTYPE enmType, + void **ppBackendData) +{ + RT_NOREF8(pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags); + RT_NOREF7(uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData); + LogFlowFunc(("pszFilename=\"%s\" cbSize=%llu uImageFlags=%#x pszComment=\"%s\" pPCHSGeometry=%#p pLCHSGeometry=%#p Uuid=%RTuuid uOpenFlags=%#x uPercentStart=%u uPercentSpan=%u pVDIfsDisk=%#p pVDIfsImage=%#p pVDIfsOperation=%#p enmType=%u ppBackendData=%#p", + pszFilename, cbSize, uImageFlags, pszComment, pPCHSGeometry, pLCHSGeometry, pUuid, uOpenFlags, uPercentStart, uPercentSpan, pVDIfsDisk, pVDIfsImage, pVDIfsOperation, enmType, ppBackendData)); + int rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnClose */ +static DECLCALLBACK(int) iscsiClose(void *pBackendData, bool fDelete) +{ + LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + int rc; + + Assert(!fDelete); /* This flag is unsupported. */ + + rc = iscsiFreeImage(pImage, fDelete); + RTMemFree(pImage); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnRead */ +static DECLCALLBACK(int) iscsiRead(void *pBackendData, uint64_t uOffset, size_t cbToRead, + PVDIOCTX pIoCtx, size_t *pcbActuallyRead) +{ + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + int rc = VINF_SUCCESS; + + LogFlowFunc(("pBackendData=%p uOffset=%#llx pIoCtx=%#p cbToRead=%u pcbActuallyRead=%p\n", + pBackendData, uOffset, pIoCtx, cbToRead, pcbActuallyRead)); + + if ( uOffset + cbToRead > pImage->cbSize + || cbToRead == 0) + return VERR_INVALID_PARAMETER; + + /* + * Clip read size to a value which is supported by the target. + */ + cbToRead = RT_MIN(cbToRead, pImage->cbRecvDataLength); + + unsigned cT2ISegs = 0; + size_t cbSegs = 0; + + /* Get the number of segments. */ + cbSegs = pImage->pIfIo->pfnIoCtxSegArrayCreate(pImage->pIfIo->Core.pvUser, pIoCtx, + NULL, &cT2ISegs, cbToRead); + Assert(cbSegs == cbToRead); + + PSCSIREQ pReq = (PSCSIREQ)RTMemAllocZ(RT_UOFFSETOF_DYN(SCSIREQ, aSegs[cT2ISegs])); + if (RT_LIKELY(pReq)) + { + uint64_t lba; + uint16_t tls; + uint8_t *pbCDB = &pReq->abCDB[0]; + size_t cbCDB; + + lba = uOffset / pImage->cbSector; + tls = (uint16_t)(cbToRead / pImage->cbSector); + + cbSegs = pImage->pIfIo->pfnIoCtxSegArrayCreate(pImage->pIfIo->Core.pvUser, pIoCtx, + &pReq->aSegs[0], + &cT2ISegs, cbToRead); + Assert(cbSegs == cbToRead); + + if (pImage->cVolume < _4G) + { + cbCDB = 10; + pbCDB[0] = SCSI_READ_10; + pbCDB[1] = 0; /* reserved */ + pbCDB[2] = (lba >> 24) & 0xff; + pbCDB[3] = (lba >> 16) & 0xff; + pbCDB[4] = (lba >> 8) & 0xff; + pbCDB[5] = lba & 0xff; + pbCDB[6] = 0; /* reserved */ + pbCDB[7] = (tls >> 8) & 0xff; + pbCDB[8] = tls & 0xff; + pbCDB[9] = 0; /* control */ + } + else + { + cbCDB = 16; + pbCDB[0] = SCSI_READ_16; + pbCDB[1] = 0; /* reserved */ + pbCDB[2] = (lba >> 56) & 0xff; + pbCDB[3] = (lba >> 48) & 0xff; + pbCDB[4] = (lba >> 40) & 0xff; + pbCDB[5] = (lba >> 32) & 0xff; + pbCDB[6] = (lba >> 24) & 0xff; + pbCDB[7] = (lba >> 16) & 0xff; + pbCDB[8] = (lba >> 8) & 0xff; + pbCDB[9] = lba & 0xff; + pbCDB[10] = 0; /* tls unused */ + pbCDB[11] = 0; /* tls unused */ + pbCDB[12] = (tls >> 8) & 0xff; + pbCDB[13] = tls & 0xff; + pbCDB[14] = 0; /* reserved */ + pbCDB[15] = 0; /* reserved */ + } + + pReq->enmXfer = SCSIXFER_FROM_TARGET; + pReq->cbCDB = cbCDB; + pReq->cbI2TData = 0; + pReq->paI2TSegs = NULL; + pReq->cI2TSegs = 0; + pReq->cbT2IData = cbToRead; + pReq->paT2ISegs = &pReq->aSegs[pReq->cI2TSegs]; + pReq->cbSense = sizeof(pReq->abSense); + pReq->cT2ISegs = cT2ISegs; + pReq->pIoCtx = pIoCtx; + pReq->cSenseRetries = 10; + pReq->rcSense = VERR_READ_ERROR; + + if (vdIfIoIntIoCtxIsSynchronous(pImage->pIfIo, pIoCtx)) + { + rc = iscsiCommandSync(pImage, pReq, true, VERR_READ_ERROR); + if (RT_FAILURE(rc)) + { + LogFlow(("iscsiCommandSync(%s, %#llx) -> %Rrc\n", pImage->pszTargetName, uOffset, rc)); + *pcbActuallyRead = 0; + } + else + *pcbActuallyRead = pReq->cbT2IData; + } + else + { + rc = iscsiCommandAsync(pImage, pReq, iscsiCommandAsyncComplete, pReq); + if (RT_FAILURE(rc)) + AssertMsgFailed(("iscsiCommandAsync(%s, %#llx) -> %Rrc\n", pImage->pszTargetName, uOffset, rc)); + else + { + *pcbActuallyRead = cbToRead; + return VERR_VD_IOCTX_HALT; /* Halt the I/O context until further notification from the I/O thread. */ + } + } + + RTMemFree(pReq); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnWrite */ +static DECLCALLBACK(int) iscsiWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite, + PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead, + size_t *pcbPostRead, unsigned fWrite) +{ + RT_NOREF3(pcbPreRead, pcbPostRead, fWrite); + LogFlowFunc(("pBackendData=%p uOffset=%llu pIoCtx=%#p cbToWrite=%u pcbWriteProcess=%p pcbPreRead=%p pcbPostRead=%p fWrite=%u\n", + pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead, fWrite)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + int rc = VINF_SUCCESS; + + AssertPtr(pImage); + Assert(uOffset % 512 == 0); + Assert(cbToWrite % 512 == 0); + + if (uOffset + cbToWrite > pImage->cbSize) + return VERR_INVALID_PARAMETER; + + /* + * Clip read size to a value which is supported by the target. + */ + cbToWrite = RT_MIN(cbToWrite, pImage->cbSendDataLength); + + unsigned cI2TSegs = 0; + size_t cbSegs = 0; + + /* Get the number of segments. */ + cbSegs = pImage->pIfIo->pfnIoCtxSegArrayCreate(pImage->pIfIo->Core.pvUser, pIoCtx, + NULL, &cI2TSegs, cbToWrite); + Assert(cbSegs == cbToWrite); + + PSCSIREQ pReq = (PSCSIREQ)RTMemAllocZ(RT_UOFFSETOF_DYN(SCSIREQ, aSegs[cI2TSegs])); + if (RT_LIKELY(pReq)) + { + uint64_t lba; + uint16_t tls; + uint8_t *pbCDB = &pReq->abCDB[0]; + size_t cbCDB; + + lba = uOffset / pImage->cbSector; + tls = (uint16_t)(cbToWrite / pImage->cbSector); + + cbSegs = pImage->pIfIo->pfnIoCtxSegArrayCreate(pImage->pIfIo->Core.pvUser, pIoCtx, + &pReq->aSegs[0], + &cI2TSegs, cbToWrite); + Assert(cbSegs == cbToWrite); + + if (pImage->cVolume < _4G) + { + cbCDB = 10; + pbCDB[0] = SCSI_WRITE_10; + pbCDB[1] = 0; /* reserved */ + pbCDB[2] = (lba >> 24) & 0xff; + pbCDB[3] = (lba >> 16) & 0xff; + pbCDB[4] = (lba >> 8) & 0xff; + pbCDB[5] = lba & 0xff; + pbCDB[6] = 0; /* reserved */ + pbCDB[7] = (tls >> 8) & 0xff; + pbCDB[8] = tls & 0xff; + pbCDB[9] = 0; /* control */ + } + else + { + cbCDB = 16; + pbCDB[0] = SCSI_WRITE_16; + pbCDB[1] = 0; /* reserved */ + pbCDB[2] = (lba >> 56) & 0xff; + pbCDB[3] = (lba >> 48) & 0xff; + pbCDB[4] = (lba >> 40) & 0xff; + pbCDB[5] = (lba >> 32) & 0xff; + pbCDB[6] = (lba >> 24) & 0xff; + pbCDB[7] = (lba >> 16) & 0xff; + pbCDB[8] = (lba >> 8) & 0xff; + pbCDB[9] = lba & 0xff; + pbCDB[10] = 0; /* tls unused */ + pbCDB[11] = 0; /* tls unused */ + pbCDB[12] = (tls >> 8) & 0xff; + pbCDB[13] = tls & 0xff; + pbCDB[14] = 0; /* reserved */ + pbCDB[15] = 0; /* reserved */ + } + + pReq->enmXfer = SCSIXFER_TO_TARGET; + pReq->cbCDB = cbCDB; + pReq->cbI2TData = cbToWrite; + pReq->paI2TSegs = &pReq->aSegs[0]; + pReq->cI2TSegs = cI2TSegs; + pReq->cbT2IData = 0; + pReq->paT2ISegs = NULL; + pReq->cT2ISegs = 0; + pReq->cbSense = sizeof(pReq->abSense); + pReq->pIoCtx = pIoCtx; + pReq->cSenseRetries = 10; + pReq->rcSense = VERR_WRITE_ERROR; + + if (vdIfIoIntIoCtxIsSynchronous(pImage->pIfIo, pIoCtx)) + { + rc = iscsiCommandSync(pImage, pReq, true, VERR_WRITE_ERROR); + if (RT_FAILURE(rc)) + { + LogFlow(("iscsiCommandSync(%s, %#llx) -> %Rrc\n", pImage->pszTargetName, uOffset, rc)); + *pcbWriteProcess = 0; + } + else + *pcbWriteProcess = cbToWrite; + } + else + { + rc = iscsiCommandAsync(pImage, pReq, iscsiCommandAsyncComplete, pReq); + if (RT_FAILURE(rc)) + AssertMsgFailed(("iscsiCommandAsync(%s, %#llx) -> %Rrc\n", pImage->pszTargetName, uOffset, rc)); + else + { + *pcbWriteProcess = cbToWrite; + return VERR_VD_IOCTX_HALT; /* Halt the I/O context until further notification from the I/O thread. */ + } + } + + RTMemFree(pReq); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnFlush */ +static DECLCALLBACK(int) iscsiFlush(void *pBackendData, PVDIOCTX pIoCtx) +{ + LogFlowFunc(("pBackendData=%p pIoCtx=%#p\n", pBackendData, pIoCtx)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + int rc = VINF_SUCCESS; + + PSCSIREQ pReq = (PSCSIREQ)RTMemAllocZ(sizeof(SCSIREQ)); + if (RT_LIKELY(pReq)) + { + uint8_t *pbCDB = &pReq->abCDB[0]; + + pbCDB[0] = SCSI_SYNCHRONIZE_CACHE; + pbCDB[1] = 0; /* reserved */ + pbCDB[2] = 0; /* reserved */ + pbCDB[3] = 0; /* reserved */ + pbCDB[4] = 0; /* reserved */ + pbCDB[5] = 0; /* reserved */ + pbCDB[6] = 0; /* reserved */ + pbCDB[7] = 0; /* reserved */ + pbCDB[8] = 0; /* reserved */ + pbCDB[9] = 0; /* control */ + + pReq->enmXfer = SCSIXFER_NONE; + pReq->cbCDB = 10; + pReq->cbI2TData = 0; + pReq->paI2TSegs = NULL; + pReq->cI2TSegs = 0; + pReq->cbT2IData = 0; + pReq->paT2ISegs = NULL; + pReq->cT2ISegs = 0; + pReq->cbSense = sizeof(pReq->abSense); + pReq->pIoCtx = pIoCtx; + pReq->cSenseRetries = 0; + pReq->rcSense = VINF_SUCCESS; + + if (vdIfIoIntIoCtxIsSynchronous(pImage->pIfIo, pIoCtx)) + { + rc = iscsiCommandSync(pImage, pReq, false, VINF_SUCCESS); + if (RT_FAILURE(rc)) + AssertMsgFailed(("iscsiCommand(%s) -> %Rrc\n", pImage->pszTargetName, rc)); + } + else + { + rc = iscsiCommandAsync(pImage, pReq, iscsiCommandAsyncComplete, pReq); + if (RT_FAILURE(rc)) + AssertMsgFailed(("iscsiCommand(%s) -> %Rrc\n", pImage->pszTargetName, rc)); + else + return VERR_VD_IOCTX_HALT; /* Halt the I/O context until further notification from the I/O thread. */ + } + + RTMemFree(pReq); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetVersion */ +static DECLCALLBACK(unsigned) iscsiGetVersion(void *pBackendData) +{ + LogFlowFunc(("pBackendData=%#p\n", pBackendData)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + + AssertPtr(pImage); + RT_NOREF1(pImage); + + return 0; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */ +static DECLCALLBACK(uint64_t) iscsiGetFileSize(void *pBackendData) +{ + LogFlowFunc(("pBackendData=%#p\n", pBackendData)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + + AssertPtrReturn(pImage, 0); + + return pImage->cbSize; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */ +static DECLCALLBACK(int) iscsiGetPCHSGeometry(void *pBackendData, PVDGEOMETRY pPCHSGeometry) +{ + RT_NOREF1(pPCHSGeometry); + LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", VERR_VD_GEOMETRY_NOT_SET, + pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors)); + return VERR_VD_GEOMETRY_NOT_SET; +} + +/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */ +static DECLCALLBACK(int) iscsiSetPCHSGeometry(void *pBackendData, PCVDGEOMETRY pPCHSGeometry) +{ + RT_NOREF1(pPCHSGeometry); + LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n", pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + int rc; + if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + rc = VERR_VD_IMAGE_READ_ONLY; + else + rc = VERR_VD_GEOMETRY_NOT_SET; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */ +static DECLCALLBACK(int) iscsiGetLCHSGeometry(void *pBackendData, PVDGEOMETRY pLCHSGeometry) +{ + RT_NOREF1(pLCHSGeometry); + LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", VERR_VD_GEOMETRY_NOT_SET, + pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors)); + return VERR_VD_GEOMETRY_NOT_SET; +} + +/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */ +static DECLCALLBACK(int) iscsiSetLCHSGeometry(void *pBackendData, PCVDGEOMETRY pLCHSGeometry) +{ + RT_NOREF1(pLCHSGeometry); + LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n", pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + int rc; + if (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + rc = VERR_VD_IMAGE_READ_ONLY; + else + rc = VERR_VD_GEOMETRY_NOT_SET; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */ +static DECLCALLBACK(int) iscsiQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList) +{ + LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + + AssertPtrReturn(pImage, VERR_VD_NOT_OPENED); + + *ppRegionList = &pImage->RegionList; + LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS)); + return VINF_SUCCESS; +} + +/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */ +static DECLCALLBACK(void) iscsiRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList) +{ + RT_NOREF1(pRegionList); + LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + AssertPtr(pImage); RT_NOREF(pImage); + + /* Nothing to do here. */ +} + +/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */ +static DECLCALLBACK(unsigned) iscsiGetImageFlags(void *pBackendData) +{ + LogFlowFunc(("pBackendData=%#p\n", pBackendData)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + + AssertPtrReturn(pImage, 0); + + LogFlowFunc(("returns %#x\n", VD_IMAGE_FLAGS_FIXED)); + return VD_IMAGE_FLAGS_FIXED; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */ +static DECLCALLBACK(unsigned) iscsiGetOpenFlags(void *pBackendData) +{ + LogFlowFunc(("pBackendData=%#p\n", pBackendData)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + + AssertPtrReturn(pImage, 0); + + LogFlowFunc(("returns %#x\n", pImage->uOpenFlags)); + return pImage->uOpenFlags; +} + +/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */ +static DECLCALLBACK(int) iscsiSetOpenFlags(void *pBackendData, unsigned uOpenFlags) +{ + LogFlowFunc(("pBackendData=%#p uOpenFlags=%#x\n", pBackendData, uOpenFlags)); + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + int rc = VINF_SUCCESS; + + /* Image must be opened and the new flags must be valid. */ + AssertReturn(pImage && !(uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO + | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE + | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)), + VERR_INVALID_PARAMETER); + + /* + * A read/write -> readonly transition is always possible, + * for the reverse direction check that the target didn't present itself + * as readonly during the first attach. + */ + if ( !(uOpenFlags & VD_OPEN_FLAGS_READONLY) + && (pImage->uOpenFlags & VD_OPEN_FLAGS_READONLY) + && pImage->fTargetReadOnly) + rc = VERR_VD_IMAGE_READ_ONLY; + else + { + pImage->uOpenFlags = uOpenFlags; + pImage->fTryReconnect = true; + } + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnGetComment */ +VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(iscsiGetComment); + +/** @copydoc VDIMAGEBACKEND::pfnSetComment */ +VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(iscsiSetComment, PISCSIIMAGE); + +/** @copydoc VDIMAGEBACKEND::pfnGetUuid */ +VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(iscsiGetUuid); + +/** @copydoc VDIMAGEBACKEND::pfnSetUuid */ +VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(iscsiSetUuid, PISCSIIMAGE); + +/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */ +VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(iscsiGetModificationUuid); + +/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */ +VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(iscsiSetModificationUuid, PISCSIIMAGE); + +/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */ +VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(iscsiGetParentUuid); + +/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */ +VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(iscsiSetParentUuid, PISCSIIMAGE); + +/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */ +VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(iscsiGetParentModificationUuid); + +/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */ +VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(iscsiSetParentModificationUuid, PISCSIIMAGE); + +/** @copydoc VDIMAGEBACKEND::pfnDump */ +static DECLCALLBACK(void) iscsiDump(void *pBackendData) +{ + PISCSIIMAGE pImage = (PISCSIIMAGE)pBackendData; + + AssertPtrReturnVoid(pImage); + /** @todo put something useful here */ + vdIfErrorMessage(pImage->pIfError, "Header: cVolume=%u\n", pImage->cVolume); +} + +/** @copydoc VDIMAGEBACKEND::pfnComposeLocation */ +static DECLCALLBACK(int) iscsiComposeLocation(PVDINTERFACE pConfig, char **pszLocation) +{ + char *pszTarget = NULL; + char *pszLUN = NULL; + char *pszAddress = NULL; + int rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "TargetName", &pszTarget); + if (RT_SUCCESS(rc)) + { + rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "LUN", &pszLUN); + if (RT_SUCCESS(rc)) + { + rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "TargetAddress", &pszAddress); + if (RT_SUCCESS(rc)) + { + if (RTStrAPrintf(pszLocation, "iscsi://%s/%s/%s", + pszAddress, pszTarget, pszLUN) < 0) + rc = VERR_NO_MEMORY; + } + } + } + RTMemFree(pszTarget); + RTMemFree(pszLUN); + RTMemFree(pszAddress); + return rc; +} + +/** @copydoc VDIMAGEBACKEND::pfnComposeName */ +static DECLCALLBACK(int) iscsiComposeName(PVDINTERFACE pConfig, char **pszName) +{ + char *pszTarget = NULL; + char *pszLUN = NULL; + char *pszAddress = NULL; + int rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "TargetName", &pszTarget); + if (RT_SUCCESS(rc)) + { + rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "LUN", &pszLUN); + if (RT_SUCCESS(rc)) + { + rc = VDCFGQueryStringAlloc(VDIfConfigGet(pConfig), "TargetAddress", &pszAddress); + if (RT_SUCCESS(rc)) + { + /** @todo think about a nicer looking location scheme for iSCSI */ + if (RTStrAPrintf(pszName, "%s/%s/%s", + pszAddress, pszTarget, pszLUN) < 0) + rc = VERR_NO_MEMORY; + } + } + } + RTMemFree(pszTarget); + RTMemFree(pszLUN); + RTMemFree(pszAddress); + + return rc; +} + + +const VDIMAGEBACKEND g_ISCSIBackend = +{ + /* u32Version */ + VD_IMGBACKEND_VERSION, + /* pszBackendName */ + "iSCSI", + /* uBackendCaps */ + VD_CAP_CONFIG | VD_CAP_TCPNET | VD_CAP_ASYNC, + /* papszFileExtensions */ + NULL, + /* paConfigInfo */ + s_iscsiConfigInfo, + /* prnProbe */ + iscsiProbe, + /* pfnOpen */ + iscsiOpen, + /* pfnCreate */ + iscsiCreate, + /* pfnRename */ + NULL, + /* pfnClose */ + iscsiClose, + /* pfnRead */ + iscsiRead, + /* pfnWrite */ + iscsiWrite, + /* pfnFlush */ + iscsiFlush, + /* pfnDiscard */ + NULL, + /* pfnGetVersion */ + iscsiGetVersion, + /* pfnGetFileSize */ + iscsiGetFileSize, + /* pfnGetPCHSGeometry */ + iscsiGetPCHSGeometry, + /* pfnSetPCHSGeometry */ + iscsiSetPCHSGeometry, + /* pfnGetLCHSGeometry */ + iscsiGetLCHSGeometry, + /* pfnSetLCHSGeometry */ + iscsiSetLCHSGeometry, + /* pfnQueryRegions */ + iscsiQueryRegions, + /* pfnRegionListRelease */ + iscsiRegionListRelease, + /* pfnGetImageFlags */ + iscsiGetImageFlags, + /* pfnGetOpenFlags */ + iscsiGetOpenFlags, + /* pfnSetOpenFlags */ + iscsiSetOpenFlags, + /* pfnGetComment */ + iscsiGetComment, + /* pfnSetComment */ + iscsiSetComment, + /* pfnGetUuid */ + iscsiGetUuid, + /* pfnSetUuid */ + iscsiSetUuid, + /* pfnGetModificationUuid */ + iscsiGetModificationUuid, + /* pfnSetModificationUuid */ + iscsiSetModificationUuid, + /* pfnGetParentUuid */ + iscsiGetParentUuid, + /* pfnSetParentUuid */ + iscsiSetParentUuid, + /* pfnGetParentModificationUuid */ + iscsiGetParentModificationUuid, + /* pfnSetParentModificationUuid */ + iscsiSetParentModificationUuid, + /* pfnDump */ + iscsiDump, + /* pfnGetTimestamp */ + NULL, + /* pfnGetParentTimestamp */ + NULL, + /* pfnSetParentTimestamp */ + NULL, + /* pfnGetParentFilename */ + NULL, + /* pfnSetParentFilename */ + NULL, + /* pfnComposeLocation */ + iscsiComposeLocation, + /* pfnComposeName */ + iscsiComposeName, + /* pfnCompact */ + NULL, + /* pfnResize */ + NULL, + /* pfnRepair */ + NULL, + /* pfnTraverseMetadata */ + NULL, + /* u32VersionEnd */ + VD_IMGBACKEND_VERSION +}; |