Jelajahi Sumber

Support for login requests with the Continue bit set to 1

Tal Aloni 8 tahun lalu
induk
melakukan
c0068284af

+ 42 - 38
ISCSI/ISCSI.Client/ClientHelper.cs

@@ -33,19 +33,21 @@ namespace ISCSI.Client
             request.Transit = true;
             request.VersionMax = 0;
             request.VersionMin = 0;
-            request.LoginParameters.Add("InitiatorName", initiatorName);
-            request.LoginParameters.Add("AuthMethod", "None");
+            KeyValuePairList<string, string> loginParameters = new KeyValuePairList<string, string>();
+            loginParameters.Add("InitiatorName", initiatorName);
+            loginParameters.Add("AuthMethod", "None");
             if (targetName == null)
             {
-                request.LoginParameters.Add("SessionType", "Discovery");
+                loginParameters.Add("SessionType", "Discovery");
             }
             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.
-                request.LoginParameters.Add("SessionType", "Normal");
-                request.LoginParameters.Add("TargetName", targetName);
+                loginParameters.Add("SessionType", "Normal");
+                loginParameters.Add("TargetName", targetName);
                 
             }
+            request.LoginParameters = loginParameters;
             return request;
         }
 
@@ -62,24 +64,25 @@ namespace ISCSI.Client
             request.Transit = true;
             request.VersionMax = 0;
             request.VersionMin = 0;
-            request.LoginParameters.Add("HeaderDigest", "None");
-            request.LoginParameters.Add("DataDigest", "None");
-            request.LoginParameters.Add("MaxRecvDataSegmentLength", connection.InitiatorMaxRecvDataSegmentLength.ToString());
+            KeyValuePairList<string, string> loginParameters = new KeyValuePairList<string, string>();
+            loginParameters.Add("HeaderDigest", "None");
+            loginParameters.Add("DataDigest", "None");
+            loginParameters.Add("MaxRecvDataSegmentLength", connection.InitiatorMaxRecvDataSegmentLength.ToString());
             if (!isDiscovery)
             {
-                request.LoginParameters.Add("MaxConnections", ISCSIClient.DesiredParameters.MaxConnections.ToString());
-                request.LoginParameters.Add("InitialR2T", ISCSIClient.DesiredParameters.InitialR2T ? "Yes" : "No");
-                request.LoginParameters.Add("ImmediateData", ISCSIClient.DesiredParameters.ImmediateData ? "Yes" : "No");
-                request.LoginParameters.Add("MaxBurstLength", ISCSIClient.DesiredParameters.MaxBurstLength.ToString());
-                request.LoginParameters.Add("FirstBurstLength", ISCSIClient.DesiredParameters.FirstBurstLength.ToString());
-                request.LoginParameters.Add("MaxOutstandingR2T", ISCSIClient.DesiredParameters.MaxOutstandingR2T.ToString());
-                request.LoginParameters.Add("DataPDUInOrder", ISCSIClient.DesiredParameters.DataPDUInOrder ? "Yes" : "No");
-                request.LoginParameters.Add("DataSequenceInOrder", ISCSIClient.DesiredParameters.DataSequenceInOrder ? "Yes" : "No");
-                request.LoginParameters.Add("ErrorRecoveryLevel", ISCSIClient.DesiredParameters.ErrorRecoveryLevel.ToString());
+                loginParameters.Add("MaxConnections", ISCSIClient.DesiredParameters.MaxConnections.ToString());
+                loginParameters.Add("InitialR2T", ISCSIClient.DesiredParameters.InitialR2T ? "Yes" : "No");
+                loginParameters.Add("ImmediateData", ISCSIClient.DesiredParameters.ImmediateData ? "Yes" : "No");
+                loginParameters.Add("MaxBurstLength", ISCSIClient.DesiredParameters.MaxBurstLength.ToString());
+                loginParameters.Add("FirstBurstLength", ISCSIClient.DesiredParameters.FirstBurstLength.ToString());
+                loginParameters.Add("MaxOutstandingR2T", ISCSIClient.DesiredParameters.MaxOutstandingR2T.ToString());
+                loginParameters.Add("DataPDUInOrder", ISCSIClient.DesiredParameters.DataPDUInOrder ? "Yes" : "No");
+                loginParameters.Add("DataSequenceInOrder", ISCSIClient.DesiredParameters.DataSequenceInOrder ? "Yes" : "No");
+                loginParameters.Add("ErrorRecoveryLevel", ISCSIClient.DesiredParameters.ErrorRecoveryLevel.ToString());
             }
