diff --git a/Core/Extensions/InteropExtensions.cs b/Core/Extensions/InteropExtensions.cs index 3218aa1f..1bce8224 100644 --- a/Core/Extensions/InteropExtensions.cs +++ b/Core/Extensions/InteropExtensions.cs @@ -54,6 +54,24 @@ namespace Vanara.Extensions } #endif + /// Returns the pointer. + /// The type of items. + /// A pointer to the starting address of a specified number of elements in memory. + /// The number of elements to be included in the pointer. + /// Bytes to skip before starting the span. + /// If known, the total number of bytes allocated to the native memory in . + /// A pointer that represents the memory. + /// + public static unsafe T* AsUnmanagedArrayPointer(this IntPtr ptr, int length, int prefixBytes = 0, SizeT allocatedBytes = default) where T : unmanaged + { + if (ptr == IntPtr.Zero) return null; + if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); + if (allocatedBytes > 0 && SizeOf() * length + prefixBytes > allocatedBytes) + throw new InsufficientMemoryException(); + + return (T*)ptr.Offset(prefixBytes).ToPointer(); + } + /// Copies the number of specified bytes from one unmanaged memory block to another. /// The allocated memory pointer. /// The allocated memory pointer to copy to. diff --git a/Core/RefEnumerator.cs b/Core/RefEnumerator.cs new file mode 100644 index 00000000..3deba494 --- /dev/null +++ b/Core/RefEnumerator.cs @@ -0,0 +1,47 @@ +using System.Runtime.InteropServices; + +namespace Vanara.PInvoke +{ + /// + /// Enumerator with zero copy access using ref. + /// + public unsafe ref struct RefEnumerator where T : unmanaged + { + private T* _arrayPtr; + private readonly int _count; + private int _index; + + /// + /// Create RefEnumerator. + /// + /// Pointer to unmanaged array + /// Number of elements in the + public RefEnumerator(T* arrayPtr, int count) + { + _arrayPtr = arrayPtr; + _count = count; + _index = -1; + } + + /// + /// Move to next element. + /// + public bool MoveNext() + { + int index = _index + 1; + if (index < _count) + { + _index = index; + _arrayPtr++; + return true; + } + + return false; + } + + /// + /// Return current element. + /// + public ref T Current => ref *_arrayPtr; + } +} diff --git a/PInvoke/IpHlpApi/IpHlpApi.cs b/PInvoke/IpHlpApi/IpHlpApi.cs index ee0236b5..1a3e6c80 100644 --- a/PInvoke/IpHlpApi/IpHlpApi.cs +++ b/PInvoke/IpHlpApi/IpHlpApi.cs @@ -7801,6 +7801,9 @@ namespace Vanara.PInvoke /// Dashed hex value string representation of a Physical Address (MAC). public static string PhysicalAddressToString(byte[] physAddr) => $"{physAddr[0]:X}-{physAddr[1]:X}-{physAddr[2]:X}-{physAddr[3]:X}-{physAddr[4]:X}-{physAddr[5]:X}"; + /// + public static unsafe string PhysicalAddressToString(byte* physAddr) => $"{physAddr[0]:X}-{physAddr[1]:X}-{physAddr[2]:X}-{physAddr[3]:X}-{physAddr[4]:X}-{physAddr[5]:X}"; + /// /// The RestoreMediaSense function restores the media sensing capability of the TCP/IP stack on a local computer on which the /// DisableMediaSense function was previously called. diff --git a/PInvoke/IpHlpApi/NetIOApi.cs b/PInvoke/IpHlpApi/NetIOApi.cs index c21ee6ad..4f60958e 100644 --- a/PInvoke/IpHlpApi/NetIOApi.cs +++ b/PInvoke/IpHlpApi/NetIOApi.cs @@ -3365,6 +3365,14 @@ namespace Vanara.PInvoke [PInvokeData("netioapi.h", MSDNShortId = "6c45d735-9a07-41ca-8d8a-919f32c98a3c")] public static extern Win32Error GetIpNetTable2(ADDRESS_FAMILY Family, out MIB_IPNET_TABLE2 Table); + /// + /// Unlike this returns structure with zero copy. + // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipnettable2 _NETIOAPI_SUCCESS_ NETIOAPI_API + // GetIpNetTable2( ADDRESS_FAMILY Family, PMIB_IPNET_TABLE2 *Table ); + [DllImport(Lib.IpHlpApi, SetLastError = false, ExactSpelling = true, EntryPoint = "GetIpNetTable2")] + [PInvokeData("netioapi.h", MSDNShortId = "6c45d735-9a07-41ca-8d8a-919f32c98a3c")] + public static extern Win32Error GetIpNetTable2_Unmanaged(ADDRESS_FAMILY Family, out MIB_IPNET_TABLE2_Unmanaged Table); + /// /// /// The GetIpNetworkConnectionBandwidthEstimates function retrieves historical bandwidth estimates for a network connection on @@ -6975,6 +6983,203 @@ namespace Vanara.PInvoke public override string ToString() => $"{Address}; MAC:{PhysicalAddressToString(PhysicalAddress)}; If:{(InterfaceIndex != 0 ? InterfaceIndex.ToString() : InterfaceLuid.ToString())}"; } + /// + /// Unlike this structure is zero copy + [PInvokeData("netioapi.h", MSDNShortId = "164dbd93-4464-40f9-989a-17597102b1d8")] + [StructLayout(LayoutKind.Sequential, Pack = 2)] + public unsafe struct MIB_IPNET_ROW2_Unmanaged + { + /// + /// Type: SOCKADDR_INET + /// The neighbor IP address. This member can be an IPv6 address or an IPv4 address. + /// + public SOCKADDR_INET Address; + + /// + /// Type: NET_IFINDEX + /// + /// The local index value for the network interface associated with this IP address. This index value may change when a network + /// adapter is disabled and then enabled, or under other circumstances, and should not be considered persistent. + /// + /// + public uint InterfaceIndex; + + /// + /// Type: NET_LUID + /// The locally unique identifier (LUID) for the network interface associated with this IP address. + /// + public NET_LUID InterfaceLuid; + + /// + /// Type: UCHAR[IF_MAX_PHYS_ADDRESS_LENGTH] + /// The physical hardware address of the adapter for the network interface associated with this IP address. + /// + public fixed byte PhysicalAddress[IF_MAX_PHYS_ADDRESS_LENGTH]; + + /// + /// Type: ULONG + /// + /// The length, in bytes, of the physical hardware address specified by the PhysicalAddress member. The maximum value + /// supported is 32 bytes. + /// + /// + public uint PhysicalAddressLength; + + /// + /// Type: NL_NEIGHBOR_STATE + /// + /// The state of a network neighbor IP address as defined in RFC 2461, section 7.3.2. For more information, see + /// http://www.ietf.org/rfc/rfc2461.txt. This member can be one of the values from the NL_NEIGHBOR_STATE enumeration type + /// defined in the Nldef.h header file. + /// + /// + /// + /// Value + /// Meaning + /// + /// + /// NlnsUnreachable + /// The IP address is unreachable. + /// + /// + /// NlnsIncomplete + /// + /// Address resolution is in progress and the link-layer address of the neighbor has not yet been determined. Specifically for + /// IPv6, a Neighbor Solicitation has been sent to the solicited-node multicast IP address of the target, but the corresponding + /// neighbor advertisement has not yet been received. + /// + /// + /// + /// NlnsProbe + /// + /// The neighbor is no longer known to be reachable, and probes are being sent to verify reachability. For IPv6, a reachability + /// confirmation is actively being sought by retransmitting unicast Neighbor Solicitation probes at regular intervals until a + /// reachability confirmation is received. + /// + /// + /// + /// NlnsDelay + /// + /// The neighbor is no longer known to be reachable, and traffic has recently been sent to the neighbor. Rather than probe the + /// neighbor immediately, however, delay sending probes for a short while in order to give upper layer protocols a chance to + /// provide reachability confirmation. For IPv6, more time has elapsed than is specified in the ReachabilityTime.ReachableTime + /// member since the last positive confirmation was received that the forward path was functioning properly and a packet was + /// sent. If no reachability confirmation is received within a period of time (used to delay the first probe) of entering the + /// NlnsDelay state, then a neighbor solicitation is sent and the State member is changed to NlnsProbe. + /// + /// + /// + /// NlnsStale + /// + /// The neighbor is no longer known to be reachable but until traffic is sent to the neighbor, no attempt should be made to + /// verify its reachability. For IPv6, more time has elapsed than is specified in the ReachabilityTime.ReachableTime member since + /// the last positive confirmation was received that the forward path was functioning properly. While the State is NlnsStale, no + /// action takes place until a packet is sent. The NlnsStale state is entered upon receiving an unsolicited neighbor discovery + /// message that updates the cached IP address. Receipt of such a message does not confirm reachability, and entering the + /// NlnsStale state insures reachability is verified quickly if the entry is actually being used. However, reachability is not + /// actually verified until the entry is actually used. + /// + /// + /// + /// NlnsReachable + /// + /// The neighbor is known to have been reachable recently (within tens of seconds ago). For IPv6, a positive confirmation was + /// received within the time specified in the ReachabilityTime.ReachableTime member that the forward path to the neighbor was + /// functioning properly. While the State is NlnsReachable, no special action takes place as packets are sent. + /// + /// + /// + /// NlnsPermanent + /// The IP address is a permanent address. + /// + /// + /// NlnsMaximum + /// The maximum possible value for the NL_NEIGHBOR_STATE enumeration type. This is not a legal value for the State member. + /// + /// + /// + public NL_NEIGHBOR_STATE State; + + /// Undocumented. + public MIB_IPNET_ROW2_FLAGS Flags; + + /// + /// + /// Type: ULONG The time, in milliseconds, that a node assumes a neighbor is reachable after having received a + /// reachability confirmation or is unreachable after not having received a reachability confirmation. + /// + /// + public uint ReachabilityTime; + + + /// Initializes a new instance of the struct. + /// The neighbor IP address. + /// The locally unique identifier (LUID) for the network interface associated with this IP address. + /// The physical hardware address of the adapter for the network interface associated with this IP address. + public MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN ipV4, NET_LUID ifLuid, byte[] macAddr = null) : this(ipV4, macAddr) => InterfaceLuid = ifLuid; + + /// Initializes a new instance of the struct. + /// The neighbor IP address. + /// + /// The local index value for the network interface associated with this IP address. This index value may change when a network + /// adapter is disabled and then enabled, or under other circumstances, and should not be considered persistent. + /// + /// The physical hardware address of the adapter for the network interface associated with this IP address. + public MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN ipV4, uint ifIdx, byte[] macAddr = null) : this(ipV4, macAddr) => InterfaceIndex = ifIdx; + + /// Initializes a new instance of the struct. + /// The neighbor IP address. + /// The locally unique identifier (LUID) for the network interface associated with this IP address. + /// The physical hardware address of the adapter for the network interface associated with this IP address. + public MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN6 ipV6, NET_LUID ifLuid, byte[] macAddr = null) : this(ipV6, macAddr) => InterfaceLuid = ifLuid; + + /// Initializes a new instance of the struct. + /// The neighbor IP address. + /// + /// The local index value for the network interface associated with this IP address. This index value may change when a network + /// adapter is disabled and then enabled, or under other circumstances, and should not be considered persistent. + /// + /// The physical hardware address of the adapter for the network interface associated with this IP address. + public MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN6 ipV6, uint ifIdx, byte[] macAddr = null) : this(ipV6, macAddr) => InterfaceIndex = ifIdx; + + private MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN ipV4, byte[] macAddr) : this() + { + Address.Ipv4 = ipV4; + SetMac(macAddr); + } + + private MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN6 ipV6, byte[] macAddr) : this() + { + Address.Ipv6 = ipV6; + SetMac(macAddr); + } + + private void SetMac(byte[] macAddr) + { + if (macAddr == null) + { + return; + } + + PhysicalAddressLength = IF_MAX_PHYS_ADDRESS_LENGTH; + + fixed (byte* physicalAddress = PhysicalAddress) + { + Marshal.Copy(macAddr, 0, (IntPtr)physicalAddress, 6); + } + } + + /// + public override string ToString() + { + fixed (byte* physicalAddress = PhysicalAddress) + { + return + $"{Address}; MAC:{PhysicalAddressToString(physicalAddress)}; If:{(InterfaceIndex != 0 ? InterfaceIndex.ToString() : InterfaceLuid.ToString())}"; + } + } + } + /// /// The MIB_IPPATH_ROW structure stores information about an IP path entry. /// @@ -7536,6 +7741,18 @@ namespace Vanara.PInvoke { } + /// + /// Unlike this provides is zero copy/// + [PInvokeData("Netioapi.h", MSDNShortId = "ff559267")] +#if ALLOWSPAN + [CorrespondingType(typeof(MIB_IPNET_ROW2_Unmanaged)), DefaultProperty(nameof(TableAsSpan))] +#else + [CorrespondingType(typeof(MIB_IPNET_ROW2_Unmanaged))] +#endif + public class MIB_IPNET_TABLE2_Unmanaged : SafeUnmanagedMibEntryBase + { + } + /// The MIB_IPPATH_TABLE structure contains a table of IP path entries. // typedef struct _MIB_IPPATH_TABLE { ULONG NumEntries; MIB_IPPATH_ROW Table[ANY_SIZE];} MIB_IPPATH_TABLE, *PMIB_IPPATH_TABLE; https://msdn.microsoft.com/en-us/library/windows/hardware/ff559273(v=vs.85).aspx [PInvokeData("Netioapi.h", MSDNShortId = "ff559273")] @@ -7623,6 +7840,28 @@ namespace Vanara.PInvoke IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } + /// Base class for all structures that support a variable length array of structures with a count in the first field. + /// Type of the structure array. + public abstract class SafeUnmanagedMibEntryBase : SafeMibTableHandle where T : unmanaged + { + /// Gets the number of interface entries in the array. + public virtual uint NumEntries => IsInvalid ? 0 : handle.ToStructure(); + + /// Exposes the pointer to the array of structures. + public unsafe T* AsUnmanagedArrayPointer() => IsInvalid ? null : handle.AsUnmanagedArrayPointer((int)NumEntries, Marshal.SizeOf(typeof(ulong))); + +#if !ALLOWSPAN + /// Gets the enumerator. + public unsafe RefEnumerator GetEnumerator() => new RefEnumerator(AsUnmanagedArrayPointer(), (int)NumEntries); +#else + /// Gets the enumerator. + public Span.Enumerator GetEnumerator() => TableAsSpan.GetEnumerator(); + + /// Gets the containing interface entries. + public virtual Span TableAsSpan => AsSpan((int)NumEntries, Marshal.SizeOf(typeof(ulong))); +#endif + } + /// SafeHandle for all objects that must be freed with FreeMibTable. /// public class SafeMibTableHandle : GenericSafeHandle diff --git a/UnitTests/Core/Core.csproj b/UnitTests/Core/Core.csproj index acd38b3e..c93c4a8c 100644 --- a/UnitTests/Core/Core.csproj +++ b/UnitTests/Core/Core.csproj @@ -21,6 +21,7 @@ prompt 4 AnyCPU + true pdbonly @@ -30,6 +31,7 @@ prompt 4 AnyCPU + true diff --git a/UnitTests/Core/Extensions/InteropExtensionsTests.cs b/UnitTests/Core/Extensions/InteropExtensionsTests.cs index c6d665c7..083a8b07 100644 --- a/UnitTests/Core/Extensions/InteropExtensionsTests.cs +++ b/UnitTests/Core/Extensions/InteropExtensionsTests.cs @@ -376,5 +376,19 @@ namespace Vanara.Extensions.Tests Assert.That(ss, Is.Null); } } + + [Test] + public unsafe void AsUnmanagedArrayPointerTest() + { + var h = new SafeHGlobalHandle(Marshal.SizeOf(typeof(RECT)) * 2 + i); + var rs = new[] { new RECT(), new RECT(10, 11, 12, 13) }; + ((IntPtr)h).Write(rs, i, h.Size); + + RECT* r = h.DangerousGetHandle().AsUnmanagedArrayPointer(Marshal.SizeOf(), i); + Assert.That(r->left, Is.EqualTo(10)); + Assert.That(r->top, Is.EqualTo(11)); + Assert.That(r->right, Is.EqualTo(12)); + Assert.That(r->bottom, Is.EqualTo(13)); + } } } \ No newline at end of file diff --git a/UnitTests/PInvoke/IpHlpApi/NetIOApiTests.cs b/UnitTests/PInvoke/IpHlpApi/NetIOApiTests.cs index 8d7ad610..156bc6e2 100644 --- a/UnitTests/PInvoke/IpHlpApi/NetIOApiTests.cs +++ b/UnitTests/PInvoke/IpHlpApi/NetIOApiTests.cs @@ -117,6 +117,90 @@ namespace Vanara.PInvoke.Tests t.Any(tr => tr.Address.Ipv4.sin_addr == r.Address.Ipv4.sin_addr && tr.InterfaceIndex == r.InterfaceIndex && tr.PhysicalAddress.SequenceEqual(r.PhysicalAddress)); } + [Test] + public unsafe void CreateSetDeleteIpNetEntry2UnmanagedPointerTest() + { + var target = new IN_ADDR(192, 168, 0, 202); + Assert.That(GetBestRoute(target, 0, out var fwdRow), ResultIs.Successful); + var mibrow = new MIB_IPNET_ROW2(new SOCKADDR_IN(target), fwdRow.dwForwardIfIndex, SendARP(target)); + Assert.That(GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t1), ResultIs.Successful); + if (HasVal(t1.AsUnmanagedArrayPointer(), mibrow, t1.NumEntries)) + Assert.That(DeleteIpNetEntry2(ref mibrow), ResultIs.Successful); + + Assert.That(CreateIpNetEntry2(ref mibrow), ResultIs.Successful); + GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t2); + Assert.That(HasVal(t2.AsUnmanagedArrayPointer(), mibrow, t1.NumEntries), Is.True); + + Assert.That(DeleteIpNetEntry2(ref mibrow), ResultIs.Successful); + GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t3); + Assert.That(HasVal(t3.AsUnmanagedArrayPointer(), mibrow, t1.NumEntries), Is.False); + + static bool HasVal(MIB_IPNET_ROW2_Unmanaged* tr, MIB_IPNET_ROW2 r, uint numEntries) + { + for (uint i = 0; i < numEntries; i++, tr++) + { + if (tr->Address.Ipv4.sin_addr == r.Address.Ipv4.sin_addr && + tr->InterfaceIndex == r.InterfaceIndex && + CompareArrays(tr->PhysicalAddress, r.PhysicalAddress)) + { + return true; + } + } + + return false; + } + + static bool CompareArrays(byte* left, byte[] right) + { + return !right.Where((t, i) => left[i] != t).Any(); + } + } + + [Test] + public void CreateSetDeleteIpNetEntry2UnmanagedSpanTest() + { + var target = new IN_ADDR(192, 168, 0, 202); + Assert.That(GetBestRoute(target, 0, out var fwdRow), ResultIs.Successful); + var mibrow = new MIB_IPNET_ROW2(new SOCKADDR_IN(target), fwdRow.dwForwardIfIndex, SendARP(target)); + Assert.That(GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t1), ResultIs.Successful); + if (HasVal(ref t1, mibrow)) + Assert.That(DeleteIpNetEntry2(ref mibrow), ResultIs.Successful); + + Assert.That(CreateIpNetEntry2(ref mibrow), ResultIs.Successful); + GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t2); + Assert.That(HasVal(ref t2, mibrow), Is.True); + + Assert.That(DeleteIpNetEntry2(ref mibrow), ResultIs.Successful); + GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t3); + Assert.That(HasVal(ref t3, mibrow), Is.False); + + static bool HasVal(ref MIB_IPNET_TABLE2_Unmanaged t, MIB_IPNET_ROW2 r) + { + foreach (ref MIB_IPNET_ROW2_Unmanaged tr in t) + { + unsafe + { + fixed (byte* physicalAddress = tr.PhysicalAddress) + { + if (tr.Address.Ipv4.sin_addr == r.Address.Ipv4.sin_addr && + tr.InterfaceIndex == r.InterfaceIndex && + CompareArrays(physicalAddress, r.PhysicalAddress)) + { + return true; + } + } + } + } + + return false; + } + + static unsafe bool CompareArrays(byte* left, byte[] right) + { + return !right.Where((t, i) => left[i] != t).Any(); + } + } + [Test] public void CreateSetGetDeleteUnicastIpAddressEntryTest() {