ClientHelper.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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.Text;
  10. using Utilities;
  11. namespace ISCSI.Client
  12. {
  13. public class ClientHelper
  14. {
  15. /// <param name="targetName">Set to null for discovery session</param>
  16. internal static LoginRequestPDU GetFirstStageLoginRequest(string initiatorName, string targetName, SessionParameters session, ConnectionParameters connection)
  17. {
  18. LoginRequestPDU request = new LoginRequestPDU();
  19. request.InitiatorTaskTag = session.GetNextTaskTag();
  20. request.ISID = session.ISID;
  21. request.TSIH = 0; // used on the first connection for a new session
  22. request.CID = connection.CID;
  23. request.CmdSN = session.GetNextCmdSN(false);
  24. request.ExpStatSN = 0;
  25. // The stage codes are:
  26. // 0 - SecurityNegotiation
  27. // 1 - LoginOperationalNegotiation
  28. // 3 - FullFeaturePhase
  29. request.CurrentStage = 0;
  30. request.NextStage = 1;
  31. request.Transit = true;
  32. request.VersionMax = 0;
  33. request.VersionMin = 0;
  34. request.LoginParameters.Add("InitiatorName", initiatorName);
  35. request.LoginParameters.Add("AuthMethod", "None");
  36. if (targetName == null)
  37. {
  38. request.LoginParameters.Add("SessionType", "Discovery");
  39. }
  40. else
  41. {
  42. // 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.
  43. request.LoginParameters.Add("SessionType", "Normal");
  44. request.LoginParameters.Add("TargetName", targetName);
  45. }
  46. return request;
  47. }
  48. internal static LoginRequestPDU GetSecondStageLoginRequest(LoginResponsePDU firstStageResponse, SessionParameters session, ConnectionParameters connection, bool isDiscovery)
  49. {
  50. LoginRequestPDU request = new LoginRequestPDU();
  51. request.ISID = firstStageResponse.ISID;
  52. request.TSIH = firstStageResponse.TSIH;
  53. request.CID = connection.CID;
  54. request.InitiatorTaskTag = session.GetNextTaskTag();
  55. request.CmdSN = session.GetNextCmdSN(false);
  56. request.CurrentStage = firstStageResponse.NextStage;
  57. request.NextStage = 3;
  58. request.Transit = true;
  59. request.VersionMax = 0;
  60. request.VersionMin = 0;
  61. request.LoginParameters.Add("HeaderDigest", "None");
  62. request.LoginParameters.Add("DataDigest", "None");
  63. request.LoginParameters.Add("MaxRecvDataSegmentLength", connection.InitiatorMaxRecvDataSegmentLength.ToString());
  64. if (!isDiscovery)
  65. {
  66. request.LoginParameters.Add("ErrorRecoveryLevel", ISCSIClient.OfferedErrorRecoveryLevel.ToString());
  67. request.LoginParameters.Add("InitialR2T", ISCSIClient.OfferedInitialR2T ? "Yes" : "No");
  68. request.LoginParameters.Add("ImmediateData", ISCSIClient.OfferedImmediateData ? "Yes" : "No");
  69. request.LoginParameters.Add("MaxBurstLength", ISCSIClient.OfferedMaxBurstLength.ToString());
  70. request.LoginParameters.Add("FirstBurstLength", ISCSIClient.OfferedFirstBurstLength.ToString());
  71. request.LoginParameters.Add("MaxConnections", ISCSIClient.OfferedMaxConnections.ToString());
  72. request.LoginParameters.Add("DataPDUInOrder", ISCSIClient.OfferedDataPDUInOrder ? "Yes" : "No");
  73. request.LoginParameters.Add("DataSequenceInOrder", ISCSIClient.OfferedDataSequenceInOrder ? "Yes" : "No");
  74. request.LoginParameters.Add("MaxOutstandingR2T", ISCSIClient.OfferedMaxOutstandingR2T.ToString());
  75. }
  76. request.LoginParameters.Add("DefaultTime2Wait", ISCSIClient.OfferedDefaultTime2Wait.ToString());
  77. request.LoginParameters.Add("DefaultTime2Retain", ISCSIClient.OfferedDefaultTime2Retain.ToString());
  78. return request;
  79. }
  80. internal static LoginRequestPDU GetSingleStageLoginRequest(string initiatorName, string targetName, SessionParameters session, ConnectionParameters connection)
  81. {
  82. LoginRequestPDU request = new LoginRequestPDU();
  83. request.ISID = session.ISID;
  84. request.TSIH = session.TSIH;
  85. request.CID = connection.CID;
  86. request.InitiatorTaskTag = session.GetNextTaskTag();
  87. request.CmdSN = session.GetNextCmdSN(false);
  88. request.CurrentStage = 1;
  89. request.NextStage = 3;
  90. request.Transit = true;
  91. request.VersionMax = 0;
  92. request.VersionMin = 0;
  93. request.LoginParameters.Add("InitiatorName", initiatorName);
  94. if (targetName == null)
  95. {
  96. request.LoginParameters.Add("SessionType", "Discovery");
  97. }
  98. else
  99. {
  100. request.LoginParameters.Add("SessionType", "Normal");
  101. request.LoginParameters.Add("TargetName", targetName);
  102. }
  103. request.LoginParameters.Add("DataDigest", "None");
  104. request.LoginParameters.Add("MaxRecvDataSegmentLength", connection.InitiatorMaxRecvDataSegmentLength.ToString());
  105. if (targetName != null)
  106. {
  107. request.LoginParameters.Add("ErrorRecoveryLevel", ISCSIClient.OfferedErrorRecoveryLevel.ToString());
  108. request.LoginParameters.Add("InitialR2T", ISCSIClient.OfferedInitialR2T ? "Yes" : "No");
  109. request.LoginParameters.Add("ImmediateData", ISCSIClient.OfferedImmediateData ? "Yes" : "No");
  110. request.LoginParameters.Add("MaxBurstLength", ISCSIClient.OfferedMaxBurstLength.ToString());
  111. request.LoginParameters.Add("FirstBurstLength", ISCSIClient.OfferedFirstBurstLength.ToString());
  112. request.LoginParameters.Add("MaxConnections", ISCSIClient.OfferedMaxConnections.ToString());
  113. request.LoginParameters.Add("DataPDUInOrder", ISCSIClient.OfferedDataPDUInOrder ? "Yes" : "No");
  114. request.LoginParameters.Add("DataSequenceInOrder", ISCSIClient.OfferedDataSequenceInOrder ? "Yes" : "No");
  115. request.LoginParameters.Add("MaxOutstandingR2T", ISCSIClient.OfferedMaxOutstandingR2T.ToString());
  116. }
  117. request.LoginParameters.Add("DefaultTime2Wait", ISCSIClient.OfferedDefaultTime2Wait.ToString());
  118. request.LoginParameters.Add("DefaultTime2Retain", ISCSIClient.OfferedDefaultTime2Retain.ToString());
  119. return request;
  120. }
  121. internal static void UpdateOperationalParameters(KeyValuePairList<string, string> loginParameters, SessionParameters session, ConnectionParameters connection)
  122. {
  123. session.InitialR2T = ISCSIClient.OfferedInitialR2T;
  124. session.ImmediateData = ISCSIClient.OfferedImmediateData;
  125. session.MaxBurstLength = ISCSIClient.OfferedMaxBurstLength;
  126. session.FirstBurstLength = ISCSIClient.OfferedFirstBurstLength;
  127. session.DefaultTime2Wait = ISCSIClient.OfferedDefaultTime2Wait;
  128. session.DefaultTime2Retain = ISCSIClient.OfferedDefaultTime2Retain;
  129. session.MaxOutstandingR2T = ISCSIClient.OfferedMaxOutstandingR2T;
  130. session.DataPDUInOrder = ISCSIClient.OfferedDataPDUInOrder;
  131. session.DataSequenceInOrder = ISCSIClient.OfferedDataSequenceInOrder;
  132. session.ErrorRecoveryLevel = ISCSIClient.OfferedErrorRecoveryLevel;
  133. string value = loginParameters.ValueOf("MaxRecvDataSegmentLength");
  134. if (value != null)
  135. {
  136. connection.TargetMaxRecvDataSegmentLength = Convert.ToInt32(value);
  137. }
  138. value = loginParameters.ValueOf("InitialR2T");
  139. if (value != null)
  140. {
  141. session.InitialR2T = (value == "Yes") ? true : false;
  142. }
  143. value = loginParameters.ValueOf("ImmediateData");
  144. if (value != null)
  145. {
  146. session.ImmediateData = (value == "Yes") ? true : false;
  147. }
  148. value = loginParameters.ValueOf("MaxBurstLength");
  149. if (value != null)
  150. {
  151. session.MaxBurstLength = Convert.ToInt32(value);
  152. }
  153. value = loginParameters.ValueOf("FirstBurstLength");
  154. if (value != null)
  155. {
  156. session.FirstBurstLength = Convert.ToInt32(value);
  157. }
  158. value = loginParameters.ValueOf("DefaultTime2Wait");
  159. if (value != null)
  160. {
  161. session.DefaultTime2Wait = Convert.ToInt32(value);
  162. }
  163. value = loginParameters.ValueOf("DefaultTime2Retain");
  164. if (value != null)
  165. {
  166. session.DefaultTime2Retain = Convert.ToInt32(value);
  167. }
  168. value = loginParameters.ValueOf("MaxOutstandingR2T");
  169. if (value != null)
  170. {
  171. session.MaxOutstandingR2T = Convert.ToInt32(value);
  172. }
  173. value = loginParameters.ValueOf("DataPDUInOrder");
  174. if (value != null)
  175. {
  176. session.DataPDUInOrder = (value == "Yes") ? true : false;
  177. }
  178. value = loginParameters.ValueOf("DataSequenceInOrder");
  179. if (value != null)
  180. {
  181. session.DataSequenceInOrder = (value == "Yes") ? true : false;
  182. }
  183. value = loginParameters.ValueOf("ErrorRecoveryLevel");
  184. if (value != null)
  185. {
  186. session.ErrorRecoveryLevel = Convert.ToInt32(value);
  187. }
  188. }
  189. internal static LogoutRequestPDU GetLogoutRequest(SessionParameters session, ConnectionParameters connection)
  190. {
  191. LogoutRequestPDU request = new LogoutRequestPDU();
  192. request.ReasonCode = LogoutReasonCode.CloseTheSession;
  193. request.InitiatorTaskTag = session.GetNextTaskTag();
  194. request.CID = connection.CID;
  195. request.CmdSN = session.GetNextCmdSN(true);
  196. return request;
  197. }
  198. internal static TextRequestPDU GetSendTargetsRequest(SessionParameters session, ConnectionParameters connection)
  199. {
  200. TextRequestPDU request = new TextRequestPDU();
  201. request.Text = "SendTargets=All";
  202. request.InitiatorTaskTag = session.GetNextTaskTag();
  203. request.CmdSN = session.GetNextCmdSN(true);
  204. request.Final = true;
  205. request.TargetTransferTag = 0xFFFFFFFF;
  206. return request;
  207. }
  208. internal static SCSICommandPDU GetReportLUNsCommand(SessionParameters session, ConnectionParameters connection, uint allocationLength)
  209. {
  210. SCSICommandDescriptorBlock reportLUNs = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ReportLUNs);
  211. reportLUNs.TransferLength = allocationLength;
  212. SCSICommandPDU scsiCommand = new SCSICommandPDU();
  213. scsiCommand.CommandDescriptorBlock = reportLUNs.GetBytes();
  214. scsiCommand.InitiatorTaskTag = session.GetNextTaskTag();
  215. scsiCommand.Final = true;
  216. scsiCommand.Read = true;
  217. scsiCommand.CmdSN = session.GetNextCmdSN(true);
  218. scsiCommand.ExpectedDataTransferLength = allocationLength;
  219. return scsiCommand;
  220. }
  221. internal static SCSICommandPDU GetReadCapacity10Command(SessionParameters session, ConnectionParameters connection, ushort LUN)
  222. {
  223. SCSICommandDescriptorBlock readCapacity10 = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ReadCapacity10);
  224. readCapacity10.TransferLength = ReadCapacity10Parameter.Length;
  225. SCSICommandPDU scsiCommand = new SCSICommandPDU();
  226. scsiCommand.CommandDescriptorBlock = readCapacity10.GetBytes();
  227. scsiCommand.InitiatorTaskTag = session.GetNextTaskTag();
  228. scsiCommand.Final = true;
  229. scsiCommand.Read = true;
  230. scsiCommand.LUN = LUN;
  231. scsiCommand.CmdSN = session.GetNextCmdSN(true);
  232. scsiCommand.ExpectedDataTransferLength = ReadCapacity10Parameter.Length;
  233. return scsiCommand;
  234. }
  235. internal static SCSICommandPDU GetReadCapacity16Command(SessionParameters session, ConnectionParameters connection, ushort LUN)
  236. {
  237. SCSICommandDescriptorBlock serviceActionIn = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ServiceActionIn);
  238. serviceActionIn.ServiceAction = ServiceAction.ReadCapacity16;
  239. serviceActionIn.TransferLength = ReadCapacity16Parameter.Length;
  240. SCSICommandPDU scsiCommand = new SCSICommandPDU();
  241. scsiCommand.CommandDescriptorBlock = serviceActionIn.GetBytes();
  242. scsiCommand.InitiatorTaskTag = session.GetNextTaskTag();
  243. scsiCommand.Final = true;
  244. scsiCommand.Read = true;
  245. scsiCommand.LUN = LUN;
  246. scsiCommand.CmdSN = session.GetNextCmdSN(true);
  247. scsiCommand.ExpectedDataTransferLength = ReadCapacity16Parameter.Length;
  248. return scsiCommand;
  249. }
  250. internal static SCSICommandPDU GetRead16Command(SessionParameters session, ConnectionParameters connection, ushort LUN, ulong sectorIndex, uint sectorCount, int bytesPerSector)
  251. {
  252. SCSICommandDescriptorBlock read16 = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.Read16);
  253. read16.LogicalBlockAddress64 = sectorIndex;
  254. read16.TransferLength = sectorCount;
  255. SCSICommandPDU scsiCommand = new SCSICommandPDU();
  256. scsiCommand.CommandDescriptorBlock = read16.GetBytes();
  257. scsiCommand.LUN = LUN;
  258. scsiCommand.InitiatorTaskTag = session.GetNextTaskTag();
  259. scsiCommand.Final = true;
  260. scsiCommand.Read = true;
  261. scsiCommand.CmdSN = session.GetNextCmdSN(true);
  262. scsiCommand.ExpectedDataTransferLength = (uint)(sectorCount * bytesPerSector);
  263. return scsiCommand;
  264. }
  265. internal static SCSICommandPDU GetWrite16Command(SessionParameters session, ConnectionParameters connection, ushort LUN, ulong sectorIndex, byte[] data, int bytesPerSector)
  266. {
  267. SCSICommandDescriptorBlock write16 = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.Write16);
  268. write16.LogicalBlockAddress64 = sectorIndex;
  269. write16.TransferLength = (uint)(data.Length / bytesPerSector);
  270. SCSICommandPDU scsiCommand = new SCSICommandPDU();
  271. scsiCommand.CommandDescriptorBlock = write16.GetBytes();
  272. if (session.ImmediateData)
  273. {
  274. int immediateDataLength = Math.Min(data.Length, session.FirstBurstLength);
  275. scsiCommand.Data = ByteReader.ReadBytes(data, 0, immediateDataLength);
  276. }
  277. scsiCommand.LUN = LUN;
  278. scsiCommand.InitiatorTaskTag = session.GetNextTaskTag();
  279. scsiCommand.Final = true;
  280. scsiCommand.Write = true;
  281. scsiCommand.CmdSN = session.GetNextCmdSN(true);
  282. scsiCommand.ExpectedDataTransferLength = (uint)(data.Length);
  283. return scsiCommand;
  284. }
  285. internal static List<SCSIDataOutPDU> GetWriteData(SessionParameters session, ConnectionParameters connection, ushort LUN, ulong sectorIndex, byte[] data, int bytesPerSector, ReadyToTransferPDU readyToTransfer)
  286. {
  287. List<SCSIDataOutPDU> result = new List<SCSIDataOutPDU>();
  288. // if readyToTransfer.DesiredDataTransferLength <= connection.TargetMaxRecvDataSegmentLength we must send multiple Data-Out PDUs
  289. // We assume DesiredDataTransferLength does not violate session.MaxBurstLength
  290. int numberOfChunks = (int)Math.Ceiling((double)readyToTransfer.DesiredDataTransferLength / connection.TargetMaxRecvDataSegmentLength);
  291. for (int chunkIndex = 0; chunkIndex < numberOfChunks; chunkIndex++)
  292. {
  293. int chunkOffset = chunkIndex * connection.TargetMaxRecvDataSegmentLength;
  294. int chunkLength = (int)Math.Min(connection.TargetMaxRecvDataSegmentLength, readyToTransfer.DesiredDataTransferLength - chunkOffset);
  295. SCSIDataOutPDU dataOut = new SCSIDataOutPDU();
  296. dataOut.BufferOffset = readyToTransfer.BufferOffset + (uint)chunkOffset;
  297. dataOut.Data = ByteReader.ReadBytes(data, (int)dataOut.BufferOffset, chunkLength);
  298. dataOut.TargetTransferTag = readyToTransfer.TargetTransferTag;
  299. dataOut.InitiatorTaskTag = readyToTransfer.InitiatorTaskTag;
  300. if (chunkIndex == numberOfChunks - 1)
  301. {
  302. dataOut.Final = true;
  303. }
  304. result.Add(dataOut);
  305. }
  306. return result;
  307. }
  308. internal static NOPOutPDU GetPingRequest(SessionParameters session, ConnectionParameters connection)
  309. {
  310. // Microsoft iSCSI Target v3.1 expects that CmdSN won't be incremented after this request regardless of whether the ImmediateDelivery bit is set or not,
  311. // So we set the ImmediateDelivery bit to work around the issue.
  312. NOPOutPDU request = new NOPOutPDU();
  313. request.ImmediateDelivery = true;
  314. request.InitiatorTaskTag = session.GetNextTaskTag();
  315. request.CmdSN = session.GetNextCmdSN(false);
  316. // RFC 3720: The NOP-Out MUST only have the Target Transfer Tag set if it is issued in response to a NOP-In (with a valid Target Transfer Tag).
  317. // Otherwise, the Target Transfer Tag MUST be set to 0xffffffff.
  318. request.TargetTransferTag = 0xFFFFFFFF;
  319. return request;
  320. }
  321. internal static NOPOutPDU GetPingResponse(NOPInPDU request, SessionParameters session, ConnectionParameters connection)
  322. {
  323. NOPOutPDU response = new NOPOutPDU();
  324. // If the Initiator Task Tag contains 0xffffffff, the I bit MUST be set to 1 and the CmdSN is not advanced after this PDU is sent.
  325. response.ImmediateDelivery = true;
  326. // RFC 3720: The NOP-Out MUST have the Initiator Task Tag set to a valid value only if a response in the form of NOP-In is requested.
  327. // Otherwise, the Initiator Task Tag MUST be set to 0xffffffff
  328. response.InitiatorTaskTag = 0xFFFFFFFF;
  329. response.CmdSN = session.GetNextCmdSN(false);
  330. response.TargetTransferTag = request.TargetTransferTag;
  331. // p.s. the Data Segment (of the request sent by the target) MUST NOT contain any data
  332. return response;
  333. }
  334. public static ulong GetRandomISID()
  335. {
  336. byte a = 0x80; // Random
  337. ushort b = (ushort)(new Random().Next(UInt16.MaxValue + 1));
  338. byte c = (byte)(new Random().Next(Byte.MaxValue + 1));
  339. ushort d = 0;
  340. ulong isid = (ulong)a << 40 | (ulong)b << 24 | (ulong)c << 16 | (ulong)d;
  341. return isid;
  342. }
  343. }
  344. }