-            request.LoginParameters.Add("DefaultTime2Wait", ISCSIClient.DesiredParameters.DefaultTime2Wait.ToString());
-            request.LoginParameters.Add("DefaultTime2Retain", ISCSIClient.DesiredParameters.DefaultTime2Retain.ToString());
-            
+            loginParameters.Add("DefaultTime2Wait", ISCSIClient.DesiredParameters.DefaultTime2Wait.ToString());
+            loginParameters.Add("DefaultTime2Retain", ISCSIClient.DesiredParameters.DefaultTime2Retain.ToString());
+            request.LoginParameters = loginParameters;
             return request;
         }
 
@@ -96,33 +99,34 @@ namespace ISCSI.Client
             request.Transit = true;
             request.VersionMax = 0;
             request.VersionMin = 0;
-            request.LoginParameters.Add("InitiatorName", initiatorName);
+            KeyValuePairList<string, string> loginParameters = new KeyValuePairList<string, string>();
+            loginParameters.Add("InitiatorName", initiatorName);
             if (targetName == null)
             {
-                request.LoginParameters.Add("SessionType", "Discovery");
+                loginParameters.Add("SessionType", "Discovery");
             }
             else
             {
-                request.LoginParameters.Add("SessionType", "Normal");
-                request.LoginParameters.Add("TargetName", targetName);
+                loginParameters.Add("SessionType", "Normal");
+                loginParameters.Add("TargetName", targetName);
             }
-            request.LoginParameters.Add("DataDigest", "None");
-            request.LoginParameters.Add("MaxRecvDataSegmentLength", connection.InitiatorMaxRecvDataSegmentLength.ToString());
+            loginParameters.Add("DataDigest", "None");
+            loginParameters.Add("MaxRecvDataSegmentLength", connection.InitiatorMaxRecvDataSegmentLength.ToString());
             if (targetName != null)
             {
-                request.LoginParameters.Add("MaxConnections", ISCSIClient.DesiredParameters.MaxConnections.ToString());
-                request.LoginParameters.Add("InitialR2T", ISCSIClient.DesiredParameters.InitialR2T ? "Yes" : "No");
-                request.LoginParameters.Add("ImmediateData", ISCSIClient.DesiredParameters.ImmediateData ? "Yes" : "No");
-                request.LoginParameters.Add("MaxBurstLength", ISCSIClient.DesiredParameters.MaxBurstLength.ToString());
-                request.LoginParameters.Add("FirstBurstLength", ISCSIClient.DesiredParameters.FirstBurstLength.ToString());
-                request.LoginParameters.Add("MaxOutstandingR2T", ISCSIClient.DesiredParameters.MaxOutstandingR2T.ToString());
-                request.LoginParameters.Add("DataPDUInOrder", ISCSIClient.DesiredParameters.DataPDUInOrder ? "Yes" : "No");
-                request.LoginParameters.Add("DataSequenceInOrder", ISCSIClient.DesiredParameters.DataSequenceInOrder ? "Yes" : "No");
-                request.LoginParameters.Add("ErrorRecoveryLevel", ISCSIClient.DesiredParameters.ErrorRecoveryLevel.ToString());
+                loginParameters.Add("MaxConnections", ISCSIClient.DesiredParameters.MaxConnections.ToString());
+                loginParameters.Add("InitialR2T", ISCSIClient.DesiredParameters.InitialR2T ? "Yes" : "No");
+                loginParameters.Add("ImmediateData", ISCSIClient.DesiredParameters.ImmediateData ? "Yes" : "No");
+                loginParameters.Add("MaxBurstLength", ISCSIClient.DesiredParameters.MaxBurstLength.ToString());
+                loginParameters.Add("FirstBurstLength", ISCSIClient.DesiredParameters.FirstBurstLength.ToString());
+                loginParameters.Add("MaxOutstandingR2T", ISCSIClient.DesiredParameters.MaxOutstandingR2T.ToString());
+                loginParameters.Add("DataPDUInOrder", ISCSIClient.DesiredParameters.DataPDUInOrder ? "Yes" : "No");
+                loginParameters.Add("DataSequenceInOrder", ISCSIClient.DesiredParameters.DataSequenceInOrder ? "Yes" : "No");
+                loginParameters.Add("ErrorRecoveryLevel", ISCSIClient.DesiredParameters.ErrorRecoveryLevel.ToString());
             }
-            request.LoginParameters.Add("DefaultTime2Wait", ISCSIClient.DesiredParameters.DefaultTime2Wait.ToString());
-            request.LoginParameters.Add("DefaultTime2Retain", ISCSIClient.DesiredParameters.DefaultTime2Retain.ToString());
-
+            loginParameters.Add("DefaultTime2Wait", ISCSIClient.DesiredParameters.DefaultTime2Wait.ToString());
+            loginParameters.Add("DefaultTime2Retain", ISCSIClient.DesiredParameters.DefaultTime2Retain.ToString());
+            request.LoginParameters = loginParameters;
             return request;
         }
 

