SCSITarget.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using System.Runtime.InteropServices;
  6. using DiskAccessLibrary;
  7. using Utilities;
  8. namespace ISCSI.Server
  9. {
  10. public class SCSITarget
  11. {
  12. private List<Disk> m_disks;
  13. public object WriteLock = new object();
  14. public SCSITarget(List<Disk> disks)
  15. {
  16. m_disks = disks;
  17. }
  18. public SCSIStatusCodeName ExecuteCommand(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
  19. {
  20. if (command.OpCode == SCSIOpCodeName.TestUnitReady)
  21. {
  22. return TestUnitReady(lun, out response);
  23. }
  24. else if (command.OpCode == SCSIOpCodeName.RequestSense)
  25. {
  26. return RequestSense(lun, out response);
  27. }
  28. else if (command.OpCode == SCSIOpCodeName.Inquiry)
  29. {
  30. return Inquiry((InquiryCommand)command, lun, out response);
  31. }
  32. else if (command.OpCode == SCSIOpCodeName.Reserve6)
  33. {
  34. return Reserve6(lun, out response);
  35. }
  36. else if (command.OpCode == SCSIOpCodeName.Release6)
  37. {
  38. return Release6(lun, out response);
  39. }
  40. else if (command.OpCode == SCSIOpCodeName.ModeSense6)
  41. {
  42. return ModeSense6((ModeSense6CommandDescriptorBlock)command, lun, out response);
  43. }
  44. else if (command.OpCode == SCSIOpCodeName.ReadCapacity10)
  45. {
  46. return ReadCapacity10(lun, out response);
  47. }
  48. else if (command.OpCode == SCSIOpCodeName.Read6 ||
  49. command.OpCode == SCSIOpCodeName.Read10 ||
  50. command.OpCode == SCSIOpCodeName.Read16)
  51. {
  52. return Read(command, lun, out response);
  53. }
  54. else if (command.OpCode == SCSIOpCodeName.Write6 ||
  55. command.OpCode == SCSIOpCodeName.Write10 ||
  56. command.OpCode == SCSIOpCodeName.Write16)
  57. {
  58. return Write(command, lun, data, out response);
  59. }
  60. else if (command.OpCode == SCSIOpCodeName.Verify10 ||
  61. command.OpCode == SCSIOpCodeName.Verify16)
  62. {
  63. return Verify(lun, out response);
  64. }
  65. else if (command.OpCode == SCSIOpCodeName.SynchronizeCache10)
  66. {
  67. return SynchronizeCache10(lun, out response);
  68. }
  69. else if (command.OpCode == SCSIOpCodeName.ServiceActionIn &&
  70. command.ServiceAction == ServiceAction.ReadCapacity16)
  71. {
  72. return ReadCapacity16(lun, out response);
  73. }
  74. else if (command.OpCode == SCSIOpCodeName.ReportLUNs)
  75. {
  76. return ReportLUNs(out response);
  77. }
  78. else
  79. {
  80. ISCSIServer.Log("[ExecuteCommand] Unsupported SCSI Command (0x{0})", command.OpCode.ToString("X"));
  81. response = FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
  82. return SCSIStatusCodeName.CheckCondition;
  83. }
  84. }
  85. public SCSIStatusCodeName Inquiry(InquiryCommand command, LUNStructure lun, out byte[] response)
  86. {
  87. if (lun >= m_disks.Count)
  88. {
  89. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  90. return SCSIStatusCodeName.CheckCondition;
  91. }
  92. if (!command.EVPD)
  93. {
  94. StandardInquiryData inquiryData = new StandardInquiryData();
  95. inquiryData.PeripheralDeviceType = 0; // Direct access block device
  96. inquiryData.VendorIdentification = "iSCSIConsole";
  97. inquiryData.ProductIdentification = "Disk_" + lun.ToString();
  98. inquiryData.ProductRevisionLevel = "1.00";
  99. inquiryData.DriveSerialNumber = 0;
  100. inquiryData.CmdQue = true;
  101. inquiryData.Version = 5; // Microsoft iSCSI Target report version 5
  102. response = inquiryData.GetBytes();
  103. }
  104. else
  105. {
  106. switch (command.PageCode)
  107. {
  108. case VitalProductDataPageName.SupportedVPDPages:
  109. {
  110. SupportedVitaLProductDataPages page = new SupportedVitaLProductDataPages();
  111. page.SupportedPageList.Add((byte)VitalProductDataPageName.SupportedVPDPages);
  112. page.SupportedPageList.Add((byte)VitalProductDataPageName.UnitSerialNumber);
  113. page.SupportedPageList.Add((byte)VitalProductDataPageName.DeviceIdentification);
  114. response = page.GetBytes();
  115. break;
  116. }
  117. case VitalProductDataPageName.UnitSerialNumber:
  118. {
  119. UnitSerialNumberVPDPage page = new UnitSerialNumberVPDPage();
  120. // Older products that only support the Product Serial Number parameter will have a page length of 08h, while newer products that support both parameters (Vendor Unique field from the StandardInquiryData) will have a page length of 14h
  121. // Microsoft iSCSI Target uses values such as "34E5A6FC-3ACC-452D-AEDA-6EE2EFF20FB4"
  122. ulong serialNumber = 0;
  123. page.ProductSerialNumber = serialNumber.ToString("00000000");
  124. response = page.GetBytes();
  125. break;
  126. }
  127. case VitalProductDataPageName.DeviceIdentification:
  128. {
  129. DeviceIdentificationVPDPage page = new DeviceIdentificationVPDPage();
  130. // Identifiers necessity is preliminary, and has not been confirmed:
  131. // WWN identifier is needed to prevent 0xF4 BSOD during Windows setup
  132. // ISCSI identifier is needed for WinPE to pick up the disk during boot (after iPXE's sanhook)
  133. page.IdentificationDescriptorList.Add(new IdentificationDescriptor(5, lun));
  134. if (this is ISCSITarget)
  135. {
  136. page.IdentificationDescriptorList.Add(IdentificationDescriptor.GetISCSIIdentifier(((ISCSITarget)this).TargetName));
  137. }
  138. response = page.GetBytes();
  139. break;
  140. }
  141. case VitalProductDataPageName.BlockLimits:
  142. {
  143. /* Provide only when requeste explicitly */
  144. BlockLimitsVPDPage page = new BlockLimitsVPDPage();
  145. page.OptimalTransferLengthGranularity = 128;
  146. page.MaximumTransferLength = (uint)DiskAccessLibrary.Settings.MaximumTransferSizeLBA;
  147. page.OptimalTransferLength = 128;
  148. response = page.GetBytes();
  149. break;
  150. }
  151. case VitalProductDataPageName.BlockDeviceCharacteristics:
  152. {
  153. /* Provide only when requeste explicitly */
  154. BlockDeviceCharacteristicsVPDPage page = new BlockDeviceCharacteristicsVPDPage();
  155. page.MediumRotationRate = 0; // Not reported
  156. response = page.GetBytes();
  157. break;
  158. }
  159. default:
  160. {
  161. response = FormatSenseData(SenseDataParameter.GetIllegalRequestParameterNotSupportedSenseData());
  162. ISCSIServer.Log("[Inquiry] Unsupported VPD Page request (0x{0})", command.PageCode.ToString("X"));
  163. return SCSIStatusCodeName.CheckCondition;
  164. }
  165. }
  166. }
  167. // we must not return more bytes than InquiryCommand.AllocationLength
  168. if (response.Length > command.AllocationLength)
  169. {
  170. response = ByteReader.ReadBytes(response, 0, command.AllocationLength);
  171. }
  172. return SCSIStatusCodeName.Good;
  173. }
  174. public SCSIStatusCodeName ModeSense6(ModeSense6CommandDescriptorBlock command, LUNStructure lun, out byte[] response)
  175. {
  176. if (lun >= m_disks.Count)
  177. {
  178. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  179. return SCSIStatusCodeName.CheckCondition;
  180. }
  181. ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor();
  182. descriptor.LogicalBlockLength = (uint)m_disks[lun].BytesPerSector;
  183. ModeParameterHeader6 header = new ModeParameterHeader6();
  184. header.WP = m_disks[lun].IsReadOnly; // Write protected, even when set to true, Windows does not always prevent the disk from being written to.
  185. header.DPOFUA = true; // Microsoft iSCSI Target support this
  186. header.BlockDescriptorLength = (byte)descriptor.Length;
  187. header.ModeDataLength += (byte)descriptor.Length;
  188. byte[] pageData = new byte[0];
  189. switch ((ModePageCodeName)command.PageCode)
  190. {
  191. case ModePageCodeName.CachingParametersPage:
  192. {
  193. CachingParametersPage page = new CachingParametersPage();
  194. page.RCD = true;
  195. header.ModeDataLength += (byte)page.Length;
  196. pageData = new byte[page.Length];
  197. Array.Copy(page.GetBytes(), pageData, page.Length);
  198. break;
  199. }
  200. case ModePageCodeName.ControlModePage:
  201. {
  202. ControlModePage page = new ControlModePage();
  203. header.ModeDataLength += (byte)page.Length;
  204. pageData = new byte[page.Length];
  205. Array.Copy(page.GetBytes(), pageData, page.Length);
  206. break;
  207. }
  208. case ModePageCodeName.InformationalExceptionsControlModePage:
  209. {
  210. InformationalExceptionsControlModePage page = new InformationalExceptionsControlModePage();
  211. header.ModeDataLength += (byte)page.Length;
  212. pageData = new byte[page.Length];
  213. Array.Copy(page.GetBytes(), pageData, page.Length);
  214. break;
  215. }
  216. case ModePageCodeName.ReturnAllPages:
  217. {
  218. CachingParametersPage page1 = new CachingParametersPage();
  219. page1.RCD = true;
  220. header.ModeDataLength += (byte)page1.Length;
  221. InformationalExceptionsControlModePage page2 = new InformationalExceptionsControlModePage();
  222. header.ModeDataLength += (byte)page2.Length;
  223. pageData = new byte[page1.Length + page2.Length];
  224. Array.Copy(page1.GetBytes(), pageData, page1.Length);
  225. Array.Copy(page2.GetBytes(), 0, pageData, page1.Length, page2.Length);
  226. break;
  227. }
  228. case ModePageCodeName.VendorSpecificPage:
  229. {
  230. // Microsoft iSCSI Target running under Windows 2000 will request this page, we immitate Microsoft iSCSI Target by sending back an empty page
  231. VendorSpecificPage page = new VendorSpecificPage();
  232. header.ModeDataLength += (byte)page.Length;
  233. pageData = new byte[page.Length];
  234. Array.Copy(page.GetBytes(), pageData, page.Length);
  235. break;
  236. }
  237. default:
  238. {
  239. response = FormatSenseData(SenseDataParameter.GetIllegalRequestParameterNotSupportedSenseData());
  240. ISCSIServer.Log("[ModeSense6] ModeSense6 page 0x{0} is not implemented", command.PageCode.ToString("x"));
  241. return SCSIStatusCodeName.CheckCondition;
  242. }
  243. }
  244. response = new byte[1 + header.ModeDataLength];
  245. Array.Copy(header.GetBytes(), 0, response, 0, header.Length);
  246. Array.Copy(descriptor.GetBytes(), 0, response, header.Length, descriptor.Length);
  247. Array.Copy(pageData, 0, response, header.Length + descriptor.Length, pageData.Length);
  248. return SCSIStatusCodeName.Good;
  249. }
  250. public SCSIStatusCodeName ReadCapacity10(LUNStructure lun, out byte[] response)
  251. {
  252. if (lun >= m_disks.Count)
  253. {
  254. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  255. return SCSIStatusCodeName.CheckCondition;
  256. }
  257. ReadCapacity10Parameter parameter = new ReadCapacity10Parameter(m_disks[lun].Size, (uint)m_disks[lun].BytesPerSector);
  258. response = parameter.GetBytes();
  259. return SCSIStatusCodeName.Good;
  260. }
  261. public SCSIStatusCodeName ReadCapacity16(LUNStructure lun, out byte[] response)
  262. {
  263. if (lun >= m_disks.Count)
  264. {
  265. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  266. return SCSIStatusCodeName.CheckCondition;
  267. }
  268. ReadCapacity16Parameter parameter = new ReadCapacity16Parameter(m_disks[lun].Size, (uint)m_disks[lun].BytesPerSector);
  269. response = parameter.GetBytes();
  270. return SCSIStatusCodeName.Good;
  271. }
  272. public SCSIStatusCodeName ReportLUNs(out byte[] response)
  273. {
  274. ReportLUNsParameter parameter = new ReportLUNsParameter(m_disks.Count);
  275. response = parameter.GetBytes();
  276. return SCSIStatusCodeName.Good;
  277. }
  278. public SCSIStatusCodeName Read(SCSICommandDescriptorBlock command, LUNStructure lun, out byte[] response)
  279. {
  280. if (lun >= m_disks.Count)
  281. {
  282. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  283. return SCSIStatusCodeName.CheckCondition;
  284. }
  285. Disk disk = m_disks[lun];
  286. int sectorCount = (int)command.TransferLength;
  287. try
  288. {
  289. response = disk.ReadSectors((long)command.LogicalBlockAddress64, sectorCount);
  290. return SCSIStatusCodeName.Good;
  291. }
  292. catch (ArgumentOutOfRangeException)
  293. {
  294. response = FormatSenseData(SenseDataParameter.GetIllegalRequestLBAOutOfRangeSenseData());
  295. return SCSIStatusCodeName.CheckCondition;
  296. }
  297. catch (IOException ex)
  298. {
  299. int error = Marshal.GetHRForException(ex);
  300. if (error == (int)Win32Error.ERROR_CRC)
  301. {
  302. response = FormatSenseData(SenseDataParameter.GetWriteFaultSenseData());
  303. return SCSIStatusCodeName.CheckCondition;
  304. }
  305. else
  306. {
  307. ISCSIServer.Log("[{0}][Read] Read error:", ex.ToString());
  308. response = FormatSenseData(SenseDataParameter.GetUnitAttentionSenseData());
  309. return SCSIStatusCodeName.CheckCondition;
  310. }
  311. }
  312. }
  313. // Some initiators (i.e. EFI iSCSI DXE) will send 'Request Sense' upon connection (likely just to verify the medium is ready)
  314. public SCSIStatusCodeName RequestSense(LUNStructure lun, out byte[] response)
  315. {
  316. if (lun >= m_disks.Count)
  317. {
  318. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  319. return SCSIStatusCodeName.CheckCondition;
  320. }
  321. response = FormatSenseData(SenseDataParameter.GetNoSenseSenseData());
  322. return SCSIStatusCodeName.Good;
  323. }
  324. public SCSIStatusCodeName Reserve6(LUNStructure lun, out byte[] response)
  325. {
  326. if (lun >= m_disks.Count)
  327. {
  328. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  329. return SCSIStatusCodeName.CheckCondition;
  330. }
  331. response = new byte[0];
  332. return SCSIStatusCodeName.Good;
  333. }
  334. public SCSIStatusCodeName SynchronizeCache10(LUNStructure lun, out byte[] response)
  335. {
  336. if (lun >= m_disks.Count)
  337. {
  338. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  339. return SCSIStatusCodeName.CheckCondition;
  340. }
  341. response = new byte[0];
  342. return SCSIStatusCodeName.Good;
  343. }
  344. public SCSIStatusCodeName Release6(LUNStructure lun, out byte[] response)
  345. {
  346. if (lun >= m_disks.Count)
  347. {
  348. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  349. return SCSIStatusCodeName.CheckCondition;
  350. }
  351. response = new byte[0];
  352. return SCSIStatusCodeName.Good;
  353. }
  354. public SCSIStatusCodeName TestUnitReady(LUNStructure lun, out byte[] response)
  355. {
  356. if (lun >= m_disks.Count)
  357. {
  358. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  359. return SCSIStatusCodeName.CheckCondition;
  360. }
  361. response = new byte[0];
  362. return SCSIStatusCodeName.Good;
  363. }
  364. public SCSIStatusCodeName Verify(LUNStructure lun, out byte[] response)
  365. {
  366. if (lun >= m_disks.Count)
  367. {
  368. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  369. return SCSIStatusCodeName.CheckCondition;
  370. }
  371. response = new byte[0];
  372. return SCSIStatusCodeName.Good;
  373. }
  374. public SCSIStatusCodeName Write(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
  375. {
  376. return Write(lun, (long)command.LogicalBlockAddress64, data, out response);
  377. }
  378. public SCSIStatusCodeName Write(LUNStructure lun, long sectorIndex, byte[] data, out byte[] response)
  379. {
  380. if (lun >= m_disks.Count)
  381. {
  382. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  383. return SCSIStatusCodeName.CheckCondition;
  384. }
  385. Disk disk = m_disks[lun];
  386. if (disk.IsReadOnly)
  387. {
  388. SenseDataParameter senseData = SenseDataParameter.GetDataProtectSenseData();
  389. response = senseData.GetBytes();
  390. return SCSIStatusCodeName.CheckCondition;
  391. }
  392. lock (WriteLock)
  393. {
  394. try
  395. {
  396. disk.WriteSectors(sectorIndex, data);
  397. response = new byte[0];
  398. return SCSIStatusCodeName.Good;
  399. }
  400. catch (ArgumentOutOfRangeException)
  401. {
  402. response = FormatSenseData(SenseDataParameter.GetIllegalRequestLBAOutOfRangeSenseData());
  403. return SCSIStatusCodeName.CheckCondition;
  404. }
  405. catch (IOException ex)
  406. {
  407. ISCSIServer.Log("[{0}][Write] Write error:", ex.ToString());
  408. response = FormatSenseData(SenseDataParameter.GetUnitAttentionSenseData());
  409. return SCSIStatusCodeName.CheckCondition;
  410. }
  411. }
  412. }
  413. public List<Disk> Disks
  414. {
  415. get
  416. {
  417. return m_disks;
  418. }
  419. }
  420. public static byte[] FormatSenseData(SenseDataParameter senseData)
  421. {
  422. byte[] senseDataBytes = senseData.GetBytes();
  423. byte[] result = new byte[senseDataBytes.Length + 2];
  424. Array.Copy(BigEndianConverter.GetBytes((ushort)senseDataBytes.Length), 0, result, 0, 2);
  425. Array.Copy(senseDataBytes, 0, result, 2, senseDataBytes.Length);
  426. return result;
  427. }
  428. }
  429. }