Beispiel #1
0
        public bool Write(ushort LUN, ulong sectorIndex, byte[] data, int bytesPerSector)
        {
            if (!m_isConnected)
            {
                throw new InvalidOperationException("iSCSI client is not connected");
            }
            SCSICommandPDU writeCommand = ClientHelper.GetWrite16Command(m_session, m_connection, LUN, sectorIndex, data, bytesPerSector);

            SendPDU(writeCommand);
            ISCSIPDU response = WaitForPDU(writeCommand.InitiatorTaskTag);

            while (response is ReadyToTransferPDU)
            {
                List <SCSIDataOutPDU> requestedData = ClientHelper.GetWriteData(m_session, m_connection, LUN, sectorIndex, data, bytesPerSector, (ReadyToTransferPDU)response);
                foreach (SCSIDataOutPDU dataOut in requestedData)
                {
                    SendPDU(dataOut);
                }
                response = WaitForPDU(writeCommand.InitiatorTaskTag);
            }

            if (response is SCSIResponsePDU)
            {
                if (((SCSIResponsePDU)response).Status == SCSIStatusCodeName.Good)
                {
                    return(true);
                }
            }
            return(false);
        }
Beispiel #2
0
        /// <returns>Capacity in bytes</returns>
        public ulong ReadCapacity(ushort LUN, out int bytesPerSector)
        {
            if (!m_isConnected)
            {
                throw new InvalidOperationException("iSCSI client is not connected");
            }
            SCSICommandPDU readCapacity = ClientHelper.GetReadCapacity10Command(m_session, m_connection, LUN);

            SendPDU(readCapacity);
            // SCSIResponsePDU with CheckCondition could be returned in case of an error
            SCSIDataInPDU data = WaitForPDU(readCapacity.InitiatorTaskTag) as SCSIDataInPDU;

            if (data != null && data.StatusPresent && data.Status == SCSIStatusCodeName.Good)
            {
                ReadCapacity10Parameter capacity = new ReadCapacity10Parameter(data.Data);
                if (capacity.ReturnedLBA != 0xFFFFFFFF)
                {
                    bytesPerSector = (int)capacity.BlockLengthInBytes;
                    return((capacity.ReturnedLBA + 1) * capacity.BlockLengthInBytes);
                }

                readCapacity = ClientHelper.GetReadCapacity16Command(m_session, m_connection, LUN);
                m_clientSocket.Send(readCapacity.GetBytes());
                data = WaitForPDU(readCapacity.InitiatorTaskTag) as SCSIDataInPDU;
                if (data != null && data.StatusPresent && data.Status == SCSIStatusCodeName.Good)
                {
                    ReadCapacity16Parameter capacity16 = new ReadCapacity16Parameter(data.Data);
                    bytesPerSector = (int)capacity16.BlockLengthInBytes;
                    return((capacity16.ReturnedLBA + 1) * capacity16.BlockLengthInBytes);
                }
            }

            bytesPerSector = 0;
            return(0);
        }
Beispiel #3
0
        public byte[] Read(ushort LUN, ulong sectorIndex, uint sectorCount, int bytesPerSector)
        {
            if (!m_isConnected)
            {
                throw new InvalidOperationException("iSCSI client is not connected");
            }
            SCSICommandPDU readCommand = ClientHelper.GetRead16Command(m_session, m_connection, LUN, sectorIndex, sectorCount, bytesPerSector);

            SendPDU(readCommand);
            // RFC 3720: Data payload is associated with a specific SCSI command through the Initiator Task Tag
            SCSIDataInPDU data = WaitForPDU(readCommand.InitiatorTaskTag) as SCSIDataInPDU;

            byte[] result = new byte[sectorCount * bytesPerSector];
            while (data != null)
            {
                Array.Copy(data.Data, 0, result, data.BufferOffset, data.DataSegmentLength);
                if (data.StatusPresent)
                {
                    break;
                }
                data = WaitForPDU(readCommand.InitiatorTaskTag) as SCSIDataInPDU;
            }
            if (data != null && data.Status == SCSIStatusCodeName.Good)
            {
                return(result);
            }
            else
            {
                return(null);
            }
        }
Beispiel #4
0
        public uint TotalR2Ts; // Numbers of R2Ts that will be sent during this transfer

        public TransferEntry(SCSICommandPDU command, uint nextR2TSN, uint nextOffset, uint totalR2Ts)
        {
            Command    = command;
            NextR2TSN  = nextR2TSN;
            NextOffset = nextOffset;
            TotalR2Ts  = totalR2Ts;
        }
Beispiel #5
0
        public TransferEntry AddTransfer(uint transferTag, SCSICommandPDU command, uint nextR2TSN, uint nextOffset, uint totalR2Ts)
        {
            TransferEntry entry = new TransferEntry(command, nextR2TSN, nextOffset, totalR2Ts);

            n_transfers.Add(transferTag, entry);
            return(entry);
        }