+ 2 - 1
ISCSI/ISCSI.Client/ISCSIClient.cs

@@ -97,7 +97,8 @@ namespace ISCSI.Client
                 response = WaitForPDU(request.InitiatorTaskTag) as LoginResponsePDU;
                 if (response != null && response.Status == LoginResponseStatusName.Success)
                 {
-                    ClientHelper.UpdateOperationalParameters(response.LoginParameters, m_session, m_connection);
+                    KeyValuePairList<string, string> loginParameters = KeyValuePairUtils.GetKeyValuePairList(response.LoginParametersText);
+                    ClientHelper.UpdateOperationalParameters(loginParameters, m_session, m_connection);
                     return true;
                 }
             }

+ 11 - 5
ISCSI/ISCSI.PDU/LoginRequestPDU.cs

@@ -27,7 +27,7 @@ namespace ISCSI
         public uint CmdSN;
         public uint ExpStatSN;
 
-        public KeyValuePairList<string, string> LoginParameters = new KeyValuePairList<string,string>(); // in text request format
+        public string LoginParametersText = String.Empty; // A key=value pair can start in one PDU and continue on the next
 
         public LoginRequestPDU() : base()
         {
@@ -52,8 +52,7 @@ namespace ISCSI
             CmdSN = BigEndianConverter.ToUInt32(OpCodeSpecific, 4);
             ExpStatSN = BigEndianConverter.ToUInt32(OpCodeSpecific, 8);
 
-            string parametersString = Encoding.ASCII.GetString(Data);
-            LoginParameters = KeyValuePairUtils.GetKeyValuePairList(parametersString);
+            LoginParametersText = Encoding.ASCII.GetString(Data);
         }
 
         public override byte[] GetBytes()
@@ -79,10 +78,17 @@ namespace ISCSI
             Array.Copy(BigEndianConverter.GetBytes(CmdSN), 0, OpCodeSpecific, 4, 4);
             Array.Copy(BigEndianConverter.GetBytes(ExpStatSN), 0, OpCodeSpecific, 8, 4);
 
-            string parametersString = KeyValuePairUtils.ToNullDelimitedString(LoginParameters);
-            Data = ASCIIEncoding.ASCII.GetBytes(parametersString);
+            Data = ASCIIEncoding.ASCII.GetBytes(LoginParametersText);
 
             return base.GetBytes();
         }
+
+        public KeyValuePairList<string, string> LoginParameters
+        {
+            set
+            {
+                LoginParametersText = KeyValuePairUtils.ToNullDelimitedString(value);
+            }
+        }
     }
 }

+ 10 - 3
ISCSI/ISCSI.PDU/LoginResponsePDU.cs

@@ -27,7 +27,7 @@ namespace ISCSI
         public uint ExpCmdSN;
         public uint MaxCmdSN;
         public LoginResponseStatusName Status; // StatusClass & StatusDetail
-        public KeyValuePairList<string, string> LoginParameters = new KeyValuePairList<string,string>(); // in text request format
+        public string LoginParametersText = String.Empty; // A key=value pair can start in one PDU and continue on the next
 
         public LoginResponsePDU() : base()
         {
@@ -78,10 +78,17 @@ namespace ISCSI
             Array.Copy(BigEndianConverter.GetBytes(MaxCmdSN), 0, OpCodeSpecific, 12, 4);
             Array.Copy(BigEndianConverter.GetBytes((ushort)Status), 0, OpCodeSpecific, 16, 2);
 
-            string parametersString = KeyValuePairUtils.ToNullDelimitedString(LoginParameters);
-            Data = ASCIIEncoding.ASCII.GetBytes(parametersString);
+            Data = ASCIIEncoding.ASCII.GetBytes(LoginParametersText);
             
             return base.GetBytes();
         }
+
+        public KeyValuePairList<string, string> LoginParameters
+        {
+            set
+            {
+                LoginParametersText = KeyValuePairUtils.ToNullDelimitedString(value);
+            }
+        }
     }
 }

+ 1 - 1
ISCSI/ISCSI.PDU/TextRequestPDU.cs

@@ -20,7 +20,7 @@ namespace ISCSI
         public uint CmdSN;
         public uint ExpStatSN;
 
