VirtualSCSITarget.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. /* Copyright (C) 2012-2017 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.IO;
  10. using System.Text;
  11. using System.Threading;
  12. using System.Runtime.InteropServices;
  13. using DiskAccessLibrary;
  14. using Utilities;
  15. namespace SCSI
  16. {
  17. public partial class VirtualSCSITarget : SCSITarget
  18. {
  19. private List<Disk> m_disks;
  20. public event EventHandler<LogEntry> OnLogEntry;
  21. public VirtualSCSITarget(List<Disk> disks)
  22. {
  23. m_disks = disks;
  24. }
  25. /// <summary>
  26. /// This implementation is not thread-safe.
  27. /// </summary>
  28. public override SCSIStatusCodeName ExecuteCommand(byte[] commandBytes, LUNStructure lun, byte[] data, out byte[] response)
  29. {
  30. SCSICommandDescriptorBlock command;
  31. try
  32. {
  33. command = SCSICommandDescriptorBlock.FromBytes(commandBytes, 0);
  34. }
  35. catch (UnsupportedSCSICommandException)
  36. {
  37. Log(Severity.Error, "Unsupported SCSI Command (0x{0})", commandBytes[0].ToString("X"));
  38. response = FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
  39. return SCSIStatusCodeName.CheckCondition;
  40. }
  41. return ExecuteCommand(command, lun, data, out response);
  42. }
  43. /// <summary>
  44. /// This implementation is not thread-safe.
  45. /// </summary>
  46. public SCSIStatusCodeName ExecuteCommand(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
  47. {
  48. Log(Severity.Verbose, "Executing command: {0}", command.OpCode);
  49. if (command.OpCode == SCSIOpCodeName.ReportLUNs)
  50. {
  51. uint allocationLength = command.TransferLength;
  52. return ReportLUNs(allocationLength, out response);
  53. }
  54. else if (lun >= m_disks.Count)
  55. {
  56. Log(Severity.Warning, "Initiator error: tried to execute command on LUN {0} which does not exist", lun);
  57. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  58. return SCSIStatusCodeName.CheckCondition;
  59. }
  60. else if (command.OpCode == SCSIOpCodeName.TestUnitReady)
  61. {
  62. return TestUnitReady(lun, out response);
  63. }
  64. else if (command.OpCode == SCSIOpCodeName.RequestSense)
  65. {
  66. uint allocationLength = command.TransferLength;
  67. return RequestSense(lun, allocationLength, out response);
  68. }
  69. else if (command.OpCode == SCSIOpCodeName.Inquiry)
  70. {
  71. return Inquiry((InquiryCommand)command, lun, out response);
  72. }
  73. else if (command.OpCode == SCSIOpCodeName.Reserve6)
  74. {
  75. return Reserve6(lun, out response);
  76. }
  77. else if (command.OpCode == SCSIOpCodeName.Release6)
  78. {
  79. return Release6(lun, out response);
  80. }
  81. else if (command.OpCode == SCSIOpCodeName.ModeSense6)
  82. {
  83. return ModeSense6((ModeSense6CommandDescriptorBlock)command, lun, out response);
  84. }
  85. else if (command.OpCode == SCSIOpCodeName.ReadCapacity10)
  86. {
  87. return ReadCapacity10(lun, out response);
  88. }
  89. else if (command.OpCode == SCSIOpCodeName.Read6 ||
  90. command.OpCode == SCSIOpCodeName.Read10 ||
  91. command.OpCode == SCSIOpCodeName.Read16)
  92. {
  93. return Read(command, lun, out response);
  94. }
  95. else if (command.OpCode == SCSIOpCodeName.Write6 ||
  96. command.OpCode == SCSIOpCodeName.Write10 ||
  97. command.OpCode == SCSIOpCodeName.Write16)
  98. {
  99. return Write(command, lun, data, out response);
  100. }
  101. else if (command.OpCode == SCSIOpCodeName.Verify10 ||
  102. command.OpCode == SCSIOpCodeName.Verify16)
  103. {
  104. return Verify(lun, out response);
  105. }
  106. else if (command.OpCode == SCSIOpCodeName.SynchronizeCache10)
  107. {
  108. return SynchronizeCache10(lun, out response);
  109. }
  110. else if (command.OpCode == SCSIOpCodeName.ServiceActionIn16 &&
  111. command.ServiceAction == ServiceAction.ReadCapacity16)
  112. {
  113. uint allocationLength = command.TransferLength;
  114. return ReadCapacity16(lun, allocationLength, out response);
  115. }
  116. else
  117. {
  118. Log(Severity.Error, "Unsupported SCSI Command (0x{0})", command.OpCode.ToString("X"));
  119. response = FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
  120. return SCSIStatusCodeName.CheckCondition;
  121. }
  122. }
  123. public SCSIStatusCodeName Inquiry(InquiryCommand command, LUNStructure lun, out byte[] response)
  124. {
  125. if (!command.EVPD)
  126. {
  127. if ((int)command.PageCode == 0)
  128. {
  129. StandardInquiryData inquiryData = new StandardInquiryData();
  130. inquiryData.PeripheralDeviceType = 0; // Direct access block device
  131. inquiryData.VendorIdentification = "TalAloni";
  132. inquiryData.ProductIdentification = "iSCSI " + _targetName;
  133. inquiryData.ProductRevisionLevel = "1.00";
  134. inquiryData.DriveSerialNumber = 0;
  135. inquiryData.CmdQue = true;
  136. inquiryData.Version = 5; // SPC-3
  137. NotifyStandardInquiry(this, new StandardInquiryEventArgs(lun, inquiryData));
  138. response = inquiryData.GetBytes();
  139. }
  140. else
  141. {
  142. Log(Severity.Warning, "Initiator error: Invalid Inquiry request");
  143. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
  144. return SCSIStatusCodeName.CheckCondition;
  145. }
  146. }
  147. else
  148. {
  149. Log(Severity.Verbose, "Inquiry: Page code: 0x{0}", command.PageCode.ToString("X"));
  150. switch (command.PageCode)
  151. {
  152. case VitalProductDataPageName.SupportedVPDPages:
  153. {
  154. SupportedVitaLProductDataPages page = new SupportedVitaLProductDataPages();
  155. page.SupportedPageList.Add((byte)VitalProductDataPageName.SupportedVPDPages);
  156. page.SupportedPageList.Add((byte)VitalProductDataPageName.UnitSerialNumber);
  157. page.SupportedPageList.Add((byte)VitalProductDataPageName.DeviceIdentification);
  158. response = page.GetBytes();
  159. break;
  160. }
  161. case VitalProductDataPageName.UnitSerialNumber:
  162. {
  163. UnitSerialNumberVPDPage page = new UnitSerialNumberVPDPage();
  164. // 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
  165. // Microsoft iSCSI Target uses values such as "34E5A6FC-3ACC-452D-AEDA-6EE2EFF20FB4"
  166. ulong serialNumber = 0;
  167. page.ProductSerialNumber = serialNumber.ToString("00000000");
  168. NotifyUnitSerialNumberInquiry(this, new UnitSerialNumberInquiryEventArgs(lun, page));
  169. response = page.GetBytes();
  170. break;
  171. }
  172. case VitalProductDataPageName.DeviceIdentification:
  173. {
  174. DeviceIdentificationVPDPage page = new DeviceIdentificationVPDPage();
  175. // NAA identifier is needed to prevent 0xF4 BSOD during Windows setup
  176. page.IdentificationDescriptorList.Add(IdentificationDescriptor.GetNAAExtendedIdentifier(5, lun));
  177. NotifyDeviceIdentificationInquiry(this, new DeviceIdentificationInquiryEventArgs(lun, page));
  178. response = page.GetBytes();
  179. break;
  180. }
  181. case VitalProductDataPageName.BlockLimits:
  182. {
  183. /* Provide only when requeste explicitly */
  184. BlockLimitsVPDPage page = new BlockLimitsVPDPage();
  185. page.OptimalTransferLengthGranularity = 128;
  186. page.MaximumTransferLength = (uint)DiskAccessLibrary.Settings.MaximumTransferSizeLBA;
  187. page.OptimalTransferLength = 128;
  188. response = page.GetBytes();
  189. break;
  190. }
  191. case VitalProductDataPageName.BlockDeviceCharacteristics:
  192. {
  193. /* Provide only when requeste explicitly */
  194. BlockDeviceCharacteristicsVPDPage page = new BlockDeviceCharacteristicsVPDPage();
  195. page.MediumRotationRate = 0; // Not reported
  196. response = page.GetBytes();
  197. break;
  198. }
  199. default:
  200. {
  201. Log(Severity.Error, "Inquiry: Unsupported VPD Page request (0x{0})", command.PageCode.ToString("X"));
  202. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
  203. return SCSIStatusCodeName.CheckCondition;
  204. }
  205. }
  206. }
  207. // we must not return more bytes than InquiryCommand.AllocationLength
  208. if (response.Length > command.AllocationLength)
  209. {
  210. response = ByteReader.ReadBytes(response, 0, command.AllocationLength);
  211. }
  212. return SCSIStatusCodeName.Good;
  213. }
  214. public SCSIStatusCodeName ModeSense6(ModeSense6CommandDescriptorBlock command, LUNStructure lun, out byte[] response)
  215. {
  216. Log(Severity.Verbose, "ModeSense6: Page code: 0x{0}, Sub page code: 0x{1}", command.PageCode.ToString("X"), command.SubpageCode.ToString("X"));
  217. byte[] pageData;
  218. switch ((ModePageCodeName)command.PageCode)
  219. {
  220. case ModePageCodeName.CachingParametersPage:
  221. {
  222. CachingParametersPage page = new CachingParametersPage();
  223. page.RCD = true;
  224. pageData = page.GetBytes();
  225. break;
  226. }
  227. case ModePageCodeName.ControlModePage:
  228. {
  229. ControlModePage page = new ControlModePage();
  230. pageData = page.GetBytes();
  231. break;
  232. }
  233. case ModePageCodeName.PowerConditionModePage:
  234. {
  235. if (command.SubpageCode == 0x00)
  236. {
  237. PowerConditionModePage page = new PowerConditionModePage();
  238. pageData = page.GetBytes();
  239. break;
  240. }
  241. else if (command.SubpageCode == 0x01)
  242. {
  243. PowerConsumptionModePage page = new PowerConsumptionModePage();
  244. pageData = page.GetBytes();
  245. break;
  246. }
  247. else
  248. {
  249. Log(Severity.Error, "ModeSense6: Power condition subpage 0x{0} is not implemented", command.SubpageCode.ToString("x"));
  250. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
  251. return SCSIStatusCodeName.CheckCondition;
  252. }
  253. }
  254. case ModePageCodeName.InformationalExceptionsControlModePage:
  255. {
  256. InformationalExceptionsControlModePage page = new InformationalExceptionsControlModePage();
  257. pageData = page.GetBytes();
  258. break;
  259. }
  260. case ModePageCodeName.ReturnAllPages:
  261. {
  262. CachingParametersPage page1 = new CachingParametersPage();
  263. page1.RCD = true;
  264. InformationalExceptionsControlModePage page2 = new InformationalExceptionsControlModePage();
  265. pageData = new byte[page1.Length + page2.Length];
  266. Array.Copy(page1.GetBytes(), pageData, page1.Length);
  267. Array.Copy(page2.GetBytes(), 0, pageData, page1.Length, page2.Length);
  268. break;
  269. }
  270. case ModePageCodeName.VendorSpecificPage:
  271. {
  272. // Windows 2000 will request this page, we immitate Microsoft iSCSI Target by sending back an empty page
  273. VendorSpecificPage page = new VendorSpecificPage();
  274. pageData = page.GetBytes();
  275. break;
  276. }
  277. default:
  278. {
  279. Log(Severity.Error, "ModeSense6: ModeSense6 page 0x{0} is not implemented", command.PageCode.ToString("x"));
  280. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
  281. return SCSIStatusCodeName.CheckCondition;
  282. }
  283. }
  284. ModeParameterHeader6 header = new ModeParameterHeader6();
  285. header.WP = m_disks[lun].IsReadOnly; // Write protected, even when set to true, Windows does not always prevent the disk from being written to.
  286. header.DPOFUA = true; // Microsoft iSCSI Target support this
  287. byte[] descriptorBytes = new byte[0];
  288. if (!command.DBD)
  289. {
  290. ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor();
  291. descriptor.LogicalBlockLength = (uint)m_disks[lun].BytesPerSector;
  292. descriptorBytes = descriptor.GetBytes();
  293. }
  294. header.BlockDescriptorLength = (byte)descriptorBytes.Length;
  295. header.ModeDataLength += (byte)(descriptorBytes.Length + pageData.Length);
  296. response = new byte[1 + header.ModeDataLength];
  297. Array.Copy(header.GetBytes(), 0, response, 0, ModeParameterHeader6.Length);
  298. Array.Copy(descriptorBytes, 0, response, ModeParameterHeader6.Length, descriptorBytes.Length);
  299. Array.Copy(pageData, 0, response, ModeParameterHeader6.Length + descriptorBytes.Length, pageData.Length);
  300. // we must not return more bytes than ModeSense6Command.AllocationLength
  301. if (response.Length > command.AllocationLength)
  302. {
  303. response = ByteReader.ReadBytes(response, 0, command.AllocationLength);
  304. }
  305. return SCSIStatusCodeName.Good;
  306. }
  307. public SCSIStatusCodeName ReadCapacity10(LUNStructure lun, out byte[] response)
  308. {
  309. ReadCapacity10Parameter parameter = new ReadCapacity10Parameter(m_disks[lun].Size, (uint)m_disks[lun].BytesPerSector);
  310. response = parameter.GetBytes();
  311. return SCSIStatusCodeName.Good;
  312. }
  313. public SCSIStatusCodeName ReadCapacity16(LUNStructure lun, uint allocationLength, out byte[] response)
  314. {
  315. ReadCapacity16Parameter parameter = new ReadCapacity16Parameter(m_disks[lun].Size, (uint)m_disks[lun].BytesPerSector);
  316. response = parameter.GetBytes();
  317. // we must not return more bytes than ReadCapacity16.AllocationLength
  318. if (response.Length > allocationLength)
  319. {
  320. response = ByteReader.ReadBytes(response, 0, (int)allocationLength);
  321. }
  322. return SCSIStatusCodeName.Good;
  323. }
  324. public SCSIStatusCodeName ReportLUNs(uint allocationLength, out byte[] response)
  325. {
  326. ReportLUNsParameter parameter = new ReportLUNsParameter(m_disks.Count);
  327. response = parameter.GetBytes();
  328. // we must not return more bytes than ReportLUNs.AllocationLength
  329. if (response.Length > allocationLength)
  330. {
  331. response = ByteReader.ReadBytes(response, 0, (int)allocationLength);
  332. }
  333. return SCSIStatusCodeName.Good;
  334. }
  335. public SCSIStatusCodeName Read(SCSICommandDescriptorBlock command, LUNStructure lun, out byte[] response)
  336. {
  337. Disk disk = m_disks[lun];
  338. int sectorCount = (int)command.TransferLength;
  339. Log(Severity.Verbose, "LUN {0}: Reading {1} blocks starting from LBA {2}", (ushort)lun, sectorCount, (long)command.LogicalBlockAddress64);
  340. try
  341. {
  342. response = disk.ReadSectors((long)command.LogicalBlockAddress64, sectorCount);
  343. return SCSIStatusCodeName.Good;
  344. }
  345. catch (ArgumentOutOfRangeException)
  346. {
  347. Log(Severity.Error, "Read error: LBA out of range");
  348. response = FormatSenseData(SenseDataParameter.GetIllegalRequestLBAOutOfRangeSenseData());
  349. return SCSIStatusCodeName.CheckCondition;
  350. }
  351. catch (CyclicRedundancyCheckException)
  352. {
  353. Log(Severity.Error, "Read error: CRC error");
  354. response = FormatSenseData(SenseDataParameter.GetWriteFaultSenseData());
  355. return SCSIStatusCodeName.CheckCondition;
  356. }
  357. catch (IOException ex)
  358. {
  359. Log(Severity.Error, "Read error: {0}", ex.ToString());
  360. response = FormatSenseData(SenseDataParameter.GetMediumErrorUnrecoverableReadErrorSenseData());
  361. return SCSIStatusCodeName.CheckCondition;
  362. }
  363. }
  364. // Some initiators (i.e. EFI iSCSI DXE) will send 'Request Sense' upon connection (likely just to verify the medium is ready)
  365. public SCSIStatusCodeName RequestSense(LUNStructure lun, uint allocationLength, out byte[] response)
  366. {
  367. response = FormatSenseData(SenseDataParameter.GetNoSenseSenseData());
  368. // we must not return more bytes than RequestSense.AllocationLength
  369. if (response.Length > allocationLength)
  370. {
  371. response = ByteReader.ReadBytes(response, 0, (int)allocationLength);
  372. }
  373. return SCSIStatusCodeName.Good;
  374. }
  375. public SCSIStatusCodeName Reserve6(LUNStructure lun, out byte[] response)
  376. {
  377. response = new byte[0];
  378. return SCSIStatusCodeName.Good;
  379. }
  380. public SCSIStatusCodeName SynchronizeCache10(LUNStructure lun, out byte[] response)
  381. {
  382. response = new byte[0];
  383. return SCSIStatusCodeName.Good;
  384. }
  385. public SCSIStatusCodeName Release6(LUNStructure lun, out byte[] response)
  386. {
  387. response = new byte[0];
  388. return SCSIStatusCodeName.Good;
  389. }
  390. public SCSIStatusCodeName TestUnitReady(LUNStructure lun, out byte[] response)
  391. {
  392. response = new byte[0];
  393. return SCSIStatusCodeName.Good;
  394. }
  395. public SCSIStatusCodeName Verify(LUNStructure lun, out byte[] response)
  396. {
  397. response = new byte[0];
  398. return SCSIStatusCodeName.Good;
  399. }
  400. public SCSIStatusCodeName Write(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
  401. {
  402. Disk disk = m_disks[lun];
  403. if (disk.IsReadOnly)
  404. {
  405. Log(Severity.Verbose, "LUN {0}: Refused attempt to write to a read-only disk", lun);
  406. SenseDataParameter senseData = SenseDataParameter.GetDataProtectSenseData();
  407. response = senseData.GetBytes();
  408. return SCSIStatusCodeName.CheckCondition;
  409. }
  410. Log(Severity.Verbose, "LUN {0}: Writing {1} blocks starting from LBA {2}", (ushort)lun, command.TransferLength, (long)command.LogicalBlockAddress64);
  411. try
  412. {
  413. disk.WriteSectors((long)command.LogicalBlockAddress64, data);
  414. response = new byte[0];
  415. return SCSIStatusCodeName.Good;
  416. }
  417. catch (ArgumentOutOfRangeException)
  418. {
  419. Log(Severity.Error, "Write error: LBA out of range");
  420. response = FormatSenseData(SenseDataParameter.GetIllegalRequestLBAOutOfRangeSenseData());
  421. return SCSIStatusCodeName.CheckCondition;
  422. }
  423. catch (IOException ex)
  424. {
  425. Log(Severity.Error, "Write error: {0}", ex.ToString());
  426. response = FormatSenseData(SenseDataParameter.GetMediumErrorWriteFaultSenseData());
  427. return SCSIStatusCodeName.CheckCondition;
  428. }
  429. }
  430. public void Log(Severity severity, string message)
  431. {
  432. // To be thread-safe we must capture the delegate reference first
  433. EventHandler<LogEntry> handler = OnLogEntry;
  434. if (handler != null)
  435. {
  436. handler(this, new LogEntry(DateTime.Now, severity, "Virtual SCSI Target", message));
  437. }
  438. }
  439. public void Log(Severity severity, string message, params object[] args)
  440. {
  441. Log(severity, String.Format(message, args));
  442. }
  443. public List<Disk> Disks
  444. {
  445. get
  446. {
  447. return m_disks;
  448. }
  449. }
  450. public static byte[] FormatSenseData(SenseDataParameter senseData)
  451. {
  452. byte[] senseDataBytes = senseData.GetBytes();
  453. byte[] result = new byte[senseDataBytes.Length + 2];
  454. BigEndianWriter.WriteUInt16(result, 0, (ushort)senseDataBytes.Length);
  455. ByteWriter.WriteBytes(result, 2, senseDataBytes);
  456. return result;
  457. }
  458. }
  459. }