Quellcode durchsuchen

ISCSIServer Code refactoring

Tal Aloni vor 7 Jahren
Ursprung
Commit
015df504c9

+ 260 - 0
ISCSI/ISCSI.Server/ISCSIServer.Login.cs

@@ -0,0 +1,260 @@
+/* Copyright (C) 2012-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+using Utilities;
+
+namespace ISCSI.Server
+{
+    public partial class ISCSIServer
+    {
+        private LoginResponsePDU GetLoginResponsePDU(LoginRequestPDU request, ISCSISession session, ConnectionParameters connection)
+        {
+            LoginResponsePDU response = new LoginResponsePDU();
+            response.Transit = request.Transit;
+            response.Continue = false;
+            
+            // The stage codes are:
+            // 0 - SecurityNegotiation
+            // 1 - LoginOperationalNegotiation
+            // 3 - FullFeaturePhase
+
+            response.CurrentStage = request.CurrentStage;
+            response.NextStage = request.NextStage;
+
+            response.VersionMax = request.VersionMax;
+            response.VersionActive = request.VersionMin;
+            response.ISID = request.ISID;
+
+            response.Status = LoginResponseStatusName.Success;
+
+            response.InitiatorTaskTag = request.InitiatorTaskTag;
+
+            if (request.TSIH == 0)
+            {
+                // For a new session, the request TSIH is zero,
+                // As part of the response, the target generates a TSIH.
+                session.TSIH = GetNextTSIH();
+            }
+            response.TSIH = session.TSIH;
+
+            if (request.Transit && request.Continue)
+            {
+                response.Status = LoginResponseStatusName.InitiatorError;
+                return response;
+            }
+            else if (request.Continue)
+            {
+                response.Status = LoginResponseStatusName.Success;
+                return response;
+            }
+
+            // RFC 3720:  The login process proceeds in two stages - the security negotiation
+            // stage and the operational parameter negotiation stage.  Both stages are optional
+            // but at least one of them has to be present.
+            bool firstLoginRequest = (!session.IsDiscovery && session.Target == null);
+            if (firstLoginRequest)
+            {
+                connection.InitiatorName = request.LoginParameters.ValueOf("InitiatorName");
+                if (String.IsNullOrEmpty(connection.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.
+                    response.Status = LoginResponseStatusName.InitiatorError;
+                    return response;
+                }
+                string sessionType = request.LoginParameters.ValueOf("SessionType");
+                if (sessionType == "Discovery")
+                {
+                    session.IsDiscovery = true;
+                }
+                else //sessionType == "Normal" or unspecified (default is Normal)
+                {
+                    session.IsDiscovery = false;
+                    if (request.LoginParameters.ContainsKey("TargetName"))
+                    {
+                        string targetName = request.LoginParameters.ValueOf("TargetName");
+                        int targetIndex = GetTargetIndex(m_targets, targetName);
+                        if (targetIndex >= 0)
+                        {
+                            session.Target = m_targets[targetIndex];
+                            if (!session.Target.AuthorizeInitiator(connection.InitiatorName, connection.InitiatorEndPoint))
+                            {
+                                response.Status = LoginResponseStatusName.AuthorizationFailure;
+                                return response;
+                            }
+                        }
+                        else
+                        {
+                            response.Status = LoginResponseStatusName.NotFound;
+                            return response;
+                        }
+                    }
+                    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.
+                        response.Status = LoginResponseStatusName.InitiatorError;
+                        return response;
+                    }
+                }
+            }
+
+            if (request.CurrentStage == 0)
+            {
+                response.LoginParameters.Add("AuthMethod", "None");
+                if (session.Target != null)
+                {
+                    // RFC 3720: During the Login Phase the iSCSI target MUST return the TargetPortalGroupTag key with the first Login Response PDU with which it is allowed to do so
+                    response.LoginParameters.Add("TargetPortalGroupTag", "1");
+                }
+
+                if (request.Transit)
+                {
+                    if (request.NextStage == 3)
+                    {
+                        session.IsFullFeaturePhase = true;
+                    }
+                    else if (request.NextStage != 1)
+                    {
+                        response.Status = LoginResponseStatusName.InitiatorError;
+                    }
+                }
+            }
+            else if (request.CurrentStage == 1)
+            {
+                UpdateOperationalParameters(request.LoginParameters, session, connection);
+                response.LoginParameters = GetLoginOperationalParameters(session, connection);
+
+                if (request.Transit)
+                {
+                    if (request.NextStage == 3)
+                    {
+                        session.IsFullFeaturePhase = true;
+                    }
+                    else
+                    {
+                        response.Status = LoginResponseStatusName.InitiatorError;
+                    }
+                }
+            }
+            else
+            {
+                // Not valid
+                response.Status = LoginResponseStatusName.InitiatorError;
+            }
+
+            return response;
+        }
+
+        private static int GetTargetIndex(List<ISCSITarget> targets, string targetName)
+        {
+            for (int index = 0; index < targets.Count; index++)
+            {
+                if (String.Equals(targets[index].TargetName, targetName, StringComparison.InvariantCultureIgnoreCase))
+                {
+                    return index;
+                }
+            }
+            return -1;
+        }
+
+        private static void UpdateOperationalParameters(KeyValuePairList<string, string> loginParameters, ISCSISession session, ConnectionParameters connectionParameters)
+        {
+            string value = loginParameters.ValueOf("MaxRecvDataSegmentLength");
+            if (value != null)
+            {
+                connectionParameters.InitiatorMaxRecvDataSegmentLength = Convert.ToInt32(value);
+            }
+
+            value = loginParameters.ValueOf("MaxConnections");
+            if (value != null)
+            {
+                session.MaxConnections = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxConnections);
+            }
+
+            value = loginParameters.ValueOf("InitialR2T");
+            if (value != null)
+            {
+                session.InitialR2T = (value == "Yes") || ISCSIServer.DesiredParameters.InitialR2T;
+            }
+
+            value = loginParameters.ValueOf("ImmediateData");
+            if (value != null)
+            {
+                session.ImmediateData = (value == "Yes") && ISCSIServer.DesiredParameters.ImmediateData;
+            }
+
+            value = loginParameters.ValueOf("MaxBurstLength");
+            if (value != null)
+            {
+                session.MaxBurstLength = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxBurstLength);
+            }
+
+            value = loginParameters.ValueOf("FirstBurstLength");
+            if (value != null)
+            {
+                session.FirstBurstLength = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.FirstBurstLength);
+            }
+
+            value = loginParameters.ValueOf("DataPDUInOrder");
+            if (value != null)
+            {
+                session.DataPDUInOrder = (value == "Yes") || ISCSIServer.DesiredParameters.DataPDUInOrder;
+            }
+
+            value = loginParameters.ValueOf("DataSequenceInOrder");
+            if (value != null)
+            {
+                session.DataSequenceInOrder = (value == "Yes") || ISCSIServer.DesiredParameters.DataSequenceInOrder;
+            }
+
+            value = loginParameters.ValueOf("DefaultTime2Wait");
+            if (value != null)
+            {
+                session.DefaultTime2Wait = Math.Max(Convert.ToInt32(value), ISCSIServer.DesiredParameters.DefaultTime2Wait);
+            }
+
+            value = loginParameters.ValueOf("DefaultTime2Retain");
+            if (value != null)
+            {
+                session.DefaultTime2Retain = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.DefaultTime2Retain);
+            }
+
+            value = loginParameters.ValueOf("MaxOutstandingR2T");
+            if (value != null)
+            {
+                session.MaxOutstandingR2T = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxOutstandingR2T);
+            }
+        }
+
+        private static KeyValuePairList<string, string> GetLoginOperationalParameters(ISCSISession session, ConnectionParameters connectionParameters)
+        {
+            KeyValuePairList<string, string> loginParameters = new KeyValuePairList<string, string>();
+            loginParameters.Add("HeaderDigest", "None");
+            loginParameters.Add("DataDigest", "None");
+            loginParameters.Add("MaxRecvDataSegmentLength", connectionParameters.TargetMaxRecvDataSegmentLength.ToString());
+            if (!session.IsDiscovery)
+            {
+                loginParameters.Add("MaxConnections", session.MaxConnections.ToString());
+                loginParameters.Add("InitialR2T", session.InitialR2T ? "Yes" : "No");    // Microsoft iSCSI Target support InitialR2T = No
+                loginParameters.Add("ImmediateData", session.ImmediateData ? "Yes" : "No");
+                loginParameters.Add("MaxBurstLength", session.MaxBurstLength.ToString());
+                loginParameters.Add("FirstBurstLength", session.FirstBurstLength.ToString());
+                loginParameters.Add("MaxOutstandingR2T", session.MaxOutstandingR2T.ToString());
+                loginParameters.Add("DataPDUInOrder", session.DataPDUInOrder ? "Yes" : "No");
+                loginParameters.Add("DataSequenceInOrder", session.DataSequenceInOrder ? "Yes" : "No");
+                loginParameters.Add("ErrorRecoveryLevel", session.ErrorRecoveryLevel.ToString());
+            }
+            loginParameters.Add("DefaultTime2Wait", session.DefaultTime2Wait.ToString());
+            loginParameters.Add("DefaultTime2Retain", session.DefaultTime2Retain.ToString());
+            
+            return loginParameters;
+        }
+    }
+}

+ 250 - 0
ISCSI/ISCSI.Server/ISCSIServer.PDUProcessor.cs

@@ -0,0 +1,250 @@
+/* Copyright (C) 2012-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using Utilities;
+
+namespace ISCSI.Server
+{
+    public partial class ISCSIServer
+    {
+        private bool ValidateCommandNumbering(ISCSIPDU pdu, ConnectionState state)
+        {
+            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;
+        }
+
+        private void ProcessPDU(ISCSIPDU pdu, ConnectionState state)
+        {
+            Log(Severity.Trace, "Entering ProcessPDU");
+            
+            if (!state.Session.IsFullFeaturePhase)
+            {
+                if (pdu is LoginRequestPDU)
+                {
+                    LoginRequestPDU request = (LoginRequestPDU)pdu;
+                    Log(Severity.Verbose, "[{0}] 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
+                        ConnectionState existingConnection = m_connectionManager.FindConnection(request.ISID, request.TSIH, request.CID);
+                        if (existingConnection != null)
+                        {
+                            // Perform implicit logout
+                            Log(Severity.Verbose, "[{0}] Initiating implicit logout", state.ConnectionIdentifier);
+                            // Wait for pending I/O to complete.
+                            existingConnection.RunningSCSICommands.WaitUntilZero();
+                            SocketUtils.ReleaseSocket(existingConnection.ClientSocket);
+                            existingConnection.SendQueue.Stop();
+                            m_connectionManager.RemoveConnection(existingConnection);
+                            Log(Severity.Verbose, "[{0}] Implicit logout completed", state.ConnectionIdentifier);
+                        }
+                    }
+                    LoginResponsePDU response = GetLoginResponsePDU(request, state.Session, state.ConnectionParameters);
+                    if (state.Session.IsFullFeaturePhase)
+                    {
+                        state.Session.ISID = request.ISID;
+                        state.ConnectionParameters.CID = request.CID;
+                        m_connectionManager.AddConnection(state);
+                    }
+                    Log(Severity.Verbose, "[{0}] Login Response parameters: {1}", state.ConnectionIdentifier, KeyValuePairUtils.ToString(response.LoginParameters));
+                    state.SendQueue.Enqueue(response);
+                }
+                else
+                {
+                    // Before the Full Feature Phase is established, only Login Request and Login Response PDUs are allowed.
+                    Log(Severity.Error, "[{0}] Improper command during login phase, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
+                    if (state.Session.TSIH == 0)
+                    {
+                        // 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 = ServerResponseHelper.GetTextResponsePDU(request, m_targets);
+                    state.SendQueue.Enqueue(response);
+                }
+                else if (pdu is LogoutRequestPDU)
+                {
+                    Log(Severity.Verbose, "[{0}] Logour Request", state.ConnectionIdentifier);
+                    LogoutRequestPDU request = (LogoutRequestPDU)pdu;
+                    if (state.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(pdu.GetBytes(), 0, 48);
+                        state.SendQueue.Enqueue(reject);
+                    }
+                    else
+                    {
+                        List<ConnectionState> connectionsToClose = new List<ConnectionState>();
+                        if (request.ReasonCode == LogoutReasonCode.CloseTheSession)
+                        {
+                            connectionsToClose = m_connectionManager.GetSessionConnections(state.Session.ISID, state.Session.TSIH);
+                        }
+                        else
+                        {
+                            // 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(state.Session.ISID, state.Session.TSIH, request.CID);
+                            if (existingConnection != null && existingConnection != state)
+                            {
+                                connectionsToClose.Add(existingConnection);
+                            }
+                            connectionsToClose.Add(state);
+                        }
+
+                        foreach (ConnectionState connection in connectionsToClose)
+                        {
+                            // Wait for pending I/O to complete.
+                            connection.RunningSCSICommands.WaitUntilZero();
+                            if (connection != state)
+                            {
+                                SocketUtils.ReleaseSocket(connection.ClientSocket);
+                            }
+                            m_connectionManager.RemoveConnection(connection);
+                        }
+                        LogoutResponsePDU response = ServerResponseHelper.GetLogoutResponsePDU(request);
+                        state.SendQueue.Enqueue(response);
+                        // connection will be closed after a LogoutResponsePDU has been sent.
+                    }
+                }
+                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.Error, "[{0}] 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.Target, state.Session, state.ConnectionParameters, out commandsToExecute);
+                        }
+                        catch (InvalidTargetTransferTagException ex)
+                        {
+                            Log(Severity.Error, "[{0}] 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.Target, state.Session, 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.Error, "[{0}] Protocol 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);
+                }
+            }
+            Log(Severity.Trace, "Leaving ProcessPDU");
+        }
+    }
+}

+ 10 - 235
ISCSI/ISCSI.Server/ISCSIServer.cs

@@ -15,8 +15,6 @@ using Utilities;
 
 namespace ISCSI.Server
 {
-    public delegate ushort GetNextTSIH();
-
     /// <summary>
     /// an iSCSI server that can serve multiple iSCSI targets
     /// </summary>
@@ -242,7 +240,16 @@ namespace ISCSI.Server
                         }
                         else
                         {
-                            ProcessPDU(pdu, state);
+                            bool valid = ValidateCommandNumbering(pdu, state);
+                            if (valid)
+                            {
+                                ProcessPDU(pdu, state);
+                            }
+                            else
+                            {
+                                // We ignore this PDU
+                                Log(Severity.Warning, "[{0}] Ignoring PDU with CmdSN outside of expected range", state.ConnectionIdentifier);
+                            }
                         }
                     }
                     
@@ -268,238 +275,6 @@ namespace ISCSI.Server
             }
         }
 
-        private void ProcessPDU(ISCSIPDU pdu, ConnectionState state)
-        {
-            Socket clientSocket = state.ClientSocket;
-            
-            uint? cmdSN = PDUHelper.GetCmdSN(pdu);
-            Log(Severity.Trace, "Entering ProcessPDU");
-            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)
-                    {
-                        Log(Severity.Error, "[{0}] CmdSN outside of expected range", state.ConnectionIdentifier);
-                        // We ignore this PDU
-                        Log(Severity.Trace, "Leaving ProcessPDU");
-                        return;
-                    }
-                }
-                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++;
-                    }
-                }
-            }
-
-            if (!state.Session.IsFullFeaturePhase)
-            {
-                if (pdu is LoginRequestPDU)
-                {
-                    LoginRequestPDU request = (LoginRequestPDU)pdu;
-                    Log(Severity.Verbose, "[{0}] 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
-                        ConnectionState existingConnection = m_connectionManager.FindConnection(request.ISID, request.TSIH, request.CID);
-                        if (existingConnection != null)
-                        {
-                            // Perform implicit logout
-                            Log(Severity.Verbose, "[{0}] Initiating implicit logout", state.ConnectionIdentifier);
-                            // Wait for pending I/O to complete.
-                            existingConnection.RunningSCSICommands.WaitUntilZero();
-                            SocketUtils.ReleaseSocket(existingConnection.ClientSocket);
-                            existingConnection.SendQueue.Stop();
-                            m_connectionManager.RemoveConnection(existingConnection);
-                            Log(Severity.Verbose, "[{0}] Implicit logout completed", state.ConnectionIdentifier);
-                        }
-                    }
-                    LoginResponsePDU response = ServerResponseHelper.GetLoginResponsePDU(request, m_targets, state.Session, state.ConnectionParameters, GetNextTSIH);
-                    if (state.Session.IsFullFeaturePhase)
-                    {
-                        state.Session.ISID = request.ISID;
-                        state.ConnectionParameters.CID = request.CID;
-                        m_connectionManager.AddConnection(state);
-                    }
-                    Log(Severity.Verbose, "[{0}] Login Response parameters: {1}", state.ConnectionIdentifier, KeyValuePairUtils.ToString(response.LoginParameters));
-                    state.SendQueue.Enqueue(response);
-                }
-                else
-                {
-                    // Before the Full Feature Phase is established, only Login Request and Login Response PDUs are allowed.
-                    Log(Severity.Error, "[{0}] Improper command during login phase, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
-                    if (state.Session.TSIH == 0)
-                    {
-                        // 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.
-                        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 = ServerResponseHelper.GetTextResponsePDU(request, m_targets);
-                    state.SendQueue.Enqueue(response);
-                }
-                else if (pdu is LogoutRequestPDU)
-                {
-                    Log(Severity.Verbose, "[{0}] Logour Request", state.ConnectionIdentifier);
-                    LogoutRequestPDU request = (LogoutRequestPDU)pdu;
-                    if (state.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(pdu.GetBytes(), 0, 48);
-                        state.SendQueue.Enqueue(reject);
-                    }
-                    else
-                    {
-                        List<ConnectionState> connectionsToClose = new List<ConnectionState>();
-                        if (request.ReasonCode == LogoutReasonCode.CloseTheSession)
-                        {
-                            connectionsToClose = m_connectionManager.GetSessionConnections(state.Session.ISID, state.Session.TSIH);
-                        }
-                        else
-                        {
-                            // 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(state.Session.ISID, state.Session.TSIH, request.CID);
-                            if (existingConnection != null && existingConnection != state)
-                            {
-                                connectionsToClose.Add(existingConnection);
-                            }
-                            connectionsToClose.Add(state);
-                        }
-
-                        foreach (ConnectionState connection in connectionsToClose)
-                        {
-                            // Wait for pending I/O to complete.
-                            connection.RunningSCSICommands.WaitUntilZero();
-                            if (connection != state)
-                            {
-                                SocketUtils.ReleaseSocket(connection.ClientSocket);
-                            }
-                            m_connectionManager.RemoveConnection(connection);
-                        }
-                        LogoutResponsePDU response = ServerResponseHelper.GetLogoutResponsePDU(request);
-                        state.SendQueue.Enqueue(response);
-                        // connection will be closed after a LogoutResponsePDU has been sent.
-                    }
-                }
-                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.Error, "[{0}] 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.Target, state.Session, state.ConnectionParameters, out commandsToExecute);
-                        }
-                        catch (InvalidTargetTransferTagException ex)
-                        {
-                            Log(Severity.Error, "[{0}] 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.Target, state.Session, 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.Error, "[{0}] Protocol 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);
-                }
-            }
-            Log(Severity.Trace, "Leaving ProcessPDU");
-        }
-
         private void ProcessSendQueue(ConnectionState state)
         {
             while (true)

+ 0 - 247
ISCSI/ISCSI.Server/ServerResponseHelper.cs

@@ -6,7 +6,6 @@
  */
 using System;
 using System.Collections.Generic;
