ClientHelper.cs 21 KB

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