-        public string Text;
+        public string Text = String.Empty;
 
         public TextRequestPDU() : base()
         {

+ 1 - 1
ISCSI/ISCSI.PDU/TextResponsePDU.cs

@@ -21,7 +21,7 @@ namespace ISCSI
         public uint ExpCmdSN;
         public uint MaxCmdSN;
 
-        public string Text;
+        public string Text = String.Empty;
 
         public TextResponsePDU() : base()
         {

+ 23 - 0
ISCSI/ISCSI.Server/ConnectionParameters.cs

@@ -34,9 +34,32 @@ namespace ISCSI.Server
         public int TargetMaxRecvDataSegmentLength = ISCSIServer.DeclaredParameters.MaxRecvDataSegmentLength;
 
         public uint StatSN = 0; // Initial StatSN, any number will do
+        private Dictionary<uint, string> m_textSequences = new Dictionary<uint, string>();
         // Dictionary of current transfers: <transfer-tag, TransferEntry>
         private Dictionary<uint, TransferEntry> n_transfers = new Dictionary<uint, TransferEntry>();
 
+        public string AddTextToSequence(uint initiatorTaskTag, string text)
+        {
+            string precedingText;
+            if (m_textSequences.TryGetValue(initiatorTaskTag, out precedingText))
+            {
+                string sequence = precedingText + text;
+                m_textSequences[initiatorTaskTag] = sequence;
+                return sequence;
+
+            }
+            else
+            {
+                m_textSequences.Add(initiatorTaskTag, text);
+                return text;
+            }
+        }
+
+        public void RemoveTextSequence(uint initiatorTaskTag)
+        {
+            m_textSequences.Remove(initiatorTaskTag);
+        }
+
         public TransferEntry AddTransfer(uint transferTag, SCSICommandPDU command, uint nextR2TSN)
         {
             TransferEntry entry = new TransferEntry(command, nextR2TSN);

+ 52 - 26
ISCSI/ISCSI.Server/ISCSIServer.Login.cs

@@ -16,18 +16,27 @@ namespace ISCSI.Server
     {
         private LoginResponsePDU GetLoginResponsePDU(LoginRequestPDU request, ISCSISession session, ConnectionParameters connection)
         {
+            if (request.Continue)
+            {
+                connection.AddTextToSequence(request.InitiatorTaskTag, request.LoginParametersText);
+                return GetPartialLoginResponsePDU(request, session, connection);
+            }
+            else
+            {
+                string text = connection.AddTextToSequence(request.InitiatorTaskTag, request.LoginParametersText);
+                connection.RemoveTextSequence(request.InitiatorTaskTag);
+                KeyValuePairList<string, string> loginParameters = KeyValuePairUtils.GetKeyValuePairList(text);
+                return GetFinalLoginResponsePDU(request, loginParameters, session, connection);
+            }
+        }
+
+        private LoginResponsePDU GetPartialLoginResponsePDU(LoginRequestPDU request, ISCSISession session, ConnectionParameters connection)
+        {
             LoginResponsePDU response = new LoginResponsePDU();
-            response.Transit = request.Transit;
+            response.Transit = false;
             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;
@@ -35,20 +44,34 @@ namespace ISCSI.Server
             // be set to the TSIH provided by the initiator in the Login Request.
             response.TSIH = request.TSIH;
             response.InitiatorTaskTag = request.InitiatorTaskTag;
-
-            string connectionIdentifier = ConnectionState.GetConnectionIdentifier(session, connection);
-
-            if (request.Transit && request.Continue)
+            if (request.Transit)
             {
+                string connectionIdentifier = ConnectionState.GetConnectionIdentifier(session, connection);
                 Log(Severity.Warning, "[{0}] Initiator error: Received login request with both Transit and Continue set to true", connectionIdentifier);
                 response.Status = LoginResponseStatusName.InitiatorError;
                 return response;
             }
-            else if (request.Continue)
-            {
-                response.Status = LoginResponseStatusName.Success;
-                return response;
-            }
+            response.Status = LoginResponseStatusName.Success;
+            return response;
+        }
+
+        private LoginResponsePDU GetFinalLoginResponsePDU(LoginRequestPDU request, KeyValuePairList<string, string> requestParameters, 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.InitiatorTaskTag = request.InitiatorTaskTag;
 
             if (request.TSIH == 0)
             {
@@ -59,6 +82,7 @@ namespace ISCSI.Server
             }
             response.TSIH = session.TSIH;
 
+            string connectionIdentifier = ConnectionState.GetConnectionIdentifier(session, connection);
             response.Status = LoginResponseStatusName.Success;
 
             // RFC 3720:  The login process proceeds in two stages - the security negotiation
@@ -67,7 +91,7 @@ namespace ISCSI.Server
             bool firstLoginRequest = (!session.IsDiscovery && session.Target == null);
             if (firstLoginRequest)
             {
-                connection.InitiatorName = request.LoginParameters.ValueOf("InitiatorName");
+                connection.InitiatorName = requestParameters.ValueOf("InitiatorName");
                 if (String.IsNullOrEmpty(connection.InitiatorName))
                 {
                     // RFC 3720: InitiatorName: The initiator of the TCP connection MUST provide this key [..]
@@ -76,7 +100,7 @@ namespace ISCSI.Server
                     response.Status = LoginResponseStatusName.InitiatorError;
                     return response;
                 }
-                string sessionType = request.LoginParameters.ValueOf("SessionType");
+                string sessionType = requestParameters.ValueOf("SessionType");
                 if (sessionType == "Discovery")
                 {
                     session.IsDiscovery = true;
@@ -84,9 +108,9 @@ namespace ISCSI.Server
                 else //sessionType == "Normal" or unspecified (default is Normal)
                 {
                     session.IsDiscovery = false;
-                    if (request.LoginParameters.ContainsKey("TargetName"))
+                    if (requestParameters.ContainsKey("TargetName"))
                     {
-                        string targetName = request.LoginParameters.ValueOf("TargetName");
+                        string targetName = requestParameters.ValueOf("TargetName");
                         ISCSITarget target = m_targets.FindTarget(targetName);
                         if (target != null)
                         {
@@ -117,11 +141,12 @@ namespace ISCSI.Server
 
             if (request.CurrentStage == 0)
             {
-                response.LoginParameters.Add("AuthMethod", "None");
+                KeyValuePairList<string, string> responseParameters = new KeyValuePairList<string, string>();
+                responseParameters.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");
+                    responseParameters.Add("TargetPortalGroupTag", "1");
                 }
 
                 if (request.Transit)
@@ -136,11 +161,12 @@ namespace ISCSI.Server
                         response.Status = LoginResponseStatusName.InitiatorError;
                     }
                 }
+                response.LoginParameters = responseParameters;
             }
             else if (request.CurrentStage == 1)
             {
-                UpdateOperationalParameters(request.LoginParameters, session, connection);
-                response.LoginParameters = GetLoginOperationalParameters(session, connection);
+                UpdateOperationalParameters(requestParameters, session, connection);
+                response.LoginParameters = GetLoginResponseOperationalParameters(session, connection);
 
                 if (request.Transit)
                 {
@@ -234,7 +260,7 @@ namespace ISCSI.Server
             }
         }
 
-        private static KeyValuePairList<string, string> GetLoginOperationalParameters(ISCSISession session, ConnectionParameters connectionParameters)
+        private static KeyValuePairList<string, string> GetLoginResponseOperationalParameters(ISCSISession session, ConnectionParameters connectionParameters)
         {
             KeyValuePairList<string, string> loginParameters = new KeyValuePairList<string, string>();
             loginParameters.Add("HeaderDigest", "None");

+ 12 - 2
ISCSI/ISCSI.Server/ISCSIServer.PDUProcessor.cs

@@ -56,7 +56,7 @@ namespace ISCSI.Server
                 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));
+                    Log(Severity.Verbose, "[{0}] Login Request, current stage: {1}, next stage: {2}, parameters: {3}", state.ConnectionIdentifier, request.CurrentStage, request.NextStage, FormatNullDelimitedText(request.LoginParametersText));
                     if (request.TSIH != 0)
                     {
                         // RFC 3720: A Login Request with a non-zero TSIH and a CID equal to that of an existing
@@ -80,7 +80,7 @@ namespace ISCSI.Server
                         state.ConnectionParameters.CID = request.CID;
                         m_connectionManager.AddConnection(state);
                     }
-                    Log(Severity.Verbose, "[{0}] Login Response parameters: {1}", state.ConnectionIdentifier, KeyValuePairUtils.ToString(response.LoginParameters));
+                    Log(Severity.Verbose, "[{0}] Login Response parameters: {1}", state.ConnectionIdentifier, FormatNullDelimitedText(response.LoginParametersText));
                     state.SendQueue.Enqueue(response);
                 }
                 else
@@ -249,5 +249,15 @@ namespace ISCSI.Server
             }
             Log(Severity.Trace, "Leaving ProcessPDU");
         }
+
+        private static string FormatNullDelimitedText(string text)
+        {
+            string result = String.Join(", ", text.Split('\0'));
+            if (result.EndsWith(", "))
+            {
+                result = result.Remove(result.Length - 2);
+            }
+            return result;
+        }
     }
 }