Example #1
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"));
                }
            }
        }
Example #2
0
        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.
            }
        }