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: InitiatorTaskTag: {1}, TargetTransferTag: {2}, LUN: {3}, BufferOffset: {4}, DataSegmentLength: {5}, DataSN: {6}, Final: {7}", state.ConnectionIdentifier, request.InitiatorTaskTag.ToString("x"), request.TargetTransferTag.ToString("x"), (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.ToString("x"));
                            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}, InitiatorTaskTag: {3}, DataSegmentLength: {4}, ExpectedDataTransferLength: {5}, Final: {6}", state.ConnectionIdentifier, command.CmdSN, (ushort)command.LUN, command.InitiatorTaskTag.ToString("x"), command.DataSegmentLength, command.ExpectedDataTransferLength, command.Final);
                        readyToTransferPDUs = TargetResponseHelper.GetReadyToTransferPDUs(command, state.ConnectionParameters, out commandsToExecute);
                    }
                    foreach (ReadyToTransferPDU readyToTransferPDU in readyToTransferPDUs)
                    {
                        state.SendQueue.Enqueue(readyToTransferPDU);
                        Log(Severity.Debug, "[{0}] Enqueued R2T, InitiatorTaskTag: {1}, TargetTransferTag: {2}, BufferOffset: {3}, DesiredDataTransferLength: {4}, R2TSN: {5}", state.ConnectionIdentifier, readyToTransferPDU.InitiatorTaskTag.ToString("x"), readyToTransferPDU.TargetTransferTag.ToString("x"), readyToTransferPDU.BufferOffset, readyToTransferPDU.DesiredDataTransferLength, readyToTransferPDU.R2TSN);
                    }
                    if (commandsToExecute != null)
                    {
                        state.RunningSCSICommands.Add(commandsToExecute.Count);
                    }
                    foreach (SCSICommandPDU commandPDU in commandsToExecute)
                    {
                        Log(Severity.Debug, "[{0}] Queuing command: CmdSN: {1}, InitiatorTaskTag: {2}", state.ConnectionIdentifier, commandPDU.CmdSN, commandPDU.InitiatorTaskTag.ToString("x"));
                        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");
        }
예제 #2
0
        private LoginResponseStatusName SetUpSession(LoginRequestPDU request, KeyValuePairList <string, string> requestParameters, ConnectionParameters connection)
        {
            string initiatorName = requestParameters.ValueOf("InitiatorName");

            if (String.IsNullOrEmpty(initiatorName))
            {
                // RFC 3720: InitiatorName: The initiator of the TCP connection MUST provide this key [..]
                // at the first Login of the Login Phase for every connection.
                string loginIdentifier = String.Format("ISID={0},TSIH={1},CID={2}", request.ISID.ToString("x"), request.TSIH.ToString("x"), request.CID.ToString("x"));
                Log(Severity.Warning, "[{0}] Initiator error: InitiatorName was not included in the login request", loginIdentifier);
                return(LoginResponseStatusName.InitiatorError);
            }

            if (request.TSIH == 0)
            {
                // Note: An initiator could login with the same ISID to a different target (another session),
                // We should only perform session reinstatement when an initiator is logging in to the same target.

                // For a new session, the request TSIH is zero,
                // As part of the response, the target generates a TSIH.
                connection.Session = m_sessionManager.StartSession(initiatorName, request.ISID);
                connection.CID     = request.CID;
                Log(Severity.Verbose, "[{0}] Session has been started", connection.Session.SessionIdentifier);
                connection.Session.CommandNumberingStarted = true;
                connection.Session.ExpCmdSN = request.CmdSN;

                string sessionType = requestParameters.ValueOf("SessionType");
                if (sessionType == "Discovery")
                {
                    connection.Session.IsDiscovery = true;
                }
                else //sessionType == "Normal" or unspecified (default is Normal)
                {
                    if (requestParameters.ContainsKey("TargetName"))
                    {
                        string targetName = requestParameters.ValueOf("TargetName");
                        return(SetUpNormalSession(request, targetName, connection));
                    }
                    else
                    {
                        // RFC 3720: For any connection within a session whose type is not "Discovery", the first Login Request MUST also include the TargetName key=value pair.
                        Log(Severity.Warning, "[{0}] Initiator error: TargetName was not included in a non-discovery session", connection.ConnectionIdentifier);
                        return(LoginResponseStatusName.InitiatorError);
                    }
                }
            }
            else
            {
                ISCSISession existingSession = m_sessionManager.FindSession(initiatorName, request.ISID, request.TSIH);
                if (existingSession == null)
                {
                    return(LoginResponseStatusName.SessionDoesNotExist);
                }
                else
                {
                    connection.Session = existingSession;
                    ConnectionState existingConnection = m_connectionManager.FindConnection(existingSession, request.CID);
                    if (existingConnection != null)
                    {
                        // do connection reinstatement
                        Log(Severity.Verbose, "[{0}] Initiating implicit logout", existingConnection.ConnectionIdentifier);
                        m_connectionManager.ReleaseConnection(existingConnection);
                    }
                    else
                    {
                        // add a new connection to the session
                        if (m_connectionManager.GetSessionConnections(existingSession).Count > existingSession.MaxConnections)
                        {
                            return(LoginResponseStatusName.TooManyConnections);
                        }
                    }
                    connection.CID = request.CID;
                }
            }
            return(LoginResponseStatusName.Success);
        }
예제 #3
0
        private ISCSIPDU GetLogoutResponsePDU(LogoutRequestPDU request, ConnectionParameters connection)
        {
            Log(Severity.Verbose, "[{0}] Logour 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.
            }
        }