SCSITarget.cs 24 KB

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