ClientHelper.cs 20 KB

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