ISCSIServer.Login.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. /* Copyright (C) 2012-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Net;
  10. using System.Text;
  11. using Utilities;
  12. namespace ISCSI.Server
  13. {
  14. public partial class ISCSIServer
  15. {
  16. private LoginResponsePDU GetLoginResponsePDU(LoginRequestPDU request, ConnectionParameters connection)
  17. {
  18. // RFC 3720: The numbering fields (StatSN, ExpCmdSN, MaxCmdSN) are only valid if status-Class is 0.
  19. // RFC 3720: Command numbering starts with the first login request on the first connection of a session
  20. if (request.Continue)
  21. {
  22. connection.AddTextToSequence(request.InitiatorTaskTag, request.LoginParametersText);
  23. return GetPartialLoginResponsePDU(request, connection);
  24. }
  25. else
  26. {
  27. string text = connection.AddTextToSequence(request.InitiatorTaskTag, request.LoginParametersText);
  28. connection.RemoveTextSequence(request.InitiatorTaskTag);
  29. KeyValuePairList<string, string> loginParameters = KeyValuePairUtils.GetKeyValuePairList(text);
  30. if (connection.Session == null)
  31. {
  32. LoginResponseStatusName status = SetUpSession(request, loginParameters, connection);
  33. if (status != LoginResponseStatusName.Success)
  34. {
  35. LoginResponsePDU response = GetLoginResponseTemplate(request);
  36. response.Transit = request.Transit;
  37. response.Status = status;
  38. return response;
  39. }
  40. }
  41. return GetFinalLoginResponsePDU(request, loginParameters, connection);
  42. }
  43. }
  44. private LoginResponsePDU GetPartialLoginResponsePDU(LoginRequestPDU request, ConnectionParameters connection)
  45. {
  46. LoginResponsePDU response = GetLoginResponseTemplate(request);
  47. response.Transit = false;
  48. response.InitiatorTaskTag = request.InitiatorTaskTag;
  49. response.ExpCmdSN = request.CmdSN; // We must set ExpCmdSN ourselves because we haven't set up the session yet.
  50. if (request.Transit)
  51. {
  52. Log(Severity.Warning, "[{0}] Initiator error: Received login request with both Transit and Continue set to true", connection.ConnectionIdentifier);
  53. response.Status = LoginResponseStatusName.InitiatorError;
  54. return response;
  55. }
  56. response.Status = LoginResponseStatusName.Success;
  57. return response;
  58. }
  59. private LoginResponseStatusName SetUpSession(LoginRequestPDU request, KeyValuePairList<string, string> requestParameters, ConnectionParameters connection)
  60. {
  61. string initiatorName = requestParameters.ValueOf("InitiatorName");
  62. if (String.IsNullOrEmpty(initiatorName))
  63. {
  64. // RFC 3720: InitiatorName: The initiator of the TCP connection MUST provide this key [..]
  65. // at the first Login of the Login Phase for every connection.
  66. string loginIdentifier = String.Format("ISID={0},TSIH={1},CID={2}", request.ISID.ToString("x"), request.TSIH.ToString("x"), request.CID.ToString("x"));
  67. Log(Severity.Warning, "[{0}] Initiator error: InitiatorName was not included in the login request", loginIdentifier);
  68. return LoginResponseStatusName.InitiatorError;
  69. }
  70. if (request.TSIH == 0)
  71. {
  72. // Note: An initiator could login with the same ISID to a different target (another session),
  73. // We should only perform session reinstatement when an initiator is logging in to the same target.
  74. // For a new session, the request TSIH is zero,
  75. // As part of the response, the target generates a TSIH.
  76. connection.Session = m_sessionManager.StartSession(initiatorName, request.ISID);
  77. connection.CID = request.CID;
  78. Log(Severity.Verbose, "[{0}] Session has been started", connection.Session.SessionIdentifier);
  79. connection.Session.CommandNumberingStarted = true;
  80. connection.Session.ExpCmdSN = request.CmdSN;
  81. string sessionType = requestParameters.ValueOf("SessionType");
  82. if (sessionType == "Discovery")
  83. {
  84. connection.Session.IsDiscovery = true;
  85. }
  86. else //sessionType == "Normal" or unspecified (default is Normal)
  87. {
  88. if (requestParameters.ContainsKey("TargetName"))
  89. {
  90. string targetName = requestParameters.ValueOf("TargetName");
  91. return SetUpNormalSession(request, targetName, connection);
  92. }
  93. else
  94. {
  95. // 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.
  96. Log(Severity.Warning, "[{0}] Initiator error: TargetName was not included in a non-discovery session", connection.ConnectionIdentifier);
  97. return LoginResponseStatusName.InitiatorError;
  98. }
  99. }
  100. }
  101. else
  102. {
  103. ISCSISession existingSession = m_sessionManager.FindSession(initiatorName, request.ISID, request.TSIH);
  104. if (existingSession == null)
  105. {
  106. return LoginResponseStatusName.SessionDoesNotExist;
  107. }
  108. else
  109. {
  110. connection.Session = existingSession;
  111. ConnectionState existingConnection = m_connectionManager.FindConnection(existingSession, request.CID);
  112. if (existingConnection != null)
  113. {
  114. // do connection reinstatement
  115. Log(Severity.Verbose, "[{0}] Initiating implicit logout", existingConnection.ConnectionIdentifier);
  116. m_connectionManager.ReleaseConnection(existingConnection);
  117. }
  118. else
  119. {
  120. // add a new connection to the session
  121. if (m_connectionManager.GetSessionConnections(existingSession).Count > existingSession.MaxConnections)
  122. {
  123. return LoginResponseStatusName.TooManyConnections;
  124. }
  125. }
  126. connection.CID = request.CID;
  127. }
  128. }
  129. return LoginResponseStatusName.Success;
  130. }
  131. private LoginResponseStatusName SetUpNormalSession(LoginRequestPDU request, string targetName, ConnectionParameters connection)
  132. {
  133. ISCSISession session = connection.Session;
  134. session.IsDiscovery = false;
  135. // If there's an existing session between this initiator and the target, we should terminate the
  136. // old session before reinstating a new iSCSI session in its place.
  137. ISCSISession existingSession = m_sessionManager.FindSession(session.InitiatorName, request.ISID, targetName);
  138. if (existingSession != null)
  139. {
  140. Log(Severity.Verbose, "[{0}] Terminating old session with target: {1}", connection.ConnectionIdentifier, targetName);
  141. List<ConnectionState> existingConnections = m_connectionManager.GetSessionConnections(existingSession);
  142. foreach (ConnectionState existingConnection in existingConnections)
  143. {
  144. m_connectionManager.ReleaseConnection(existingConnection);
  145. }
  146. m_sessionManager.RemoveSession(existingSession, SessionTerminationReason.ImplicitLogout);
  147. }
  148. // We use m_targets.Lock to synchronize between the login logic and the target removal logic.
  149. lock (m_targets.Lock)
  150. {
  151. ISCSITarget target = m_targets.FindTarget(targetName);
  152. if (target != null)
  153. {
  154. if (!target.AuthorizeInitiator(session.InitiatorName, session.ISID, connection.InitiatorEndPoint))
  155. {
  156. Log(Severity.Warning, "[{0}] Initiator was not authorized to access {1}", connection.ConnectionIdentifier, targetName);
  157. return LoginResponseStatusName.AuthorizationFailure;
  158. }
  159. session.Target = target;
  160. }
  161. else
  162. {
  163. Log(Severity.Warning, "[{0}] Initiator requested an unknown target: {1}", connection.ConnectionIdentifier, targetName);
  164. return LoginResponseStatusName.NotFound;
  165. }
  166. }
  167. return LoginResponseStatusName.Success;
  168. }
  169. private LoginResponsePDU GetFinalLoginResponsePDU(LoginRequestPDU request, KeyValuePairList<string, string> requestParameters, ConnectionParameters connection)
  170. {
  171. LoginResponsePDU response = GetLoginResponseTemplate(request);
  172. response.Transit = request.Transit;
  173. response.TSIH = connection.Session.TSIH;
  174. string connectionIdentifier = connection.ConnectionIdentifier;
  175. response.Status = LoginResponseStatusName.Success;
  176. ISCSISession session = connection.Session;
  177. // RFC 3720: The login process proceeds in two stages - the security negotiation
  178. // stage and the operational parameter negotiation stage. Both stages are optional
  179. // but at least one of them has to be present.
  180. // The stage codes are:
  181. // 0 - SecurityNegotiation
  182. // 1 - LoginOperationalNegotiation
  183. // 3 - FullFeaturePhase
  184. if (request.CurrentStage == 0)
  185. {
  186. KeyValuePairList<string, string> responseParameters = new KeyValuePairList<string, string>();
  187. responseParameters.Add("AuthMethod", "None");
  188. if (session.Target != null)
  189. {
  190. // 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
  191. responseParameters.Add("TargetPortalGroupTag", "1");
  192. }
  193. if (request.Transit)
  194. {
  195. if (request.NextStage == 3)
  196. {
  197. session.IsFullFeaturePhase = true;
  198. }
  199. else if (request.NextStage != 1)
  200. {
  201. Log(Severity.Warning, "[{0}] Initiator error: Received login request with Invalid NextStage", connectionIdentifier);
  202. response.Status = LoginResponseStatusName.InitiatorError;
  203. }
  204. }
  205. response.LoginParameters = responseParameters;
  206. }
  207. else if (request.CurrentStage == 1)
  208. {
  209. UpdateOperationalParameters(requestParameters, connection);
  210. response.LoginParameters = GetLoginResponseOperationalParameters(connection);
  211. if (request.Transit)
  212. {
  213. if (request.NextStage == 3)
  214. {
  215. session.IsFullFeaturePhase = true;
  216. }
  217. else
  218. {
  219. Log(Severity.Warning, "[{0}] Initiator error: Received login request with Invalid NextStage", connectionIdentifier);
  220. response.Status = LoginResponseStatusName.InitiatorError;
  221. }
  222. }
  223. }
  224. else
  225. {
  226. // Not valid
  227. Log(Severity.Warning, "[{0}] Initiator error: Received login request with Invalid CurrentStage", connectionIdentifier);
  228. response.Status = LoginResponseStatusName.InitiatorError;
  229. }
  230. return response;
  231. }
  232. private LoginResponsePDU GetLoginResponseTemplate(LoginRequestPDU request)
  233. {
  234. LoginResponsePDU response = new LoginResponsePDU();
  235. response.Continue = false;
  236. response.CurrentStage = request.CurrentStage;
  237. response.NextStage = request.NextStage;
  238. response.VersionMax = request.VersionMax;
  239. response.VersionActive = request.VersionMin;
  240. response.ISID = request.ISID;
  241. // TSIH: With the exception of the Login Final-Response in a new session, this field should
  242. // be set to the TSIH provided by the initiator in the Login Request.
  243. response.TSIH = request.TSIH;
  244. response.InitiatorTaskTag = request.InitiatorTaskTag;
  245. return response;
  246. }
  247. private static void UpdateOperationalParameters(KeyValuePairList<string, string> loginParameters, ConnectionParameters connection)
  248. {
  249. ISCSISession session = connection.Session;
  250. string value = loginParameters.ValueOf("MaxRecvDataSegmentLength");
  251. if (value != null)
  252. {
  253. connection.InitiatorMaxRecvDataSegmentLength = Convert.ToInt32(value);
  254. }
  255. value = loginParameters.ValueOf("MaxConnections");
  256. if (value != null)
  257. {
  258. session.MaxConnections = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxConnections);
  259. }
  260. value = loginParameters.ValueOf("InitialR2T");
  261. if (value != null)
  262. {
  263. session.InitialR2T = (value == "Yes") || ISCSIServer.DesiredParameters.InitialR2T;
  264. }
  265. value = loginParameters.ValueOf("ImmediateData");
  266. if (value != null)
  267. {
  268. session.ImmediateData = (value == "Yes") && ISCSIServer.DesiredParameters.ImmediateData;
  269. }
  270. value = loginParameters.ValueOf("MaxBurstLength");
  271. if (value != null)
  272. {
  273. session.MaxBurstLength = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxBurstLength);
  274. }
  275. value = loginParameters.ValueOf("FirstBurstLength");
  276. if (value != null)
  277. {
  278. session.FirstBurstLength = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.FirstBurstLength);
  279. }
  280. value = loginParameters.ValueOf("DataPDUInOrder");
  281. if (value != null)
  282. {
  283. session.DataPDUInOrder = (value == "Yes") || ISCSIServer.DesiredParameters.DataPDUInOrder;
  284. }
  285. value = loginParameters.ValueOf("DataSequenceInOrder");
  286. if (value != null)
  287. {
  288. session.DataSequenceInOrder = (value == "Yes") || ISCSIServer.DesiredParameters.DataSequenceInOrder;
  289. }
  290. value = loginParameters.ValueOf("DefaultTime2Wait");
  291. if (value != null)
  292. {
  293. session.DefaultTime2Wait = Math.Max(Convert.ToInt32(value), ISCSIServer.DesiredParameters.DefaultTime2Wait);
  294. }
  295. value = loginParameters.ValueOf("DefaultTime2Retain");
  296. if (value != null)
  297. {
  298. session.DefaultTime2Retain = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.DefaultTime2Retain);
  299. }
  300. value = loginParameters.ValueOf("MaxOutstandingR2T");
  301. if (value != null)
  302. {
  303. session.MaxOutstandingR2T = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxOutstandingR2T);
  304. }
  305. }
  306. private static KeyValuePairList<string, string> GetLoginResponseOperationalParameters(ConnectionParameters connection)
  307. {
  308. ISCSISession session = connection.Session;
  309. KeyValuePairList<string, string> loginParameters = new KeyValuePairList<string, string>();
  310. loginParameters.Add("HeaderDigest", "None");
  311. loginParameters.Add("DataDigest", "None");
  312. loginParameters.Add("MaxRecvDataSegmentLength", connection.TargetMaxRecvDataSegmentLength.ToString());
  313. if (!session.IsDiscovery)
  314. {
  315. loginParameters.Add("MaxConnections", session.MaxConnections.ToString());
  316. loginParameters.Add("InitialR2T", session.InitialR2T ? "Yes" : "No"); // Microsoft iSCSI Target support InitialR2T = No
  317. loginParameters.Add("ImmediateData", session.ImmediateData ? "Yes" : "No");
  318. loginParameters.Add("MaxBurstLength", session.MaxBurstLength.ToString());
  319. loginParameters.Add("FirstBurstLength", session.FirstBurstLength.ToString());
  320. loginParameters.Add("MaxOutstandingR2T", session.MaxOutstandingR2T.ToString());
  321. loginParameters.Add("DataPDUInOrder", session.DataPDUInOrder ? "Yes" : "No");
  322. loginParameters.Add("DataSequenceInOrder", session.DataSequenceInOrder ? "Yes" : "No");
  323. }
  324. loginParameters.Add("DefaultTime2Wait", session.DefaultTime2Wait.ToString());
  325. loginParameters.Add("DefaultTime2Retain", session.DefaultTime2Retain.ToString());
  326. loginParameters.Add("ErrorRecoveryLevel", session.ErrorRecoveryLevel.ToString());
  327. return loginParameters;
  328. }
  329. }
  330. }