123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Text;
- using System.Runtime.InteropServices;
- using DiskAccessLibrary;
- using Utilities;
- namespace ISCSI.Server
- {
- public class SCSITarget
- {
- private List<Disk> m_disks;
- public object WriteLock = new object();
- public SCSITarget(List<Disk> disks)
- {
- m_disks = disks;
- }
- public SCSIStatusCodeName ExecuteCommand(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
- {
- if (command.OpCode == SCSIOpCodeName.TestUnitReady)
- {
- return TestUnitReady(lun, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.RequestSense)
- {
- return RequestSense(lun, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.Inquiry)
- {
- return Inquiry((InquiryCommand)command, lun, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.Reserve6)
- {
- return Reserve6(lun, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.Release6)
- {
- return Release6(lun, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.ModeSense6)
- {
- return ModeSense6((ModeSense6CommandDescriptorBlock)command, lun, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.ReadCapacity10)
- {
- return ReadCapacity10(lun, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.Read6 ||
- command.OpCode == SCSIOpCodeName.Read10 ||
- command.OpCode == SCSIOpCodeName.Read16)
- {
- return Read(command, lun, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.Write6 ||
- command.OpCode == SCSIOpCodeName.Write10 ||
- command.OpCode == SCSIOpCodeName.Write16)
- {
- return Write(command, lun, data, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.Verify10 ||
- command.OpCode == SCSIOpCodeName.Verify16)
- {
- return Verify(lun, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.SynchronizeCache10)
- {
- return SynchronizeCache10(lun, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.ServiceActionIn &&
- command.ServiceAction == ServiceAction.ReadCapacity16)
- {
- return ReadCapacity16(lun, out response);
- }
- else if (command.OpCode == SCSIOpCodeName.ReportLUNs)
- {
- return ReportLUNs(out response);
- }
- else
- {
- ISCSIServer.Log("[ExecuteCommand] Unsupported SCSI Command (0x{0})", command.OpCode.ToString("X"));
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- }
- public SCSIStatusCodeName Inquiry(InquiryCommand command, LUNStructure lun, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- if (!command.EVPD)
- {
- StandardInquiryData inquiryData = new StandardInquiryData();
- inquiryData.PeripheralDeviceType = 0; // Direct access block device
- inquiryData.VendorIdentification = "iSCSIConsole";
- inquiryData.ProductIdentification = "Disk_" + lun.ToString();
- inquiryData.ProductRevisionLevel = "1.00";
- inquiryData.DriveSerialNumber = 0;
- inquiryData.CmdQue = true;
- inquiryData.Version = 5; // Microsoft iSCSI Target report version 5
- response = inquiryData.GetBytes();
- }
- else
- {
- switch (command.PageCode)
- {
- case VitalProductDataPageName.SupportedVPDPages:
- {
- SupportedVitaLProductDataPages page = new SupportedVitaLProductDataPages();
- page.SupportedPageList.Add((byte)VitalProductDataPageName.SupportedVPDPages);
- page.SupportedPageList.Add((byte)VitalProductDataPageName.UnitSerialNumber);
- page.SupportedPageList.Add((byte)VitalProductDataPageName.DeviceIdentification);
- response = page.GetBytes();
- break;
- }
- case VitalProductDataPageName.UnitSerialNumber:
- {
- UnitSerialNumberVPDPage page = new UnitSerialNumberVPDPage();
- // 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
- // Microsoft iSCSI Target uses values such as "34E5A6FC-3ACC-452D-AEDA-6EE2EFF20FB4"
- ulong serialNumber = 0;
- page.ProductSerialNumber = serialNumber.ToString("00000000");
- response = page.GetBytes();
- break;
- }
- case VitalProductDataPageName.DeviceIdentification:
- {
- DeviceIdentificationVPDPage page = new DeviceIdentificationVPDPage();
- // Identifiers necessity is preliminary, and has not been confirmed:
- // WWN identifier is needed to prevent 0xF4 BSOD during Windows setup
- // ISCSI identifier is needed for WinPE to pick up the disk during boot (after iPXE's sanhook)
- page.IdentificationDescriptorList.Add(new IdentificationDescriptor(5, lun));
- if (this is ISCSITarget)
- {
- page.IdentificationDescriptorList.Add(IdentificationDescriptor.GetISCSIIdentifier(((ISCSITarget)this).TargetName));
- }
- response = page.GetBytes();
- break;
- }
- case VitalProductDataPageName.BlockLimits:
- {
- /* Provide only when requeste explicitly */
- BlockLimitsVPDPage page = new BlockLimitsVPDPage();
- page.OptimalTransferLengthGranularity = 128;
- page.MaximumTransferLength = (uint)DiskAccessLibrary.Settings.MaximumTransferSizeLBA;
- page.OptimalTransferLength = 128;
- response = page.GetBytes();
- break;
- }
- case VitalProductDataPageName.BlockDeviceCharacteristics:
- {
- /* Provide only when requeste explicitly */
- BlockDeviceCharacteristicsVPDPage page = new BlockDeviceCharacteristicsVPDPage();
- page.MediumRotationRate = 0; // Not reported
- response = page.GetBytes();
- break;
- }
- default:
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestParameterNotSupportedSenseData());
- ISCSIServer.Log("[Inquiry] Unsupported VPD Page request (0x{0})", command.PageCode.ToString("X"));
- return SCSIStatusCodeName.CheckCondition;
- }
- }
- }
- // we must not return more bytes than InquiryCommand.AllocationLength
- if (response.Length > command.AllocationLength)
- {
- response = ByteReader.ReadBytes(response, 0, command.AllocationLength);
- }
- return SCSIStatusCodeName.Good;
- }
- public SCSIStatusCodeName ModeSense6(ModeSense6CommandDescriptorBlock command, LUNStructure lun, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor();
- descriptor.LogicalBlockLength = (uint)m_disks[lun].BytesPerSector;
- ModeParameterHeader6 header = new ModeParameterHeader6();
- header.WP = m_disks[lun].IsReadOnly; // Write protected, even when set to true, Windows does not always prevent the disk from being written to.
- header.DPOFUA = true; // Microsoft iSCSI Target support this
- header.BlockDescriptorLength = (byte)descriptor.Length;
- header.ModeDataLength += (byte)descriptor.Length;
- byte[] pageData = new byte[0];
- switch ((ModePageCodeName)command.PageCode)
- {
- case ModePageCodeName.CachingParametersPage:
- {
- CachingParametersPage page = new CachingParametersPage();
- page.RCD = true;
- header.ModeDataLength += (byte)page.Length;
- pageData = new byte[page.Length];
- Array.Copy(page.GetBytes(), pageData, page.Length);
- break;
- }
- case ModePageCodeName.ControlModePage:
- {
- ControlModePage page = new ControlModePage();
- header.ModeDataLength += (byte)page.Length;
- pageData = new byte[page.Length];
- Array.Copy(page.GetBytes(), pageData, page.Length);
- break;
- }
- case ModePageCodeName.InformationalExceptionsControlModePage:
- {
- InformationalExceptionsControlModePage page = new InformationalExceptionsControlModePage();
- header.ModeDataLength += (byte)page.Length;
- pageData = new byte[page.Length];
- Array.Copy(page.GetBytes(), pageData, page.Length);
- break;
- }
- case ModePageCodeName.ReturnAllPages:
- {
- CachingParametersPage page1 = new CachingParametersPage();
- page1.RCD = true;
- header.ModeDataLength += (byte)page1.Length;
- InformationalExceptionsControlModePage page2 = new InformationalExceptionsControlModePage();
- header.ModeDataLength += (byte)page2.Length;
- pageData = new byte[page1.Length + page2.Length];
- Array.Copy(page1.GetBytes(), pageData, page1.Length);
- Array.Copy(page2.GetBytes(), 0, pageData, page1.Length, page2.Length);
- break;
- }
- case ModePageCodeName.VendorSpecificPage:
- {
- // Microsoft iSCSI Target running under Windows 2000 will request this page, we immitate Microsoft iSCSI Target by sending back an empty page
- VendorSpecificPage page = new VendorSpecificPage();
- header.ModeDataLength += (byte)page.Length;
- pageData = new byte[page.Length];
- Array.Copy(page.GetBytes(), pageData, page.Length);
- break;
- }
- default:
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestParameterNotSupportedSenseData());
- ISCSIServer.Log("[ModeSense6] ModeSense6 page 0x{0} is not implemented", command.PageCode.ToString("x"));
- return SCSIStatusCodeName.CheckCondition;
- }
- }
- response = new byte[1 + header.ModeDataLength];
- Array.Copy(header.GetBytes(), 0, response, 0, header.Length);
- Array.Copy(descriptor.GetBytes(), 0, response, header.Length, descriptor.Length);
- Array.Copy(pageData, 0, response, header.Length + descriptor.Length, pageData.Length);
- return SCSIStatusCodeName.Good;
- }
- public SCSIStatusCodeName ReadCapacity10(LUNStructure lun, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- ReadCapacity10Parameter parameter = new ReadCapacity10Parameter(m_disks[lun].Size, (uint)m_disks[lun].BytesPerSector);
- response = parameter.GetBytes();
- return SCSIStatusCodeName.Good;
- }
- public SCSIStatusCodeName ReadCapacity16(LUNStructure lun, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- ReadCapacity16Parameter parameter = new ReadCapacity16Parameter(m_disks[lun].Size, (uint)m_disks[lun].BytesPerSector);
- response = parameter.GetBytes();
- return SCSIStatusCodeName.Good;
- }
- public SCSIStatusCodeName ReportLUNs(out byte[] response)
- {
- ReportLUNsParameter parameter = new ReportLUNsParameter(m_disks.Count);
- response = parameter.GetBytes();
- return SCSIStatusCodeName.Good;
- }
- public SCSIStatusCodeName Read(SCSICommandDescriptorBlock command, LUNStructure lun, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- Disk disk = m_disks[lun];
- int sectorCount = (int)command.TransferLength;
- try
- {
- response = disk.ReadSectors((long)command.LogicalBlockAddress64, sectorCount);
- return SCSIStatusCodeName.Good;
- }
- catch (ArgumentOutOfRangeException)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestLBAOutOfRangeSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- catch (IOException ex)
- {
- int error = Marshal.GetHRForException(ex);
- if (error == (int)Win32Error.ERROR_CRC)
- {
- response = FormatSenseData(SenseDataParameter.GetWriteFaultSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- else
- {
- ISCSIServer.Log("[{0}][Read] Read error:", ex.ToString());
- response = FormatSenseData(SenseDataParameter.GetUnitAttentionSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- }
- }
- // Some initiators (i.e. EFI iSCSI DXE) will send 'Request Sense' upon connection (likely just to verify the medium is ready)
- public SCSIStatusCodeName RequestSense(LUNStructure lun, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- response = FormatSenseData(SenseDataParameter.GetNoSenseSenseData());
- return SCSIStatusCodeName.Good;
- }
- public SCSIStatusCodeName Reserve6(LUNStructure lun, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- response = new byte[0];
- return SCSIStatusCodeName.Good;
- }
- public SCSIStatusCodeName SynchronizeCache10(LUNStructure lun, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- response = new byte[0];
- return SCSIStatusCodeName.Good;
- }
- public SCSIStatusCodeName Release6(LUNStructure lun, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- response = new byte[0];
- return SCSIStatusCodeName.Good;
- }
- public SCSIStatusCodeName TestUnitReady(LUNStructure lun, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- response = new byte[0];
- return SCSIStatusCodeName.Good;
- }
- public SCSIStatusCodeName Verify(LUNStructure lun, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- response = new byte[0];
- return SCSIStatusCodeName.Good;
- }
- public SCSIStatusCodeName Write(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
- {
- return Write(lun, (long)command.LogicalBlockAddress64, data, out response);
- }
- public SCSIStatusCodeName Write(LUNStructure lun, long sectorIndex, byte[] data, out byte[] response)
- {
- if (lun >= m_disks.Count)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- Disk disk = m_disks[lun];
- if (disk.IsReadOnly)
- {
- SenseDataParameter senseData = SenseDataParameter.GetDataProtectSenseData();
- response = senseData.GetBytes();
- return SCSIStatusCodeName.CheckCondition;
- }
- lock (WriteLock)
- {
- try
- {
- disk.WriteSectors(sectorIndex, data);
- response = new byte[0];
- return SCSIStatusCodeName.Good;
- }
- catch (ArgumentOutOfRangeException)
- {
- response = FormatSenseData(SenseDataParameter.GetIllegalRequestLBAOutOfRangeSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- catch (IOException ex)
- {
- ISCSIServer.Log("[{0}][Write] Write error:", ex.ToString());
- response = FormatSenseData(SenseDataParameter.GetUnitAttentionSenseData());
- return SCSIStatusCodeName.CheckCondition;
- }
- }
- }
- public List<Disk> Disks
- {
- get
- {
- return m_disks;
- }
- }
- public static byte[] FormatSenseData(SenseDataParameter senseData)
- {
- byte[] senseDataBytes = senseData.GetBytes();
- byte[] result = new byte[senseDataBytes.Length + 2];
- Array.Copy(BigEndianConverter.GetBytes((ushort)senseDataBytes.Length), 0, result, 0, 2);
- Array.Copy(senseDataBytes, 0, result, 2, senseDataBytes.Length);
- return result;
- }
- }
- }
|