Beispiel #6
0
        public List <SCSICommandPDU> GetDelayedCommandsReadyForExecution()
        {
            List <SCSICommandPDU> result = new List <SCSICommandPDU>();

            if (CommandsInTransfer.Count == 0)
            {
                result.AddRange(DelayedCommands);
                DelayedCommands.Clear();
                return(result);
            }

            // We find the earliest CmdSN of the commands in transfer
            uint earliestCmdSN = CommandsInTransfer[0];

            for (int index = 1; index < CommandsInTransfer.Count; index++)
            {
                if (IsFirstCmdSNPreceding(CommandsInTransfer[index], earliestCmdSN))
                {
                    earliestCmdSN = CommandsInTransfer[index];
                }
            }

            // Any command that is preceding earliestCmdSN should be executed
            for (int index = 0; index < DelayedCommands.Count; index++)
            {
                SCSICommandPDU delayedCommand = DelayedCommands[index];
                if (IsFirstCmdSNPreceding(delayedCommand.CmdSN, earliestCmdSN))
                {
                    result.Add(delayedCommand);
                    DelayedCommands.RemoveAt(index);
                    index--;
                }
            }
            return(result);
        }
        public void OnCommandCompleted(SCSIStatusCodeName status, byte[] responseBytes, object task)
        {
            RunningSCSICommands.Decrement();
            SCSICommandPDU  command      = (SCSICommandPDU)task;
            List <ISCSIPDU> responseList = TargetResponseHelper.PrepareSCSICommandResponse(command, status, responseBytes, ConnectionParameters);

            SendQueue.Enqueue(responseList);
        }
        public TransferEntry AddTransfer(uint initiatorTaskTag, uint transferTag, SCSICommandPDU command, uint nextR2TSN, uint nextOffset, uint totalR2Ts)
        {
            m_taskTagToTransferTag.Add(initiatorTaskTag, transferTag);
            TransferEntry entry = new TransferEntry(command, nextR2TSN, nextOffset, totalR2Ts);

            m_transfers.Add(transferTag, entry);
            return(entry);
        }
Beispiel #9
0
        internal static List <ReadyToTransferPDU> GetReadyToTransferPDUs(SCSICommandPDU command, ConnectionParameters connection, out List <SCSICommandPDU> commandsToExecute)
        {
            // We return either SCSIResponsePDU or List<SCSIDataInPDU>
            List <ReadyToTransferPDU> responseList = new List <ReadyToTransferPDU>();

            commandsToExecute = new List <SCSICommandPDU>();

            ISCSISession session = connection.Session;

            if (command.Write && command.DataSegmentLength < command.ExpectedDataTransferLength)
            {
                uint transferTag = session.GetNextTransferTag();

                // Create buffer for next segments (we only execute the command after receiving all of its data)
                Array.Resize <byte>(ref command.Data, (int)command.ExpectedDataTransferLength);

                // Send R2Ts:
                uint bytesLeft  = command.ExpectedDataTransferLength - command.DataSegmentLength;
                uint nextOffset = command.DataSegmentLength;
                if (!session.InitialR2T)
                {
                    uint firstDataPDULength = Math.Min((uint)session.FirstBurstLength, command.ExpectedDataTransferLength) - command.DataSegmentLength;
                    bytesLeft  -= firstDataPDULength;
                    nextOffset += firstDataPDULength;
                }
                int totalR2Ts    = (int)Math.Ceiling((double)bytesLeft / connection.TargetMaxRecvDataSegmentLength);
                int outgoingR2Ts = Math.Min(session.MaxOutstandingR2T, totalR2Ts);

                for (uint index = 0; index < outgoingR2Ts; index++)
                {
                    ReadyToTransferPDU response = new ReadyToTransferPDU();
                    response.InitiatorTaskTag          = command.InitiatorTaskTag;
                    response.R2TSN                     = index; // R2Ts are sequenced per command and must start with 0 for each new command;
                    response.TargetTransferTag         = transferTag;
                    response.BufferOffset              = nextOffset;
                    response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, command.ExpectedDataTransferLength - response.BufferOffset);
                    responseList.Add(response);
                    nextOffset += (uint)connection.TargetMaxRecvDataSegmentLength;
                }
                connection.AddTransfer(transferTag, command, (uint)outgoingR2Ts, nextOffset, (uint)totalR2Ts);
                session.CommandsInTransfer.Add(command.CmdSN);
                return(responseList);
            }

            if (session.IsPrecedingCommandPending(command.CmdSN))
            {
                session.DelayedCommands.Add(command);
            }
            else
            {
                commandsToExecute.Add(command);
            }
            return(responseList);
        }
