SPTITarget.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. /* Copyright (C) 2017 Tal Aloni <tal.aloni.il@gmail.com>.
  2. * Copyright (C) 2017 Alex Bowden <alex.bowden@outlook.com>.
  3. *
  4. * You can redistribute this program and/or modify it under the terms of
  5. * the GNU Lesser Public License as published by the Free Software Foundation,
  6. * either version 3 of the License, or (at your option) any later version.
  7. */
  8. using System;
  9. using System.Collections.Generic;
  10. using System.IO;
  11. using System.Runtime.InteropServices;
  12. using Microsoft.Win32.SafeHandles;
  13. using DiskAccessLibrary;
  14. using Utilities;
  15. namespace SCSI.Win32
  16. {
  17. // An excellent C# example of SPTI can be seen here:
  18. // https://github.com/brandonlw/Psychson/blob/master/DriveCom/DriveCom/PhisonDevice.cs
  19. public class SPTITarget : SCSITarget
  20. {
  21. public const int IOCTL_SCSI_GET_INQUIRY_DATA = 0x4100c;
  22. public const int IOCTL_SCSI_PASS_THROUGH_DIRECT = 0x4D014;
  23. public const int SCSI_TIMEOUT = 60;
  24. public event EventHandler<LogEntry> OnLogEntry;
  25. private string m_path;
  26. private SafeFileHandle m_handle;
  27. private bool m_emulateReportLUNs;
  28. private LogicalUnitManager m_logicalUnitManager = new LogicalUnitManager();
  29. public SPTITarget(string path) : this(path, false)
  30. {
  31. }
  32. public SPTITarget(string path, bool emulateReportLUNs)
  33. {
  34. m_path = path;
  35. m_handle = HandleUtils.GetFileHandle(m_path, FileAccess.ReadWrite, ShareMode.None);
  36. m_emulateReportLUNs = emulateReportLUNs;
  37. }
  38. public void Close()
  39. {
  40. m_handle.Close();
  41. }
  42. [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
  43. public static extern bool DeviceIoControl(SafeHandle hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped);
  44. /// <summary>
  45. /// This takes the iSCSI command and forwards it to a SCSI Passthrough device. It then returns the response.
  46. /// </summary>
  47. public override SCSIStatusCodeName ExecuteCommand(byte[] commandBytes, LUNStructure lun, byte[] data, out byte[] response)
  48. {
  49. // SPTI only supports up to 16 byte CDBs
  50. if (commandBytes.Length > SCSI_PASS_THROUGH_DIRECT.CdbBufferLength)
  51. {
  52. response = VirtualSCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
  53. return SCSIStatusCodeName.CheckCondition;
  54. }
  55. if (commandBytes[0] == (byte)SCSIOpCodeName.ReportLUNs)
  56. {
  57. if (m_emulateReportLUNs)
  58. {
  59. Log(Severity.Verbose, "SCSI Command: ReportLUNs");
  60. ReportLUNsParameter parameter = new ReportLUNsParameter();
  61. parameter.LUNList.Add(0);
  62. response = parameter.GetBytes();
  63. if (m_logicalUnitManager.FindLogicalUnit(0) == null)
  64. {
  65. m_logicalUnitManager.AddLogicalUnit(new LogicalUnit());
  66. }
  67. return SCSIStatusCodeName.Good;
  68. }
  69. else
  70. {
  71. return ReportLUNs(out response);
  72. }
  73. }
  74. LogicalUnit logicalUnit = m_logicalUnitManager.FindLogicalUnit((byte)lun);
  75. if (logicalUnit == null)
  76. {
  77. response = VirtualSCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  78. return SCSIStatusCodeName.CheckCondition;
  79. }
  80. // Pad all CDBs to 16 bytes
  81. Array.Resize(ref commandBytes, SCSI_PASS_THROUGH_DIRECT.CdbBufferLength);
  82. // Build SCSI Passthrough structure
  83. SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER scsi = BuildSCSIPassThroughStructure(commandBytes, logicalUnit, data);
  84. if (scsi == null)
  85. {
  86. response = VirtualSCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
  87. return SCSIStatusCodeName.CheckCondition;
  88. }
  89. uint bytesReturned;
  90. IntPtr inBuffer = inBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(scsi));
  91. uint size = (uint)Marshal.SizeOf(scsi);
  92. Marshal.StructureToPtr(scsi, inBuffer, true);
  93. // Forward SCSI command to target
  94. SCSIStatusCodeName status;
  95. if (!DeviceIoControl(m_handle, IOCTL_SCSI_PASS_THROUGH_DIRECT, inBuffer, size, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero))
  96. {
  97. // Notes:
  98. // 1. DeviceIoControl will return ERROR_INVALID_HANDLE under Windows Vista or later if not running as administrator.
  99. // 2. If a device class driver has claimed the device then passthrough IOCTLs must go through the device class driver.
  100. // Sending IOCTLs to the port driver will return ERROR_INVALID_FUNCTION in such cases.
  101. // To work with an HBA one can disable the disk drivers of disks connected to that HBA.
  102. Win32Error lastError = (Win32Error)Marshal.GetLastWin32Error();
  103. Log(Severity.Error, "DeviceIoControl/IOCTL_SCSI_PASS_THROUGH_DIRECT error: {0}, Device path: {1}", lastError, m_path);
  104. response = VirtualSCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
  105. status = SCSIStatusCodeName.CheckCondition;
  106. }
  107. else
  108. {
  109. Marshal.PtrToStructure(inBuffer, scsi);
  110. status = (SCSIStatusCodeName)scsi.Spt.ScsiStatus;
  111. if (status != SCSIStatusCodeName.Good)
  112. {
  113. Log(Severity.Verbose, "SCSI Status: {0}, Sense: {1}", status, BitConverter.ToString(scsi.Sense));
  114. response = new byte[scsi.Sense.Length + 2];
  115. BigEndianWriter.WriteUInt16(response, 0, (ushort)scsi.Sense.Length);
  116. ByteWriter.WriteBytes(response, 2, scsi.Sense);
  117. }
  118. else
  119. {
  120. if (scsi.Spt.DataTransferLength > 0)
  121. {
  122. if (scsi.Spt.DataIn == (byte)SCSIDataDirection.In)
  123. {
  124. response = new byte[scsi.Spt.DataTransferLength];
  125. Marshal.Copy(scsi.Spt.DataBuffer, response, 0, response.Length);
  126. }
  127. else
  128. {
  129. response = new byte[0];
  130. }
  131. Log(Severity.Verbose, "SCSI Status: {0}, Response Length: {1}", status, response.Length);
  132. if (commandBytes[0] == (byte)SCSIOpCodeName.Inquiry)
  133. {
  134. InterceptInquiry(logicalUnit, commandBytes, response);
  135. }
  136. else if (commandBytes[0] == (byte)SCSIOpCodeName.ModeSelect6)
  137. {
  138. InterceptModeSelect6(logicalUnit, commandBytes, data);
  139. }
  140. else if (commandBytes[0] == (byte)SCSIOpCodeName.ModeSense6)
  141. {
  142. InterceptModeSense6(logicalUnit, commandBytes, response);
  143. }
  144. else if (commandBytes[0] == (byte)SCSIOpCodeName.ReadCapacity10)
  145. {
  146. InterceptReadCapacity10(logicalUnit, commandBytes, response);
  147. }
  148. else if (commandBytes[0] == (byte)SCSIOpCodeName.ModeSelect10)
  149. {
  150. InterceptModeSelect10(logicalUnit, commandBytes, data);
  151. }
  152. else if (commandBytes[0] == (byte)SCSIOpCodeName.ModeSense10)
  153. {
  154. InterceptModeSense10(logicalUnit, commandBytes, response);
  155. }
  156. else if (commandBytes[0] == (byte)SCSIOpCodeName.ServiceActionIn16 && commandBytes[1] == (byte)ServiceAction.ReadCapacity16)
  157. {
  158. InterceptReadCapacity16(logicalUnit, commandBytes, response);
  159. }
  160. }
  161. else
  162. {
  163. // SPTI request was GOOD, no data in response buffer.
  164. response = new byte[0];
  165. Log(Severity.Verbose, "SCSI Status: {0}", status);
  166. }
  167. }
  168. }
  169. Marshal.FreeHGlobal(inBuffer);
  170. if (scsi.Spt.DataBuffer != IntPtr.Zero)
  171. {
  172. Marshal.FreeHGlobal(scsi.Spt.DataBuffer);
  173. }
  174. return status;
  175. }
  176. private SCSIStatusCodeName ReportLUNs(out byte[] response)
  177. {
  178. uint bytesReturned;
  179. uint outBufferLength = 4096;
  180. IntPtr outBuffer = Marshal.AllocHGlobal((int)outBufferLength);
  181. SCSIStatusCodeName status;
  182. if (!DeviceIoControl(m_handle, IOCTL_SCSI_GET_INQUIRY_DATA, IntPtr.Zero, 0, outBuffer, outBufferLength, out bytesReturned, IntPtr.Zero))
  183. {
  184. Win32Error lastError = (Win32Error)Marshal.GetLastWin32Error();
  185. Log(Severity.Error, "DeviceIoControl/IOCTL_SCSI_GET_INQUIRY_DATA error: {0}, Device path: {1}", lastError, m_path);
  186. response = VirtualSCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
  187. status = SCSIStatusCodeName.CheckCondition;
  188. }
  189. else
  190. {
  191. List<SCSI_INQUIRY_DATA> devices = SCSI_ADAPTER_BUS_INFO.GetInquiryDataForAllDevices(outBuffer);
  192. ReportLUNsParameter parameter = new ReportLUNsParameter();
  193. foreach (SCSI_INQUIRY_DATA device in devices)
  194. {
  195. // If the device has been claimed by a class driver then passthrough IOCTLs must go through the class driver
  196. if (!device.DeviceClaimed)
  197. {
  198. PeripheralDeviceType deviceType = (PeripheralDeviceType)(device.InquiryData[0] & 0x1F);
  199. if (deviceType == PeripheralDeviceType.DirectAccessBlockDevice |
  200. deviceType == PeripheralDeviceType.SequentialAccessDevice |
  201. deviceType == PeripheralDeviceType.CDRomDevice)
  202. {
  203. byte? associatedLUN = m_logicalUnitManager.FindAssociatedLUN(device.PathId, device.TargetId, device.Lun);
  204. if (!associatedLUN.HasValue)
  205. {
  206. associatedLUN = m_logicalUnitManager.FindUnusedLUN();
  207. LogicalUnit logicalUnit = new LogicalUnit();
  208. logicalUnit.AssociatedLun = associatedLUN.Value;
  209. logicalUnit.PathId = device.PathId;
  210. logicalUnit.TargetId = device.TargetId;
  211. logicalUnit.TargetLun = device.Lun;
  212. logicalUnit.DeviceType = deviceType;
  213. m_logicalUnitManager.AddLogicalUnit(logicalUnit);
  214. Log(Severity.Verbose, "Assigned virtual LUN {0} to device PathId: {1}, TargetId: {2}, LUN: {3}", associatedLUN.Value, device.PathId, device.TargetId, device.Lun);
  215. }
  216. if (!associatedLUN.HasValue)
  217. {
  218. throw new NotImplementedException("The maximum number of LUNs supported has been reached");
  219. }
  220. parameter.LUNList.Add(associatedLUN.Value);
  221. }
  222. }
  223. }
  224. response = parameter.GetBytes();
  225. Log(Severity.Verbose, "DeviceIoControl/IOCTL_SCSI_GET_INQUIRY_DATA reported {0} usable devices", parameter.LUNList.Count);
  226. status = SCSIStatusCodeName.Good;
  227. }
  228. Marshal.FreeHGlobal(outBuffer);
  229. return status;
  230. }
  231. private SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER BuildSCSIPassThroughStructure(byte[] commandBytes, LogicalUnit logicalUnit, byte[] data)
  232. {
  233. SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER scsi = null;
  234. scsi = new SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER();
  235. scsi.Spt.Length = (ushort)Marshal.SizeOf(scsi.Spt);
  236. scsi.Spt.PathId = logicalUnit.PathId;
  237. scsi.Spt.TargetId = logicalUnit.TargetId;
  238. scsi.Spt.Lun = logicalUnit.TargetLun;
  239. scsi.Spt.CdbLength = (byte)commandBytes.Length;
  240. scsi.Spt.Cdb = commandBytes;
  241. if (data != null && data.Length > 0)
  242. {
  243. // DATA OUT (from initiator to target, WRITE)
  244. scsi.Spt.DataIn = (byte)SCSIDataDirection.Out;
  245. scsi.Spt.DataTransferLength = (uint)data.Length;
  246. scsi.Spt.DataBuffer = Marshal.AllocHGlobal((int)scsi.Spt.DataTransferLength);
  247. Marshal.Copy(data, 0, scsi.Spt.DataBuffer, data.Length);
  248. }
  249. else
  250. {
  251. // DATA IN (to initiator from target, READ)
  252. scsi.Spt.DataIn = (byte)SCSICommandParser.GetDataDirection(commandBytes);
  253. if ((SCSIDataDirection)scsi.Spt.DataIn == SCSIDataDirection.In)
  254. {
  255. scsi.Spt.DataTransferLength = GetDataInTransferLength(commandBytes, logicalUnit);
  256. scsi.Spt.DataBuffer = Marshal.AllocHGlobal((int)scsi.Spt.DataTransferLength);
  257. }
  258. else
  259. {
  260. scsi.Spt.DataTransferLength = 0; // No data!
  261. scsi.Spt.DataBuffer = IntPtr.Zero;
  262. }
  263. }
  264. Log(Severity.Verbose, "SCSI Command: {0}, Data Length: {1}, Transfer Direction: {2}, Transfer Length: {3}, LUN: {4}", (SCSIOpCodeName)commandBytes[0], data.Length, (SCSIDataDirection)scsi.Spt.DataIn, scsi.Spt.DataTransferLength, logicalUnit.AssociatedLun);
  265. scsi.Spt.TimeOutValue = SCSI_TIMEOUT;
  266. scsi.Spt.SenseInfoOffset = (uint)Marshal.OffsetOf(typeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER), "Sense");
  267. scsi.Spt.SenseInfoLength = (byte)scsi.Sense.Length;
  268. return scsi;
  269. }
  270. private uint GetDataInTransferLength(byte[] commandBytes, LogicalUnit logicalUnit)
  271. {
  272. switch ((SCSIOpCodeName)commandBytes[0])
  273. {
  274. case SCSIOpCodeName.Read16: // DATA_IN (12-14)
  275. case SCSIOpCodeName.ReadReverse16: // DATA_IN (12-14)
  276. case SCSIOpCodeName.Read6: // DATA_IN (2-4)
  277. case SCSIOpCodeName.ReadReverse6: // DATA_IN (2-4)
  278. case SCSIOpCodeName.Read10: // DATA_IN (7-8)
  279. case SCSIOpCodeName.Read12: // DATA_IN (6-9)
  280. {
  281. if (logicalUnit.BlockSize == null)
  282. {
  283. throw new NotSupportedException("Command Sequence Not Supported!");
  284. }
  285. return SCSICommandParser.GetDeviceReadTransferLength(commandBytes, logicalUnit.DeviceType, logicalUnit.BlockSize.Value);
  286. }
  287. default:
  288. return SCSICommandParser.GetCDBTransferLength(commandBytes, logicalUnit.DeviceType);
  289. }
  290. }
  291. // Intercept Inquiry and update the peripheral device type
  292. private void InterceptInquiry(LogicalUnit logicalUnit, byte[] commandBytes, byte[] response)
  293. {
  294. bool EVPD = ((commandBytes[1] & 0x01) != 0);
  295. byte pageCode = commandBytes[2];
  296. if (!EVPD && pageCode == 0)
  297. {
  298. logicalUnit.DeviceType = (PeripheralDeviceType)(response[0] & 0x1F);
  299. Log(Severity.Verbose, "LUN: {0}, DeviceType updated to {1}", logicalUnit.AssociatedLun, logicalUnit.DeviceType);
  300. }
  301. }
  302. private void InterceptModeSelect6(LogicalUnit logicalUnit, byte[] commandBytes, byte[] data)
  303. {
  304. ModeParameterHeader6 header = new ModeParameterHeader6(data, 0);
  305. if (header.BlockDescriptorLength == ShortLBAModeParameterBlockDescriptor.Length)
  306. {
  307. ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor(data, ModeParameterHeader6.Length);
  308. logicalUnit.BlockSize = descriptor.LogicalBlockLength;
  309. Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
  310. }
  311. }
  312. private void InterceptModeSense6(LogicalUnit logicalUnit, byte[] commandBytes, byte[] response)
  313. {
  314. ModeParameterHeader6 header = new ModeParameterHeader6(response, 0);
  315. if (header.BlockDescriptorLength == ShortLBAModeParameterBlockDescriptor.Length)
  316. {
  317. ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor(response, ModeParameterHeader6.Length);
  318. logicalUnit.BlockSize = descriptor.LogicalBlockLength;
  319. Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
  320. }
  321. }
  322. private void InterceptModeSelect10(LogicalUnit logicalUnit, byte[] commandBytes, byte[] data)
  323. {
  324. ModeParameterHeader10 header = new ModeParameterHeader10(data, 0);
  325. if (header.BlockDescriptorLength == ShortLBAModeParameterBlockDescriptor.Length)
  326. {
  327. ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor(data, ModeParameterHeader10.Length);
  328. logicalUnit.BlockSize = descriptor.LogicalBlockLength;
  329. Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
  330. }
  331. else if (header.BlockDescriptorLength == LongLBAModeParameterBlockDescriptor.Length)
  332. {
  333. LongLBAModeParameterBlockDescriptor descriptor = new LongLBAModeParameterBlockDescriptor(data, ModeParameterHeader10.Length);
  334. logicalUnit.BlockSize = descriptor.LogicalBlockLength;
  335. Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
  336. }
  337. }
  338. private void InterceptModeSense10(LogicalUnit logicalUnit, byte[] commandBytes, byte[] response)
  339. {
  340. ModeParameterHeader10 header = new ModeParameterHeader10(response, 0);
  341. if (header.BlockDescriptorLength == ShortLBAModeParameterBlockDescriptor.Length)
  342. {
  343. ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor(response, ModeParameterHeader10.Length);
  344. logicalUnit.BlockSize = descriptor.LogicalBlockLength;
  345. Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
  346. }
  347. else if (header.BlockDescriptorLength == LongLBAModeParameterBlockDescriptor.Length)
  348. {
  349. LongLBAModeParameterBlockDescriptor descriptor = new LongLBAModeParameterBlockDescriptor(commandBytes, ModeParameterHeader10.Length);
  350. logicalUnit.BlockSize = descriptor.LogicalBlockLength;
  351. Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
  352. }
  353. }
  354. private void InterceptReadCapacity10(LogicalUnit logicalUnit, byte[] commandBytes, byte[] response)
  355. {
  356. ReadCapacity10Parameter parameter = new ReadCapacity10Parameter(response);
  357. logicalUnit.BlockSize = parameter.BlockLengthInBytes;
  358. Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
  359. }
  360. private void InterceptReadCapacity16(LogicalUnit logicalUnit, byte[] commandBytes, byte[] response)
  361. {
  362. ReadCapacity16Parameter parameter = new ReadCapacity16Parameter(response);
  363. logicalUnit.BlockSize = parameter.BlockLengthInBytes;
  364. Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
  365. }
  366. public void Log(Severity severity, string message)
  367. {
  368. // To be thread-safe we must capture the delegate reference first
  369. EventHandler<LogEntry> handler = OnLogEntry;
  370. if (handler != null)
  371. {
  372. handler(this, new LogEntry(DateTime.Now, severity, "SPTI Target", message));
  373. }
  374. }
  375. public void Log(Severity severity, string message, params object[] args)
  376. {
  377. Log(severity, String.Format(message, args));
  378. }
  379. }
  380. }