This is a performance focused C# wrapper for iphlpapi.dll.
Works on .NET6
This could be done more efficiently in newer vesrions but I want to keep it compatible even with old versions of the language.
It was made for the purpose of retrieving all TCP/UDP connections of any type (process/module/basic, ipv6/ipv4) as fast as possible. Speed is a key goal.
There are two main types of functions: instance functions and static function. If the function involves an external call, it would most certainly need to allocate some memory. Instance functions use an already created buffer for this purpose. Static functions use stack allocated memory.
Stack allocated memory is generally faster on windows, so static functions are preferred over instance functions, but in some cases they behave faster only under specific conditions, static functions which are not recommended to use are hidden, using non-hidden static functions is fine.
Available functions and methods:
-
SetBufferSize(int newSize)Sets buffer size. Reallocates internal buffer for storing data between iphlpapi.dll and the wrapper.
-
GetTcpTable(AddressFamily networkType, TcpTableClass tcpTable, bool sortedOrder = false)Retrieves information about all tcp connections of any type (process/module/basic) in the specified network layer (ipv4/ipv6)
-
GetUdpTable(AddressFamily networkType, UdpTableClass udpTable, bool sortedOrder = false)Retrieves information about all udp connections of any type (process/module/basic) in the specified network layer (ipv4/ipv6)
-
GetIpNetTableRecords(bool sortedOrder = false)Retrieves information about all ip addresses in the network and their associated physical addresses
-
GetTcp4Connections(TcpTableClass tcpTable = TcpTableClass.BasicAll, bool sortedOrder = false)Retrieves information about all tcp ipv4 connections of any type (process/module/basic)
-
GetProcessTcp4Connections(TcpTableClass tcpTable = TcpTableClass.ProcessAll, bool sortedOrder = false)Retrieves information about all tcp ipv4 process connections (all/listener/connections)
-
GetModuleTcp4Connections(TcpTableClass tcpTable = TcpTableClass.ModuleAll, bool sortedOrder = false)Retrieves information about all tcp ipv4 module connections (all/listener/connections)
-
GetBasicTcp4Connections(TcpTableClass tcpTable = TcpTableClass.BasicAll, bool sortedOrder = false)Retrieves information about all tcp ipv4 basic connections (all/listener/connections)
-
GetTcp6Connections(TcpTableClass tcpTable = TcpTableClass.ProcessAll, bool sortedOrder = false)Retrieves information about all tcp ipv6 connections of any type (process/module/basic)
-
GetProcessTcp6Connections(TcpTableClass tcpTable = TcpTableClass.ProcessAll, bool sortedOrder = false)Retrieves information about all tcp ipv6 process connections (all/listener/connections)
-
GetModuleTcp6Connections(TcpTableClass tcpTable = TcpTableClass.ModuleAll, bool sortedOrder = false)Retrieves information about all tcp ipv6 module connections (all/listener/connections)
-
GetUdp4Connections(UdpTableClass udpTable = UdpTableClass.Basic, bool sortedOrder = false)Retrieves information about all udp ipv4 connections of any type (process/module/basic)
-
GetProcessUdp4Connections(UdpTableClass udpTable = UdpTableClass.Process, bool sortedOrder = false)Retrieves information about all udp ipv4 process connections
-
GetModuleUdp4Connections(UdpTableClass udpTable = UdpTableClass.Module, bool sortedOrder = false)Retrieves information about all udp ipv4 module connections
-
GetBasicUdp4Connections(UdpTableClass udpTable = UdpTableClass.Basic, bool sortedOrder = false)Retrieves information about all udp ipv4 basic connections
-
GetUdp6Connections(UdpTableClass udpTable = UdpTableClass.Basic, bool sortedOrder = false)Retrieves information about all udp ipv6 connections of any type (basic/module/process)
-
GetProcessUdp6Connections(UdpTableClass udpTable = UdpTableClass.Process, bool sortedOrder = false)Retrieves information about all udp ipv6 process connections
-
GetModuleUdp6Connections(UdpTableClass udpTable = UdpTableClass.Module, bool sortedOrder = false)Retrieves information about all udp ipv6 module connections
-
GetBasicUdp6Connections(UdpTableClass udpTable = UdpTableClass.Basic, bool sortedOrder = false)Retrieves information about all udp ipv6 basic connections
-
GetIpAddrTableRecords(bool sortedOrder = false)Retrieves information about network interfaces and their corresponding ipv4 addresses.
Available static functions (not recommended functions are not listed):
-
uint GetBestInterfaceIndex(uint address)/GetBestInterfaceIndex(IPAddress address)Retrieves best network interface address index to the target ipv4 address
-
NetworkInterface GetBestInterface4(IPAddress address, NetworkInterface[]? interfaces = null)Retrieves best network interface to the target ipv4 address
-
uint GetBestInterfaceIndexEx(IPAddress address)Retrieves best network interface address index to the target ipv4/ipv6 address
-
NetworkInterface GetBestInterface6(IPAddress address, NetworkInterface[]? interfaces = null)Retrieves best network inteface to the target ipv6 address
-
NetworkInterface GetBestInterface(IPAddress address, NetworkInterface[]? interfaces = null)Retrieves best network interface to the target ipv4/ipv6 address
-
byte[] SendARP(uint destionationAddress, uint sourceAddress = 0)Resolves PhysicalAddress (in byte[]) by sending ARP request to the target or by getting the PhysicalAddress from the ARP entries list
-
PhysicalAddress SendARP(IPAddress destAddress, IPAddress? srcAddress = null)Resolves PhysicalAddress by sending ARP request to the target or by getting the PhysicalAddress from the ARP entries list
Using freshly created wrapper:
using(IpHelpApiWrapper wrapper = new IpHelpApiWrapper())
{
Tcp4ProcessRecord[] tcp4ProcessRecordList = wrapper.GetProcessTcp4Connections();
Tcp4ModuleRecord[] tcp4ModuleRecordList = wrapper.GetModuleTcp4Connections();
Tcp4Record[] tcp4BasicRecordList = wrapper.GetBasicTcp4Connections();
Udp4ProcessRecord[] udp4ProcessRecordList = wrapper.GetProcessUdp4Connections();
Udp4ModuleRecord[] udp4ModuleRecordList = wrapper.GetModuleUdp4Connections();
Udp4Record[] udp4BasicRecordList = wrapper.GetBasicUdp4Connections();
Tcp6ProcessRecord[] tcp6ProcessRecordList = wrapper.GetProcessTcp6Connections();
Tcp6ModuleRecord[] tcp6ModuleRecordList = wrapper.GetModuleTcp6Connections();
Udp6ProcessRecord[] udp6ProcessRecordList = wrapper.GetProcessUdp6Connections();
Udp6ModuleRecord[] udp6ModuleRecordList = wrapper.GetModuleUdp6Connections();
PhysicalAddressRecord[] ipNetRecordList = wrapper.GetIpNetTableRecords();
tcp4ProcessRecordList = wrapper.GetTcpTable(AddressFamily.InterNetwork, TcpTableClass.ProcessConnections).Cast<Tcp4ProcessRecord>().ToArray();
tcp6ModuleRecordList = wrapper.GetTcpTable(AddressFamily.InterNetworkV6, TcpTableClass.ModuleConnections).Cast<Tcp6ModuleRecord>().ToArray();
}Using shared wrapper instance:
Tcp4ProcessRecord[] tcp4ProcessRecordList = IpHelpApiWrapper.Shared.GetProcessTcp4Connections();
Tcp4ModuleRecord[] tcp4ModuleRecordList = IpHelpApiWrapper.Shared.GetModuleTcp4Connections();
Tcp4Record[] tcp4BasicRecordList = IpHelpApiWrapper.Shared.GetBasicTcp4Connections();
Udp4ProcessRecord[] udp4ProcessRecordList = IpHelpApiWrapper.Shared.GetProcessUdp4Connections();
Udp4ModuleRecord[] udp4ModuleRecordList = IpHelpApiWrapper.Shared.GetModuleUdp4Connections();
Udp4Record[] udp4BasicRecordList = IpHelpApiWrapper.Shared.GetBasicUdp4Connections();
Tcp6ProcessRecord[] tcp6ProcessRecordList = IpHelpApiWrapper.Shared.GetProcessTcp6Connections();
Tcp6ModuleRecord[] tcp6ModuleRecordList = IpHelpApiWrapper.Shared.GetModuleTcp6Connections();
Udp6ProcessRecord[] udp6ProcessRecordList = IpHelpApiWrapper.Shared.GetProcessUdp6Connections();
Udp6ModuleRecord[] udp6ModuleRecordList = IpHelpApiWrapper.Shared.GetModuleUdp6Connections();
PhysicalAddressRecord[] ipNetRecordList = IpHelpApiWrapper.Shared.GetIpNetTableRecords();
tcp4ProcessRecordList = IpHelpApiWrapper.Shared.GetTcpTable(AddressFamily.InterNetwork, TcpTableClass.ProcessConnections).Cast<Tcp4ProcessRecord>().ToArray();
tcp6ModuleRecordList = IpHelpApiWrapper.Shared.GetTcpTable(AddressFamily.InterNetworkV6, TcpTableClass.ModuleConnections).Cast<Tcp6ModuleRecord>().ToArray();Using static functions:
//Retrieves main network interface
public static NetworkInterface GetMainNetworkInterface()
{
return IpHelpApiWrapper.GetBestInterface4(IPAddress.Any);
}
//Retrieves gateway's address
public static PhysicalAddress GetGatewayPhysicalAddress()
{
IPAddress targetAddress = IpHelpApiWrapper.GetBestInterface4(IPAddress.Any).GetIPProperties().GatewayAddresses.Last().Address;
PhysicalAddressRecord[] records = IpHelpApiWrapper.Shared.GetIpNetTableRecords(); // List of ip addresses and their physical addresses
PhysicalAddress? found = Array.Find(records, x => x.IpAddress.Equals(targetAddress) && (x.NetType != IpNetType.Invalid))?.PhysicalAddress;
if (found == null) throw new AggregateException("Gateway address was not found in the table");
return found;
}If you want to get some fixed type of record, you should get it directly (for example GetProcessTcp4Connections(), GetModuleUdp6Connections()) instead of GetTcpTable or GetUdpTable
Using LocalIPAddress or LocalEndPoint property of any record type internally converts LocalAddress, LocalPort, LocalScopeId into an IPAddress or IpEndPoint. This takes some computing power. If you are developing a high performance application which is, for example, have to compare IP address to the processes IP addresses in bulk, you should consider converting the IP address into uint and only then compare it with the processes IP addresses. Same applies to RemoteAddress and RemoteEndPoint
On wrapper creation, it either allocates a new byte array or borrows it from the array pool, if the array pool is specified in the constructor. After that this byte array is pinned and a pointer to it is stored. When the external call is made, this pointer is passed into the external call and this array is used for an external call. During this call, no other function should access the byte array, so it is locked. If the byte array was too small, class either throws an exception (if AutoResizeBuffer is set to false) or unpins and unhooks the old array and either reallocates or reborrows the new array.
Results of external calls are manually marshalled into classes, SpanByteConverter is an internal class for managing byte conversions.
External call's return codes are handled by the HandleErrorCode method.
Static functions use stack allocated memory. It's impossible to store this memory between calls since this memory gets discarded per function call. Stack allocated memory on windows is generally faster in allocation, reading and writing, so it's use should be preffered over the heap memory allocation. However, since we cant store this memory, we need to reallocate it on every call, which creates the problem that on bigger sizes (Should be above 2 kb I believe), the allocation time becomes too big and it's no longer faster than the heap-using functions. Also stack memory allocation is tricky so I decided to not implement auto-resize so they would throw an exception if the allocated memory is not big enough.
Above said only applies to BROWSER-HIDDEN functions, non browser-hidden functions are safe and fast to use.
Static function use internal (only for compiler use) function GetPinnableReference(), so if the dotnet removes it, it would be impossible to implmenet stack allocation.