Beispiel #10
0
        internal static SCSICommandPDU GetReportLUNsCommand(SessionParameters session, ConnectionParameters connection, uint allocationLength)
        {
            SCSICommandDescriptorBlock reportLUNs = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ReportLUNs);

            reportLUNs.TransferLength = allocationLength;

            SCSICommandPDU scsiCommand = new SCSICommandPDU();

            scsiCommand.CommandDescriptorBlock = reportLUNs.GetBytes();
            scsiCommand.InitiatorTaskTag       = session.GetNextTaskTag();
            scsiCommand.Final = true;
            scsiCommand.Read  = true;
            scsiCommand.CmdSN = session.GetNextCmdSN(true);
            scsiCommand.ExpectedDataTransferLength = allocationLength;
            return(scsiCommand);
        }
Beispiel #11
0
        internal static SCSICommandPDU GetReadCapacity10Command(SessionParameters session, ConnectionParameters connection, ushort LUN)
        {
            SCSICommandDescriptorBlock readCapacity10 = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ReadCapacity10);

            readCapacity10.TransferLength = ReadCapacity10Parameter.Length;

            SCSICommandPDU scsiCommand = new SCSICommandPDU();

            scsiCommand.CommandDescriptorBlock = readCapacity10.GetBytes();
            scsiCommand.InitiatorTaskTag       = session.GetNextTaskTag();
            scsiCommand.Final = true;
            scsiCommand.Read  = true;
            scsiCommand.LUN   = LUN;
            scsiCommand.CmdSN = session.GetNextCmdSN(true);
            scsiCommand.ExpectedDataTransferLength = ReadCapacity10Parameter.Length;
            return(scsiCommand);
        }
Beispiel #12
0
        internal static SCSICommandPDU GetRead16Command(SessionParameters session, ConnectionParameters connection, ushort LUN, ulong sectorIndex, uint sectorCount, int bytesPerSector)
        {
            SCSICommandDescriptorBlock read16 = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.Read16);

            read16.LogicalBlockAddress64 = sectorIndex;
            read16.TransferLength        = sectorCount;

            SCSICommandPDU scsiCommand = new SCSICommandPDU();

            scsiCommand.CommandDescriptorBlock = read16.GetBytes();
            scsiCommand.LUN = LUN;
            scsiCommand.InitiatorTaskTag           = session.GetNextTaskTag();
            scsiCommand.Final                      = true;
            scsiCommand.Read                       = true;
            scsiCommand.CmdSN                      = session.GetNextCmdSN(true);
            scsiCommand.ExpectedDataTransferLength = (uint)(sectorCount * bytesPerSector);
            return(scsiCommand);
        }
Beispiel #13
0
        internal static SCSICommandPDU GetReadCapacity16Command(ConnectionParameters connection, ushort LUN)
        {
            ISCSISession session = connection.Session;
            SCSICommandDescriptorBlock serviceActionIn = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ServiceActionIn16);

            serviceActionIn.ServiceAction  = ServiceAction.ReadCapacity16;
            serviceActionIn.TransferLength = ReadCapacity16Parameter.Length;

            SCSICommandPDU scsiCommand = new SCSICommandPDU();

            scsiCommand.CommandDescriptorBlock = serviceActionIn.GetBytes();
            scsiCommand.InitiatorTaskTag       = session.GetNextTaskTag();
            scsiCommand.Final = true;
            scsiCommand.Read  = true;
            scsiCommand.LUN   = LUN;
            scsiCommand.CmdSN = session.GetNextCmdSN(true);
            scsiCommand.ExpectedDataTransferLength = ReadCapacity16Parameter.Length;
            return(scsiCommand);
        }
Beispiel #14
0
        public List <ushort> GetLUNList()
        {
            if (!m_isConnected)
            {
                throw new InvalidOperationException("iSCSI client is not connected");
            }
            SCSICommandPDU reportLUNs = ClientHelper.GetReportLUNsCommand(m_session, m_connection, ReportLUNsParameter.MinimumAllocationLength);

            SendPDU(reportLUNs);
            SCSIDataInPDU data = WaitForPDU(reportLUNs.InitiatorTaskTag) as SCSIDataInPDU;

            if (data != null && data.StatusPresent && data.Status == SCSIStatusCodeName.Good)
            {
                uint requiredAllocationLength = ReportLUNsParameter.GetRequiredAllocationLength(data.Data);
                if (requiredAllocationLength > ReportLUNsParameter.MinimumAllocationLength)
                {
                    reportLUNs = ClientHelper.GetReportLUNsCommand(m_session, m_connection, requiredAllocationLength);
                    m_clientSocket.Send(reportLUNs.GetBytes());
                    data = WaitForPDU(reportLUNs.InitiatorTaskTag) as SCSIDataInPDU;

                    if (data == null || !data.StatusPresent || data.Status != SCSIStatusCodeName.Good)
                    {
                        return(null);
                    }
                }

                ReportLUNsParameter parameter = new ReportLUNsParameter(data.Data);
                List <ushort>       result    = new List <ushort>();
                foreach (LUNStructure lun in parameter.LUNList)
                {
                    if (lun.IsSingleLevelLUN)
                    {
                        result.Add(lun);
                    }
                }
                return(result);
            }
            return(null);
        }
