ServerResponseHelper.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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 class ServerResponseHelper
  15. {
  16. // CmdSN is session wide
  17. // StatSN is per connection
  18. internal static NOPInPDU GetNOPResponsePDU(NOPOutPDU request)
  19. {
  20. NOPInPDU response = new NOPInPDU();
  21. response.Data = request.Data;
  22. // When a target receives the NOP-Out with a valid Initiator Task Tag (not the reserved value 0xffffffff),
  23. // it MUST respond with a NOP-In with the same Initiator Task Tag that was provided in the NOP-Out request.
  24. // For such a response, the Target Transfer Tag MUST be 0xffffffff
  25. response.InitiatorTaskTag = request.InitiatorTaskTag;
  26. response.TargetTransferTag = 0xFFFFFFFF;
  27. return response;
  28. }
  29. internal static LoginResponsePDU GetLoginResponsePDU(LoginRequestPDU request, List<ISCSITarget> availableTargets, SessionParameters session, ConnectionParameters connection, ref ISCSITarget target, GetNextTSIH GetNextTSIH)
  30. {
  31. LoginResponsePDU response = new LoginResponsePDU();
  32. response.Transit = request.Transit;
  33. response.Continue = false;
  34. // The stage codes are:
  35. // 0 - SecurityNegotiation
  36. // 1 - LoginOperationalNegotiation
  37. // 3 - FullFeaturePhase
  38. response.CurrentStage = request.CurrentStage;
  39. response.NextStage = request.NextStage;
  40. response.VersionMax = request.VersionMax;
  41. response.VersionActive = request.VersionMin;
  42. response.ISID = request.ISID;
  43. response.Status = LoginResponseStatusName.Success;
  44. response.InitiatorTaskTag = request.InitiatorTaskTag;
  45. if (request.TSIH == 0)
  46. {
  47. // For a new session, the request TSIH is zero,
  48. // As part of the response, the target generates a TSIH.
  49. session.TSIH = GetNextTSIH();
  50. }
  51. response.TSIH = session.TSIH;
  52. if (request.Transit && request.Continue)
  53. {
  54. response.Status = LoginResponseStatusName.InitiatorError;
  55. return response;
  56. }
  57. else if (request.Continue)
  58. {
  59. response.Status = LoginResponseStatusName.Success;
  60. return response;
  61. }
  62. // RFC 3720: The login process proceeds in two stages - the security negotiation
  63. // stage and the operational parameter negotiation stage. Both stages are optional
  64. // but at least one of them has to be present.
  65. bool firstLoginRequest = (!session.IsDiscovery && target == null);
  66. if (firstLoginRequest)
  67. {
  68. string sessionType = request.LoginParameters.ValueOf("SessionType");
  69. if (sessionType == "Discovery")
  70. {
  71. session.IsDiscovery = true;
  72. }
  73. else //sessionType == "Normal" or unspecified (default is Normal)
  74. {
  75. session.IsDiscovery = false;
  76. if (request.LoginParameters.ContainsKey("TargetName"))
  77. {
  78. string targetName = request.LoginParameters.ValueOf("TargetName");
  79. int targetIndex = GetTargetIndex(availableTargets, targetName);
  80. if (targetIndex >= 0)
  81. {
  82. target = availableTargets[targetIndex];
  83. }
  84. else
  85. {
  86. response.Status = LoginResponseStatusName.NotFound;
  87. return response;
  88. }
  89. }
  90. else
  91. {
  92. // 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.
  93. response.Status = LoginResponseStatusName.InitiatorError;
  94. return response;
  95. }
  96. }
  97. }
  98. if (request.CurrentStage == 0)
  99. {
  100. response.LoginParameters.Add("AuthMethod", "None");
  101. if (target != null)
  102. {
  103. // 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
  104. response.LoginParameters.Add("TargetPortalGroupTag", "1");
  105. }
  106. if (request.Transit)
  107. {
  108. if (request.NextStage == 3)
  109. {
  110. session.IsFullFeaturePhase = true;
  111. }
  112. else if (request.NextStage != 1)
  113. {
  114. response.Status = LoginResponseStatusName.InitiatorError;
  115. }
  116. }
  117. }
  118. else if (request.CurrentStage == 1)
  119. {
  120. UpdateOperationalParameters(request.LoginParameters, session, connection);
  121. response.LoginParameters = GetLoginOperationalParameters(session, connection);
  122. if (request.Transit)
  123. {
  124. if (request.NextStage == 3)
  125. {
  126. session.IsFullFeaturePhase = true;
  127. }
  128. else
  129. {
  130. response.Status = LoginResponseStatusName.InitiatorError;
  131. }
  132. }
  133. }
  134. else
  135. {
  136. // Not valid
  137. response.Status = LoginResponseStatusName.InitiatorError;
  138. }
  139. return response;
  140. }
  141. private static int GetTargetIndex(List<ISCSITarget> targets, string targetName)
  142. {
  143. for (int index = 0; index < targets.Count; index++)
  144. {
  145. if (String.Equals(targets[index].TargetName, targetName, StringComparison.InvariantCultureIgnoreCase))
  146. {
  147. return index;
  148. }
  149. }
  150. return -1;
  151. }
  152. public static void UpdateOperationalParameters(KeyValuePairList<string, string> loginParameters, SessionParameters sessionParameters, ConnectionParameters connectionParameters)
  153. {
  154. sessionParameters.InitialR2T = ISCSIServer.OfferedInitialR2T;
  155. sessionParameters.ImmediateData = ISCSIServer.OfferedImmediateData;
  156. sessionParameters.MaxBurstLength = ISCSIServer.OfferedMaxBurstLength;
  157. sessionParameters.FirstBurstLength = ISCSIServer.OfferedFirstBurstLength;
  158. sessionParameters.MaxConnections = ISCSIServer.OfferedMaxConnections;
  159. sessionParameters.DataPDUInOrder = ISCSIServer.OfferedDataPDUInOrder;
  160. sessionParameters.DataSequenceInOrder = ISCSIServer.OfferedDataSequenceInOrder;
  161. sessionParameters.DefaultTime2Wait = ISCSIServer.OfferedDefaultTime2Wait;
  162. sessionParameters.DefaultTime2Retain = ISCSIServer.OfferedDefaultTime2Retain;
  163. sessionParameters.MaxOutstandingR2T = ISCSIServer.OfferedMaxOutstandingR2T;
  164. string value = loginParameters.ValueOf("MaxRecvDataSegmentLength");
  165. if (value != null)
  166. {
  167. connectionParameters.InitiatorMaxRecvDataSegmentLength = Convert.ToInt32(value);
  168. }
  169. value = loginParameters.ValueOf("InitialR2T");
  170. if (value != null)
  171. {
  172. sessionParameters.InitialR2T = (value == "Yes") || ISCSIServer.OfferedInitialR2T;
  173. }
  174. value = loginParameters.ValueOf("ImmediateData");
  175. if (value != null)
  176. {
  177. sessionParameters.ImmediateData = (value == "Yes") && ISCSIServer.OfferedImmediateData;
  178. }
  179. value = loginParameters.ValueOf("MaxBurstLength");
  180. if (value != null)
  181. {
  182. sessionParameters.MaxBurstLength = Math.Min(Convert.ToInt32(value), ISCSIServer.OfferedMaxBurstLength);
  183. }
  184. value = loginParameters.ValueOf("FirstBurstLength");
  185. if (value != null)
  186. {
  187. sessionParameters.FirstBurstLength = Math.Min(Convert.ToInt32(value), ISCSIServer.OfferedFirstBurstLength);
  188. }
  189. value = loginParameters.ValueOf("MaxConnections");
  190. if (value != null)
  191. {
  192. sessionParameters.MaxConnections = Math.Min(Convert.ToInt32(value), ISCSIServer.OfferedMaxConnections);
  193. }
  194. value = loginParameters.ValueOf("DataPDUInOrder");
  195. if (value != null)
  196. {
  197. sessionParameters.DataPDUInOrder = (value == "Yes") || ISCSIServer.OfferedDataPDUInOrder;
  198. }
  199. value = loginParameters.ValueOf("DataSequenceInOrder");
  200. if (value != null)
  201. {
  202. sessionParameters.DataSequenceInOrder = (value == "Yes") || ISCSIServer.OfferedDataSequenceInOrder;
  203. }
  204. value = loginParameters.ValueOf("DefaultTime2Wait");
  205. if (value != null)
  206. {
  207. sessionParameters.DefaultTime2Wait = Math.Max(Convert.ToInt32(value), ISCSIServer.OfferedDefaultTime2Wait);
  208. }
  209. value = loginParameters.ValueOf("DefaultTime2Retain");
  210. if (value != null)
  211. {
  212. sessionParameters.DefaultTime2Retain = Math.Min(Convert.ToInt32(value), ISCSIServer.OfferedDefaultTime2Retain);
  213. }
  214. value = loginParameters.ValueOf("MaxOutstandingR2T");
  215. if (value != null)
  216. {
  217. sessionParameters.MaxOutstandingR2T = Math.Min(Convert.ToInt32(value), ISCSIServer.OfferedMaxOutstandingR2T);
  218. }
  219. }
  220. public static KeyValuePairList<string, string> GetLoginOperationalParameters(SessionParameters sessionParameters, ConnectionParameters connectionParameters)
  221. {
  222. KeyValuePairList<string, string> loginParameters = new KeyValuePairList<string, string>();
  223. loginParameters.Add("HeaderDigest", "None");
  224. loginParameters.Add("DataDigest", "None");
  225. loginParameters.Add("MaxRecvDataSegmentLength", connectionParameters.TargetMaxRecvDataSegmentLength.ToString());
  226. if (!sessionParameters.IsDiscovery)
  227. {
  228. loginParameters.Add("ErrorRecoveryLevel", ISCSIServer.OfferedErrorRecoveryLevel.ToString());
  229. loginParameters.Add("InitialR2T", sessionParameters.InitialR2T ? "Yes" : "No"); // Microsoft iSCSI Target support InitialR2T = No
  230. loginParameters.Add("ImmediateData", sessionParameters.ImmediateData ? "Yes" : "No");
  231. loginParameters.Add("MaxBurstLength", sessionParameters.MaxBurstLength.ToString());
  232. loginParameters.Add("FirstBurstLength", sessionParameters.FirstBurstLength.ToString());
  233. loginParameters.Add("MaxConnections", sessionParameters.MaxConnections.ToString());
  234. loginParameters.Add("DataPDUInOrder", sessionParameters.DataPDUInOrder ? "Yes" : "No");
  235. loginParameters.Add("DataSequenceInOrder", sessionParameters.DataSequenceInOrder ? "Yes" : "No");
  236. loginParameters.Add("MaxOutstandingR2T", sessionParameters.MaxOutstandingR2T.ToString());
  237. }
  238. loginParameters.Add("DefaultTime2Wait", sessionParameters.DefaultTime2Wait.ToString());
  239. loginParameters.Add("DefaultTime2Retain", sessionParameters.DefaultTime2Retain.ToString());
  240. return loginParameters;
  241. }
  242. internal static TextResponsePDU GetTextResponsePDU(TextRequestPDU request, List<ISCSITarget> targets)
  243. {
  244. TextResponsePDU response = new TextResponsePDU();
  245. response.Final = true;
  246. response.InitiatorTaskTag = request.InitiatorTaskTag;
  247. KeyValuePairList<string, string> entries = new KeyValuePairList<string, string>();
  248. foreach (ISCSITarget target in targets)
  249. {
  250. entries.Add("TargetName", target.TargetName);
  251. }
  252. response.Text = KeyValuePairUtils.ToNullDelimitedString(entries);
  253. return response;
  254. }
  255. internal static LogoutResponsePDU GetLogoutResponsePDU(LogoutRequestPDU request)
  256. {
  257. LogoutResponsePDU response = new LogoutResponsePDU();
  258. response.Response = LogoutResponse.ClosedSuccessfully;
  259. response.Final = true;
  260. response.InitiatorTaskTag = request.InitiatorTaskTag;
  261. return response;
  262. }
  263. }
  264. }