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")); } } }
private ISCSIPDU GetLogoutResponsePDU(LogoutRequestPDU request, ConnectionParameters connection) { Log(Severity.Verbose, "[{0}] Logout Request", connection.ConnectionIdentifier); if (connection.Session.IsDiscovery && request.ReasonCode != LogoutReasonCode.CloseTheSession) { // RFC 3720: Discovery-session: The target MUST ONLY accept [..] logout request with the reason "close the session" RejectPDU reject = new RejectPDU(); reject.Reason = RejectReason.ProtocolError; reject.Data = ByteReader.ReadBytes(request.GetBytes(), 0, 48); return(reject); } else { List <ConnectionState> connectionsToClose = new List <ConnectionState>(); if (request.ReasonCode == LogoutReasonCode.CloseTheSession) { connectionsToClose = m_connectionManager.GetSessionConnections(connection.Session); } else if (request.ReasonCode == LogoutReasonCode.CloseTheConnection) { // RFC 3720: A Logout for a CID may be performed on a different transport connection when the TCP connection for the CID has already been terminated. ConnectionState existingConnection = m_connectionManager.FindConnection(connection.Session, request.CID); if (existingConnection != null) { connectionsToClose.Add(existingConnection); } else { return(ServerResponseHelper.GetLogoutResponsePDU(request, LogoutResponse.CIDNotFound)); } } else if (request.ReasonCode == LogoutReasonCode.RemoveTheConnectionForRecovery) { return(ServerResponseHelper.GetLogoutResponsePDU(request, LogoutResponse.ConnectionRecoveryNotSupported)); } else { // Unknown LogoutRequest ReasonCode RejectPDU reject = new RejectPDU(); reject.Reason = RejectReason.ProtocolError; reject.Data = ByteReader.ReadBytes(request.GetBytes(), 0, 48); return(reject); } foreach (ConnectionState connectionToClose in connectionsToClose) { // Wait for pending I/O to complete. connectionToClose.RunningSCSICommands.WaitUntilZero(); if (connectionToClose.ConnectionParameters != connection) { SocketUtils.ReleaseSocket(connectionToClose.ClientSocket); } m_connectionManager.RemoveConnection(connectionToClose); } if (request.ReasonCode == LogoutReasonCode.CloseTheSession) { Log(Severity.Verbose, "[{0}] Session has been closed", connection.Session.SessionIdentifier); m_sessionManager.RemoveSession(connection.Session, SessionTerminationReason.Logout); } return(ServerResponseHelper.GetLogoutResponsePDU(request, LogoutResponse.ClosedSuccessfully)); // connection will be closed after a LogoutResponsePDU has been sent. } }