private void ProcessSendQueue(ConnectionState state) { LogTrace("Entering ProcessSendQueue"); while (true) { ISCSIPDU response; bool stopped = !state.SendQueue.TryDequeue(out response); if (stopped) { return; } Socket clientSocket = state.ClientSocket; PDUHelper.SetStatSN(response, state.ConnectionParameters.StatSN); if (state.Session != null) { PDUHelper.SetExpCmdSN(response, state.Session.ExpCmdSN, state.Session.ExpCmdSN + state.Session.CommandQueueSize); } if (response is SCSIResponsePDU || response is LoginResponsePDU || response is TextResponsePDU || (response is SCSIDataInPDU && ((SCSIDataInPDU)response).StatusPresent) || response is RejectPDU) { state.ConnectionParameters.StatSN++; } try { clientSocket.Send(response.GetBytes()); Log(Severity.Verbose, "[{0}] Sent response to initator, Operation: {1}, Size: {2}", state.ConnectionIdentifier, response.OpCode, response.Length); if (response is LogoutResponsePDU) { clientSocket.Close(); // We can close the connection now LogTrace("Leaving ProcessSendQueue"); return; } else if (response is LoginResponsePDU) { if (((LoginResponsePDU)response).Status != LoginResponseStatusName.Success) { // Login Response: If the Status Class is not 0, the initiator and target MUST close the TCP connection. clientSocket.Close(); // We can close the connection now LogTrace("Leaving ProcessSendQueue"); return; } } } catch (SocketException ex) { Log(Severity.Verbose, "[{0}] Failed to send response to initator. Operation: {1}, Size: {2}, SocketException: {3}", state.ConnectionIdentifier, response.OpCode, response.Length, ex.Message); LogTrace("Leaving ProcessSendQueue"); return; } catch (ObjectDisposedException) { Log(Severity.Verbose, "[{0}] Failed to send response to initator. Operation: {1}, Size: {2}. ObjectDisposedException", state.ConnectionIdentifier, response.OpCode, response.Length); LogTrace("Leaving ProcessSendQueue"); return; } } }
public static void TrySendPDU(StateObject state, ISCSIPDU response) { Socket clientSocket = state.ClientSocket; try { PDUHelper.SetStatSN(response, state.ConnectionParameters.StatSN); PDUHelper.SetExpCmdSN(response, state.SessionParameters.ExpCmdSN, state.SessionParameters.ExpCmdSN + state.SessionParameters.CommandQueueSize); if (response is SCSIResponsePDU || (response is SCSIDataInPDU && ((SCSIDataInPDU)response).StatusPresent)) { state.ConnectionParameters.StatSN++; } clientSocket.Send(response.GetBytes()); Log(LogLevel.Debug, "[{0}][TrySendPDU] Sent response to initator, Operation: {1}, Size: {2}", state.ConnectionIdentifier, response.OpCode, response.Length); } catch (SocketException ex) { Log(LogLevel.Warning, "[{0}][TrySendPDU] Failed to send response to initator (Operation: {1}, Size: {2}), SocketException: {3}", state.ConnectionIdentifier, response.OpCode, response.Length, ex.Message); } catch (ObjectDisposedException) { } }
private bool ValidateCommandNumbering(ISCSIPDU pdu, ConnectionState state) { if (state.Session == null) { return(true); } uint?cmdSN = PDUHelper.GetCmdSN(pdu); Log(Severity.Verbose, "[{0}] 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.Session.CommandNumberingStarted) { if (cmdSN != state.Session.ExpCmdSN) { return(false); } } else { state.Session.ExpCmdSN = cmdSN.Value; state.Session.CommandNumberingStarted = true; } if (pdu is LogoutRequestPDU || pdu is TextRequestPDU || pdu is SCSICommandPDU || pdu is RejectPDU) { if (!pdu.ImmediateDelivery) { state.Session.ExpCmdSN++; } } } return(true); }
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")); } } }