-using System.Net;
 using System.Text;
 using Utilities;
 
@@ -14,9 +13,6 @@ namespace ISCSI.Server
 {
     internal class ServerResponseHelper
     {
-        // CmdSN is session wide
-        // StatSN is per connection
-        
         internal static NOPInPDU GetNOPResponsePDU(NOPOutPDU request)
         {
             NOPInPDU response = new NOPInPDU();
@@ -29,249 +25,6 @@ namespace ISCSI.Server
             return response;
         }
 
-        internal static LoginResponsePDU GetLoginResponsePDU(LoginRequestPDU request, List<ISCSITarget> availableTargets, ISCSISession session, ConnectionParameters connection, GetNextTSIH GetNextTSIH)
-        {
-            LoginResponsePDU response = new LoginResponsePDU();
-            response.Transit = request.Transit;
-            response.Continue = false;
-            
-            // The stage codes are:
-            // 0 - SecurityNegotiation
-            // 1 - LoginOperationalNegotiation
-            // 3 - FullFeaturePhase
-
-            response.CurrentStage = request.CurrentStage;
-            response.NextStage = request.NextStage;
-
-            response.VersionMax = request.VersionMax;
-            response.VersionActive = request.VersionMin;
-            response.ISID = request.ISID;
-
-            response.Status = LoginResponseStatusName.Success;
-
-            response.InitiatorTaskTag = request.InitiatorTaskTag;
-
-            if (request.TSIH == 0)
-            {
-                // For a new session, the request TSIH is zero,
-                // As part of the response, the target generates a TSIH.
-                session.TSIH = GetNextTSIH();
-            }
-            response.TSIH = session.TSIH;
-
-            if (request.Transit && request.Continue)
-            {
-                response.Status = LoginResponseStatusName.InitiatorError;
-                return response;
-            }
-            else if (request.Continue)
-            {
-                response.Status = LoginResponseStatusName.Success;
-                return response;
-            }
-
-            // RFC 3720:  The login process proceeds in two stages - the security negotiation
-            // stage and the operational parameter negotiation stage.  Both stages are optional
-            // but at least one of them has to be present.
-            bool firstLoginRequest = (!session.IsDiscovery && session.Target == null);
-            if (firstLoginRequest)
-            {
-                connection.InitiatorName = request.LoginParameters.ValueOf("InitiatorName");
-                if (String.IsNullOrEmpty(connection.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.
-                    response.Status = LoginResponseStatusName.InitiatorError;
-                    return response;
-                }
-                string sessionType = request.LoginParameters.ValueOf("SessionType");
-                if (sessionType == "Discovery")
-                {
-                    session.IsDiscovery = true;
-                }
-                else //sessionType == "Normal" or unspecified (default is Normal)
-                {
-                    session.IsDiscovery = false;
-                    if (request.LoginParameters.ContainsKey("TargetName"))
-                    {
-                        string targetName = request.LoginParameters.ValueOf("TargetName");
-                        int targetIndex = GetTargetIndex(availableTargets, targetName);
-                        if (targetIndex >= 0)
-                        {
-                            session.Target = availableTargets[targetIndex];
-                            if (!session.Target.AuthorizeInitiator(connection.InitiatorName, connection.InitiatorEndPoint))
-                            {
-                                response.Status = LoginResponseStatusName.AuthorizationFailure;
-                                return response;
-                            }
-                        }
-                        else
-                        {
-                            response.Status = LoginResponseStatusName.NotFound;
-                            return response;
-                        }
-                    }
-                    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.
-                        response.Status = LoginResponseStatusName.InitiatorError;
-                        return response;
-                    }
-                }
-            }
-
-            if (request.CurrentStage == 0)
-            {
-                response.LoginParameters.Add("AuthMethod", "None");
-                if (session.Target != null)
-                {
-                    // RFC 3720: During the Login Phase the iSCSI target MUST return the TargetPortalGroupTag key with the first Login Response PDU with which it is allowed to do so
-                    response.LoginParameters.Add("TargetPortalGroupTag", "1");
-                }
-
-                if (request.Transit)
-                {
-                    if (request.NextStage == 3)
-                    {
-                        session.IsFullFeaturePhase = true;
-                    }
-                    else if (request.NextStage != 1)
-                    {
-                        response.Status = LoginResponseStatusName.InitiatorError;
-                    }
-                }
-            }
-            else if (request.CurrentStage == 1)
-            {
-                UpdateOperationalParameters(request.LoginParameters, session, connection);
-                response.LoginParameters = GetLoginOperationalParameters(session, connection);
-
-                if (request.Transit)
-                {
-                    if (request.NextStage == 3)
-                    {
-                        session.IsFullFeaturePhase = true;
-                    }
-                    else
-                    {
-                        response.Status = LoginResponseStatusName.InitiatorError;
-                    }
-                }
-            }
-            else
-            {
-                // Not valid
-                response.Status = LoginResponseStatusName.InitiatorError;
-            }
-
-            return response;
-        }
-
-        private static int GetTargetIndex(List<ISCSITarget> targets, string targetName)
-        {
-            for (int index = 0; index < targets.Count; index++)
-            {
-                if (String.Equals(targets[index].TargetName, targetName, StringComparison.InvariantCultureIgnoreCase))
-                {
-                    return index;
-                }
-            }
-            return -1;
-        }
-
-        public static void UpdateOperationalParameters(KeyValuePairList<string, string> loginParameters, ISCSISession session, ConnectionParameters connectionParameters)
-        {
-            string value = loginParameters.ValueOf("MaxRecvDataSegmentLength");
-            if (value != null)
-            {
-                connectionParameters.InitiatorMaxRecvDataSegmentLength = Convert.ToInt32(value);
-            }
-
-            value = loginParameters.ValueOf("MaxConnections");
-            if (value != null)
-            {
-                session.MaxConnections = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxConnections);
-            }
-
-            value = loginParameters.ValueOf("InitialR2T");
-            if (value != null)
-            {
-                session.InitialR2T = (value == "Yes") || ISCSIServer.DesiredParameters.InitialR2T;
-            }
-
-            value = loginParameters.ValueOf("ImmediateData");
-            if (value != null)
-            {
-                session.ImmediateData = (value == "Yes") && ISCSIServer.DesiredParameters.ImmediateData;
-            }
-
-            value = loginParameters.ValueOf("MaxBurstLength");
-            if (value != null)
-            {
-                session.MaxBurstLength = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxBurstLength);
-            }
-
-            value = loginParameters.ValueOf("FirstBurstLength");
-            if (value != null)
-            {
-                session.FirstBurstLength = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.FirstBurstLength);
-            }
-
-            value = loginParameters.ValueOf("DataPDUInOrder");
-            if (value != null)
-            {
-                session.DataPDUInOrder = (value == "Yes") || ISCSIServer.DesiredParameters.DataPDUInOrder;
-            }
-
-            value = loginParameters.ValueOf("DataSequenceInOrder");
-            if (value != null)
-            {
-                session.DataSequenceInOrder = (value == "Yes") || ISCSIServer.DesiredParameters.DataSequenceInOrder;
-            }
-
-            value = loginParameters.ValueOf("DefaultTime2Wait");
-            if (value != null)
-            {
-                session.DefaultTime2Wait = Math.Max(Convert.ToInt32(value), ISCSIServer.DesiredParameters.DefaultTime2Wait);
-            }
-
-            value = loginParameters.ValueOf("DefaultTime2Retain");
-            if (value != null)
-            {
-                session.DefaultTime2Retain = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.DefaultTime2Retain);
-            }
-
-            value = loginParameters.ValueOf("MaxOutstandingR2T");
-            if (value != null)
-            {
-                session.MaxOutstandingR2T = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxOutstandingR2T);
-            }
-        }
-
-        public static KeyValuePairList<string, string> GetLoginOperationalParameters(ISCSISession session, ConnectionParameters connectionParameters)
-        {
-            KeyValuePairList<string, string> loginParameters = new KeyValuePairList<string, string>();
-            loginParameters.Add("HeaderDigest", "None");
-            loginParameters.Add("DataDigest", "None");
-            loginParameters.Add("MaxRecvDataSegmentLength", connectionParameters.TargetMaxRecvDataSegmentLength.ToString());
-            if (!session.IsDiscovery)
-            {
-                loginParameters.Add("MaxConnections", session.MaxConnections.ToString());
-                loginParameters.Add("InitialR2T", session.InitialR2T ? "Yes" : "No");    // Microsoft iSCSI Target support InitialR2T = No
-                loginParameters.Add("ImmediateData", session.ImmediateData ? "Yes" : "No");
-                loginParameters.Add("MaxBurstLength", session.MaxBurstLength.ToString());
-                loginParameters.Add("FirstBurstLength", session.FirstBurstLength.ToString());
-                loginParameters.Add("MaxOutstandingR2T", session.MaxOutstandingR2T.ToString());
-                loginParameters.Add("DataPDUInOrder", session.DataPDUInOrder ? "Yes" : "No");
-                loginParameters.Add("DataSequenceInOrder", session.DataSequenceInOrder ? "Yes" : "No");
-                loginParameters.Add("ErrorRecoveryLevel", session.ErrorRecoveryLevel.ToString());
-            }
-            loginParameters.Add("DefaultTime2Wait", session.DefaultTime2Wait.ToString());
-            loginParameters.Add("DefaultTime2Retain", session.DefaultTime2Retain.ToString());
-            
-            return loginParameters;
-        }
-
         internal static TextResponsePDU GetTextResponsePDU(TextRequestPDU request, List<ISCSITarget> targets)
         {
             TextResponsePDU response = new TextResponsePDU();

+ 2 - 0
ISCSI/ISCSI.csproj

@@ -69,7 +69,9 @@
     <Compile Include="ISCSI.Server\DefaultParameters.cs" />
     <Compile Include="ISCSI.Server\Exceptions\InvalidTargetTransferTagException.cs" />
     <Compile Include="ISCSI.Server\ISCSIServer.cs" />
+    <Compile Include="ISCSI.Server\ISCSIServer.Login.cs" />
     <Compile Include="ISCSI.Server\ISCSIServer.Parameters.cs" />
+    <Compile Include="ISCSI.Server\ISCSIServer.PDUProcessor.cs" />
     <Compile Include="ISCSI.Server\ISCSISession.cs" />
     <Compile Include="ISCSI.Server\ISCSITarget.cs" />
     <Compile Include="ISCSI.Server\PDUHelper.cs" />