Beispiel #15
0
        internal static SCSICommandPDU GetWrite16Command(SessionParameters session, ConnectionParameters connection, ushort LUN, ulong sectorIndex, byte[] data, int bytesPerSector)
        {
            SCSICommandDescriptorBlock write16 = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.Write16);

            write16.LogicalBlockAddress64 = sectorIndex;
            write16.TransferLength        = (uint)(data.Length / bytesPerSector);

            SCSICommandPDU scsiCommand = new SCSICommandPDU();

            scsiCommand.CommandDescriptorBlock = write16.GetBytes();
            if (session.ImmediateData)
            {
                int immediateDataLength = Math.Min(data.Length, session.FirstBurstLength);
                scsiCommand.Data = ByteReader.ReadBytes(data, 0, immediateDataLength);
            }
            scsiCommand.LUN = LUN;
            scsiCommand.InitiatorTaskTag           = session.GetNextTaskTag();
            scsiCommand.Final                      = true;
            scsiCommand.Write                      = true;
            scsiCommand.CmdSN                      = session.GetNextCmdSN(true);
            scsiCommand.ExpectedDataTransferLength = (uint)(data.Length);
            return(scsiCommand);
        }
Beispiel #16
0
        internal static List <ISCSIPDU> PrepareSCSICommandResponse(SCSICommandPDU command, SCSIStatusCodeName status, byte[] scsiResponse, ConnectionParameters connection)
        {
            List <ISCSIPDU> responseList = new List <ISCSIPDU>();

            if (!command.Read || status != SCSIStatusCodeName.Good)
            {
                // RFC 3720: if the command is completed with an error, then the response and sense data MUST be sent in a SCSI Response PDU
                SCSIResponsePDU response = new SCSIResponsePDU();
                response.InitiatorTaskTag = command.InitiatorTaskTag;
                response.Status           = status;
                response.Data             = scsiResponse;
                if (command.Read)
                {
                    EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
                }
                responseList.Add(response);
            }
            else if (scsiResponse.Length <= connection.InitiatorMaxRecvDataSegmentLength)
            {
                SCSIDataInPDU response = new SCSIDataInPDU();
                response.InitiatorTaskTag = command.InitiatorTaskTag;
                response.Status           = status;
                response.StatusPresent    = true;
                response.Final            = true;
                response.Data             = scsiResponse;
                EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
                responseList.Add(response);
            }
            else // we have to split the response to multiple Data-In PDUs
            {
                int bytesLeftToSend = scsiResponse.Length;

                uint dataSN = 0;
                while (bytesLeftToSend > 0)
                {
                    int dataSegmentLength = Math.Min(connection.InitiatorMaxRecvDataSegmentLength, bytesLeftToSend);
                    int dataOffset        = scsiResponse.Length - bytesLeftToSend;

                    SCSIDataInPDU response = new SCSIDataInPDU();
                    response.InitiatorTaskTag = command.InitiatorTaskTag;
                    if (bytesLeftToSend == dataSegmentLength)
                    {
                        // last Data-In PDU
                        response.Status        = status;
                        response.StatusPresent = true;
                        response.Final         = true;
                    }
                    response.BufferOffset = (uint)dataOffset;
                    response.DataSN       = dataSN;
                    dataSN++;

                    response.Data = new byte[dataSegmentLength];
                    Array.Copy(scsiResponse, dataOffset, response.Data, 0, dataSegmentLength);
                    responseList.Add(response);

                    bytesLeftToSend -= dataSegmentLength;
                }
            }

            return(responseList);
        }
        private void ProcessPDU(ISCSIPDU pdu, ConnectionState state)
        {
            LogTrace("Entering ProcessPDU");

            if (state.Session == null || !state.Session.IsFullFeaturePhase)
            {
                if (pdu is LoginRequestPDU)
                {
                    LoginRequestPDU request         = (LoginRequestPDU)pdu;
                    string          loginIdentifier = String.Format("ISID={0},TSIH={1},CID={2}", request.ISID.ToString("x"), request.TSIH.ToString("x"), request.CID.ToString("x"));
                    Log(Severity.Verbose, "[{0}] Login Request, current stage: {1}, next stage: {2}, parameters: {3}", loginIdentifier, request.CurrentStage, request.NextStage, FormatNullDelimitedText(request.LoginParametersText));
                    LoginResponsePDU response = GetLoginResponsePDU(request, state.ConnectionParameters);
                    if (state.Session != null && state.Session.IsFullFeaturePhase)
                    {
                        m_connectionManager.AddConnection(state);
                    }
                    Log(Severity.Verbose, "[{0}] Login Response parameters: {1}", state.ConnectionIdentifier, FormatNullDelimitedText(response.LoginParametersText));
                    state.SendQueue.Enqueue(response);
                }
                else
                {
                    // Before the Full Feature Phase is established, only Login Request and Login Response PDUs are allowed.
                    Log(Severity.Warning, "[{0}] Initiator error: Improper command during login phase, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                    if (state.Session == null)
                    {
                        // A target receiving any PDU except a Login request before the Login phase is started MUST
                        // immediately terminate the connection on which the PDU was received.
                        state.ClientSocket.Close();
                    }
                    else
                    {
                        // Once the Login phase has started, if the target receives any PDU except a Login request,
                        // it MUST send a Login reject (with Status "invalid during login") and then disconnect.
                        LoginResponsePDU loginResponse = new LoginResponsePDU();
                        loginResponse.TSIH   = state.Session.TSIH;
                        loginResponse.Status = LoginResponseStatusName.InvalidDuringLogon;
                        state.SendQueue.Enqueue(loginResponse);
                    }
                }
            }
            else // Logged in
            {
                if (pdu is TextRequestPDU)
                {
                    TextRequestPDU  request  = (TextRequestPDU)pdu;
                    TextResponsePDU response = GetTextResponsePDU(request, state.ConnectionParameters);
                    state.SendQueue.Enqueue(response);
                }
                else if (pdu is LogoutRequestPDU)
                {
                    LogoutRequestPDU request  = (LogoutRequestPDU)pdu;
                    ISCSIPDU         response = GetLogoutResponsePDU(request, state.ConnectionParameters);
                    state.SendQueue.Enqueue(response);
                }
                else if (state.Session.IsDiscovery)
                {
                    // The target MUST ONLY accept text requests with the SendTargets key and a logout
                    // request with the reason "close the session".  All other requests MUST be rejected.
                    Log(Severity.Warning, "[{0}] Initiator error: Improper command during discovery session, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                    RejectPDU reject = new RejectPDU();
                    reject.Reason = RejectReason.ProtocolError;
                    reject.Data   = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);

                    state.SendQueue.Enqueue(reject);
                }
                else if (pdu is NOPOutPDU)
                {
                    NOPOutPDU request = (NOPOutPDU)pdu;
                    if (request.InitiatorTaskTag != 0xFFFFFFFF)
                    {
                        NOPInPDU response = ServerResponseHelper.GetNOPResponsePDU(request);
                        state.SendQueue.Enqueue(response);
                    }
                }
                else if (pdu is SCSIDataOutPDU || pdu is SCSICommandPDU)
                {
                    // RFC 3720: the iSCSI target layer MUST deliver the commands for execution (to the SCSI execution engine) in the order specified by CmdSN.
                    // e.g. read requests should not be executed while previous write request data is being received (via R2T)
                    List <SCSICommandPDU>     commandsToExecute   = null;
                    List <ReadyToTransferPDU> readyToTransferPDUs = new List <ReadyToTransferPDU>();
                    if (pdu is SCSIDataOutPDU)
                    {
                        SCSIDataOutPDU request = (SCSIDataOutPDU)pdu;
                        Log(Severity.Debug, "[{0}] SCSIDataOutPDU: Target transfer tag: {1}, LUN: {2}, Buffer offset: {3}, Data segment length: {4}, DataSN: {5}, Final: {6}", state.ConnectionIdentifier, request.TargetTransferTag, (ushort)request.LUN, request.BufferOffset, request.DataSegmentLength, request.DataSN, request.Final);
                        try
                        {
                            readyToTransferPDUs = TargetResponseHelper.GetReadyToTransferPDUs(request, state.ConnectionParameters, out commandsToExecute);
                        }
                        catch (InvalidTargetTransferTagException ex)
                        {
                            Log(Severity.Warning, "[{0}] Initiator error: Invalid TargetTransferTag: {1}", state.ConnectionIdentifier, ex.TargetTransferTag);
                            RejectPDU reject = new RejectPDU();
                            reject.InitiatorTaskTag = request.InitiatorTaskTag;
                            reject.Reason           = RejectReason.InvalidPDUField;
                            reject.Data             = ByteReader.ReadBytes(request.GetBytes(), 0, 48);
                            state.SendQueue.Enqueue(reject);
                        }
                    }
                    else
                    {
                        SCSICommandPDU command = (SCSICommandPDU)pdu;
                        Log(Severity.Debug, "[{0}] SCSICommandPDU: CmdSN: {1}, LUN: {2}, Data segment length: {3}, Expected Data Transfer Length: {4}, Final: {5}", state.ConnectionIdentifier, command.CmdSN, (ushort)command.LUN, command.DataSegmentLength, command.ExpectedDataTransferLength, command.Final);
                        readyToTransferPDUs = TargetResponseHelper.GetReadyToTransferPDUs(command, state.ConnectionParameters, out commandsToExecute);
                    }
                    foreach (ReadyToTransferPDU readyToTransferPDU in readyToTransferPDUs)
                    {
                        state.SendQueue.Enqueue(readyToTransferPDU);
                    }
                    if (commandsToExecute != null)
                    {
                        state.RunningSCSICommands.Add(commandsToExecute.Count);
                    }
                    foreach (SCSICommandPDU commandPDU in commandsToExecute)
                    {
                        Log(Severity.Debug, "[{0}] Queuing command: CmdSN: {1}", state.ConnectionIdentifier, commandPDU.CmdSN);
                        state.Target.QueueCommand(commandPDU.CommandDescriptorBlock, commandPDU.LUN, commandPDU.Data, commandPDU, state.OnCommandCompleted);
                    }
                }
                else if (pdu is LoginRequestPDU)
                {
                    Log(Severity.Warning, "[{0}] Initiator Error: Login request during full feature phase", state.ConnectionIdentifier);
                    // RFC 3720: Login requests and responses MUST be used exclusively during Login.
                    // On any connection, the login phase MUST immediately follow TCP connection establishment and
                    // a subsequent Login Phase MUST NOT occur before tearing down a connection
                    RejectPDU reject = new RejectPDU();
                    reject.Reason = RejectReason.ProtocolError;
                    reject.Data   = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);

                    state.SendQueue.Enqueue(reject);
                }
                else
                {
                    Log(Severity.Error, "[{0}] Unsupported command, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                    RejectPDU reject = new RejectPDU();
                    reject.Reason = RejectReason.CommandNotSupported;
                    reject.Data   = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);

                    state.SendQueue.Enqueue(reject);
                }
            }
            LogTrace("Leaving ProcessPDU");
        }
Beispiel #18
0
        public void ProcessPDU(ISCSIPDU pdu, StateObject state)
        {
            Socket clientSocket = state.ClientSocket;

            uint?cmdSN = PDUHelper.GetCmdSN(pdu);

            Log(LogLevel.Debug, "[{0}][ProcessPDU] Received PDU from initiator, Operation: {1}, Size: {2}, CmdSN: {3}", state.ConnectionIdentifier, (ISCSIOpCodeName)pdu.OpCode, pdu.Length, cmdSN);
            // RFC 3720: On any connection, the iSCSI initiator MUST send the commands in increasing order of CmdSN,
            // except for commands that are retransmitted due to digest error recovery and connection recovery.
            if (cmdSN.HasValue)
            {
                if (state.SessionParameters.CommandNumberingStarted)
                {
                    if (cmdSN != state.SessionParameters.ExpCmdSN)
                    {
                        Log(LogLevel.Warning, "[{0}][ProcessPDU] CmdSN outside of expected range", state.ConnectionIdentifier);
                        // We ignore this PDU
                        return;
                    }
                }
                else
                {
                    state.SessionParameters.ExpCmdSN = cmdSN.Value;
                    state.SessionParameters.CommandNumberingStarted = true;
                }

                if (pdu is LogoutRequestPDU || pdu is TextRequestPDU || pdu is SCSICommandPDU || pdu is RejectPDU)
                {
                    if (!pdu.ImmediateDelivery)
                    {
                        state.SessionParameters.ExpCmdSN++;
                    }
                }
            }

            if (pdu is LoginRequestPDU)
            {
                LoginRequestPDU request = (LoginRequestPDU)pdu;
                Log(LogLevel.Information, "[{0}][ReceiveCallback] Login Request, current stage: {1}, next stage: {2}, parameters: {3}", state.ConnectionIdentifier, request.CurrentStage, request.NextStage, KeyValuePairUtils.ToString(request.LoginParameters));
                if (request.TSIH != 0)
                {
                    // RFC 3720: A Login Request with a non-zero TSIH and a CID equal to that of an existing
                    // connection implies a logout of the connection followed by a Login
                    lock (m_activeConnectionsLock)
                    {
                        int existingConnectionIndex = GetStateObjectIndex(m_activeConnections, request.ISID, request.TSIH, request.CID);
                        if (existingConnectionIndex >= 0)
                        {
                            // Perform implicit logout
                            Log(LogLevel.Information, "[{0}][ProcessPDU] Initiating implicit logout", state.ConnectionIdentifier);
                            SocketUtils.ReleaseSocket(m_activeConnections[existingConnectionIndex].ClientSocket);
                            if (m_activeConnections[existingConnectionIndex].Target != null)
                            {
                                lock (m_activeConnections[existingConnectionIndex].Target.IOLock)
                                {
                                    // Wait for pending I/O to complete.
                                }
                            }
                            m_activeConnections.RemoveAt(existingConnectionIndex);
                            Log(LogLevel.Information, "[{0}][ProcessPDU] Implicit logout completed", state.ConnectionIdentifier);
                        }
                    }
                }
                LoginResponsePDU response = ServerResponseHelper.GetLoginResponsePDU(request, m_targets, state.SessionParameters, state.ConnectionParameters, ref state.Target, GetNextTSIH);
                if (state.SessionParameters.IsFullFeaturePhase)
                {
                    state.SessionParameters.ISID   = request.ISID;
                    state.ConnectionParameters.CID = request.CID;
                    lock (m_activeConnectionsLock)
                    {
                        m_activeConnections.Add(state);
                    }
                }
                Log(LogLevel.Information, "[{0}][ReceiveCallback] Login Response parameters: {1}", state.ConnectionIdentifier, KeyValuePairUtils.ToString(response.LoginParameters));
                TrySendPDU(state, response);
            }
            else if (!state.SessionParameters.IsFullFeaturePhase)
            {
                // Before the Full Feature Phase is established, only Login Request and Login Response PDUs are allowed.
                Log(LogLevel.Warning, "[{0}][ProcessPDU] Improper command during login phase, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                // A target receiving any PDU except a Login request before the Login phase is started MUST
                // immediately terminate the connection on which the PDU was received.
                // Once the Login phase has started, if the target receives any PDU except a Login request,
                // it MUST send a Login reject (with Status "invalid during login") and then disconnect.
                clientSocket.Dispose();
            }
            else // Logged in
            {
                if (pdu is TextRequestPDU)
                {
                    TextRequestPDU  request  = (TextRequestPDU)pdu;
                    TextResponsePDU response = ServerResponseHelper.GetTextResponsePDU(request, m_targets);
                    TrySendPDU(state, response);
                }
                else if (pdu is LogoutRequestPDU)
                {
                    lock (m_activeConnectionsLock)
                    {
                        int connectionIndex = GetStateObjectIndex(m_activeConnections, state.SessionParameters.ISID, state.SessionParameters.TSIH, state.ConnectionParameters.CID);
                        if (connectionIndex >= 0)
                        {
                            if (m_activeConnections[connectionIndex].Target != null)
                            {
                                lock (m_activeConnections[connectionIndex].Target.IOLock)
                                {
                                    // Wait for pending I/O to complete.
                                }
                            }
                            m_activeConnections.RemoveAt(connectionIndex);
                        }
                    }
                    LogoutRequestPDU  request  = (LogoutRequestPDU)pdu;
                    LogoutResponsePDU response = ServerResponseHelper.GetLogoutResponsePDU(request);
                    TrySendPDU(state, response);
                    clientSocket.Dispose(); // We can close the connection now
                }
                else if (state.SessionParameters.IsDiscovery)
                {
                    // The target MUST ONLY accept text requests with the SendTargets key and a logout
                    // request with the reason "close the session".  All other requests MUST be rejected.
                    Log(LogLevel.Warning, "[{0}][ProcessPDU] Improper command during discovery session, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                    RejectPDU reject = new RejectPDU();
                    reject.Reason = RejectReason.ProtocolError;
                    reject.Data   = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);

                    TrySendPDU(state, reject);
                }
                else if (pdu is NOPOutPDU)
                {
                    NOPOutPDU request = (NOPOutPDU)pdu;
                    if (request.InitiatorTaskTag != 0xFFFFFFFF)
                    {
                        NOPInPDU response = ServerResponseHelper.GetNOPResponsePDU(request);
                        TrySendPDU(state, response);
                    }
                }
                else if (pdu is SCSIDataOutPDU)
                {
                    // FIXME: the iSCSI target layer MUST deliver the commands for execution (to the SCSI execution engin) in the order specified by CmdSN
                    // e.g. read requests should not be executed while previous write request data is being received (via R2T)
                    SCSIDataOutPDU request = (SCSIDataOutPDU)pdu;
                    Log(LogLevel.Debug, "[{0}][ProcessPDU] SCSIDataOutPDU: Target transfer tag: {1}, LUN: {2}, Buffer offset: {3}, Data segment length: {4}, DataSN: {5}, Final: {6}", state.ConnectionIdentifier, request.TargetTransferTag, (ushort)request.LUN, request.BufferOffset, request.DataSegmentLength, request.DataSN, request.Final);
                    ISCSIPDU response = TargetResponseHelper.GetSCSIDataOutResponsePDU(request, state.Target, state.SessionParameters, state.ConnectionParameters);
                    TrySendPDU(state, response);
                }
                else if (pdu is SCSICommandPDU)
                {
                    SCSICommandPDU command = (SCSICommandPDU)pdu;
                    Log(LogLevel.Debug, "[{0}][ProcessPDU] SCSICommandPDU: CmdSN: {1}, LUN: {2}, Data segment length: {3}, Expected Data Transfer Length: {4}, Final: {5}", state.ConnectionIdentifier, command.CmdSN, (ushort)command.LUN, command.DataSegmentLength, command.ExpectedDataTransferLength, command.Final);
                    List <ISCSIPDU> scsiResponseList = TargetResponseHelper.GetSCSIResponsePDU(command, state.Target, state.SessionParameters, state.ConnectionParameters);
                    foreach (ISCSIPDU response in scsiResponseList)
                    {
                        TrySendPDU(state, response);
                        if (!clientSocket.Connected)
                        {
                            return;
                        }
                    }
                }
                else
                {
                    Log(LogLevel.Warning, "[{0}][ProcessPDU] Unsupported command, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                }
            }
        }
        internal static List <ISCSIPDU> GetSCSIResponsePDU(SCSICommandPDU command, ISCSITarget target, SessionParameters session, ConnectionParameters connection)
        {
            // We return either SCSIResponsePDU or List<SCSIDataInPDU>
            List <ISCSIPDU> responseList = new List <ISCSIPDU>();

            string connectionIdentifier = StateObject.GetConnectionIdentifier(session, connection);

            if (command.Write && command.DataSegmentLength < command.ExpectedDataTransferLength)
            {
                uint transferTag = session.GetNextTransferTag();

                // Store segment (we only execute the command after receiving all of its data)
                byte[] commandDataBuffer = new byte[command.ExpectedDataTransferLength];
                Array.Copy(command.Data, 0, commandDataBuffer, 0, command.DataSegmentLength);

                // Send R2T
                ReadyToTransferPDU response = new ReadyToTransferPDU();
                response.InitiatorTaskTag          = command.InitiatorTaskTag;
                response.R2TSN                     = 0; // R2Ts are sequenced per command and must start with 0 for each new command;
                response.TargetTransferTag         = transferTag;
                response.BufferOffset              = command.DataSegmentLength;
                response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, command.ExpectedDataTransferLength - response.BufferOffset);

                connection.AddTransfer(transferTag, command.CommandDescriptorBlock, commandDataBuffer, 1);

                responseList.Add(response);
                return(responseList);
            }

            byte[]             scsiResponse;
            SCSIStatusCodeName status = target.ExecuteCommand(command.CommandDescriptorBlock, command.LUN, command.Data, out scsiResponse);

            if (!command.Read || status != SCSIStatusCodeName.Good)
            {
                // RFC 3720: if the command is completed with an error, then the response and sense data MUST be sent in a SCSI Response PDU
                SCSIResponsePDU response = new SCSIResponsePDU();
                response.InitiatorTaskTag = command.InitiatorTaskTag;
                response.Status           = status;
                response.Data             = scsiResponse;
                if (command.Read)
                {
                    EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
                }
                responseList.Add(response);
            }
            else if (scsiResponse.Length <= connection.InitiatorMaxRecvDataSegmentLength)
            {
                SCSIDataInPDU response = new SCSIDataInPDU();
                response.InitiatorTaskTag = command.InitiatorTaskTag;
                response.Status           = status;
                response.StatusPresent    = true;
                response.Final            = true;
                response.Data             = scsiResponse;
                EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
                responseList.Add(response);
            }
            else // we have to split the response to multiple Data-In PDUs
            {
                int bytesLeftToSend = scsiResponse.Length;

                uint dataSN = 0;
                while (bytesLeftToSend > 0)
                {
                    int dataSegmentLength = Math.Min(connection.InitiatorMaxRecvDataSegmentLength, bytesLeftToSend);
                    int dataOffset        = scsiResponse.Length - bytesLeftToSend;

                    SCSIDataInPDU response = new SCSIDataInPDU();
                    response.InitiatorTaskTag = command.InitiatorTaskTag;
                    if (bytesLeftToSend == dataSegmentLength)
                    {
                        // last Data-In PDU
                        response.Status        = status;
                        response.StatusPresent = true;
                        response.Final         = true;
                    }
                    response.BufferOffset = (uint)dataOffset;
                    response.DataSN       = dataSN;
                    dataSN++;

                    response.Data = new byte[dataSegmentLength];
                    Array.Copy(scsiResponse, dataOffset, response.Data, 0, dataSegmentLength);
                    responseList.Add(response);

                    bytesLeftToSend -= dataSegmentLength;
                }
            }

            return(responseList);
        }