Friday, 18 November 2011

Determine which NIC card will be used to route a specific IP address

I tried to find an easy way in C# to determine which network interface card was going to be used if I had to connect to a specific address. This is done by the system with the help of the routing table.

The only way I found was to use the unmanaged api of Windows. I am using the GetBestInterface() function from the iphlpapi.dll. This function returns the index that will be used to route the specified IP address. You must then use the GetAdaptersInfo() function from the same DLL to retrieve the network card info and determine which one corresponds to this index.

This works well but note that you cannot use IPv6 address with the GetBestInterface(). On the other hand, the GetBestInterfaceEx() supports IPv6 but I was not able to make it work with C#.

Error handling is missing and I only tested this on Vista 64 machine but it might be helpful for others:

You can get the code from here: http://pastebin.com/u9159Ys8

public class NetworkInterfaceHelper
{
    private static readonly Dictionary<string, NetworkInterface> m_networkInterfaces;

    static NetworkInterfaceHelper()
    {
        m_networkInterfaces = NetworkInterface.GetAllNetworkInterfaces().ToDictionary(o => o.Id);
    }
     
    public static NetworkInterface GetBestInterface(IPAddress address)
    {
        byte[] byteArray1 = address.GetAddressBytes();
        uint ipaddr = BitConverter.ToUInt32(byteArray1, 0);
        uint interfaceIndex;
        int error = IPHelperInvoke.GetBestInterface(ipaddr, out interfaceIndex);

        if (error != 0)
        {
            throw new InvalidOperationException(string.Format("Error while calling GetBestInterface(). Error={0}", error));
        }
         
        var indexedIpAdapterInfo = AdapterInfo.IndexedIpAdapterInfos[interfaceIndex];

        return m_networkInterfaces[indexedIpAdapterInfo.AdapterName];
    }
}
 
public class AdapterInfo
{
    public static IEnumerable<IPHelperInvoke.IP_ADAPTER_INFO> IpAdapterInfos { get; private set; }
    public static Dictionary<uint, IPHelperInvoke.IP_ADAPTER_INFO> IndexedIpAdapterInfos { get; private set; }
     
     
    static AdapterInfo()
    {
        IpAdapterInfos = RetrieveAdapters();
        IndexedIpAdapterInfos = IpAdapterInfos.ToDictionary(o => (uint)o.Index);
    }

    private static IEnumerable<IPHelperInvoke.IP_ADAPTER_INFO> RetrieveAdapters()
    {
        long structSize = Marshal.SizeOf(typeof(IPHelperInvoke.IP_ADAPTER_INFO));
        IntPtr pArray = Marshal.AllocHGlobal(new IntPtr(structSize));

        int ret = IPHelperInvoke.GetAdaptersInfo(pArray, ref structSize);
         
        IntPtr pEntry = pArray;
        if (ret == IPHelperInvoke.ERROR_BUFFER_OVERFLOW)
        {
            // Buffer was too small, reallocate the correct size for the buffer.
            pArray = Marshal.ReAllocHGlobal(pArray, new IntPtr(structSize));
            ret = IPHelperInvoke.GetAdaptersInfo(pArray, ref structSize);
        }

        List<IPHelperInvoke.IP_ADAPTER_INFO> result = new List<IPHelperInvoke.IP_ADAPTER_INFO>();

        if (ret == 0)
        {
            do
            {
                // Retrieve the adapter info from the memory address
                IPHelperInvoke.IP_ADAPTER_INFO entry = (IPHelperInvoke.IP_ADAPTER_INFO)Marshal.PtrToStructure(pEntry, typeof(IPHelperInvoke.IP_ADAPTER_INFO));

                result.Add(entry);

                // Get next adapter (if any)
                pEntry = entry.Next;
            }
            while (pEntry != IntPtr.Zero);

            Marshal.FreeHGlobal(pArray);
        }
        else
        {
            Marshal.FreeHGlobal(pArray);
        }

        return result;
    }
}

public class IPHelperInvoke
{
    public const int MAX_ADAPTER_ADDRESS_LENGTH = 8;

    public const int MAX_ADAPTER_DESCRIPTION_LENGTH = 128;

    public const int MAX_ADAPTER_NAME_LENGTH = 256;

    public const int ERROR_BUFFER_OVERFLOW = 111;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct IP_ADDRESS_STRING
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string Address;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct IP_ADDR_STRING
    {
        public IntPtr Next;
        public IP_ADDRESS_STRING IpAddress;
        public IP_ADDRESS_STRING IpMask;
        public Int32 Context;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct IP_ADAPTER_INFO
    {
        public IntPtr Next;
        public Int32 ComboIndex;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ADAPTER_NAME_LENGTH + 4)]
        public string AdapterName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ADAPTER_DESCRIPTION_LENGTH + 4)]
        public string AdapterDescription;
        public UInt32 AddressLength;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_ADAPTER_ADDRESS_LENGTH)]
        public byte[] Address;
        public Int32 Index;
        public UInt32 Type;
        public UInt32 DhcpEnabled;
        public IntPtr CurrentIpAddress;
        public IP_ADDR_STRING IpAddressList;
        public IP_ADDR_STRING GatewayList;
        public IP_ADDR_STRING DhcpServer;
        public bool HaveWins;
        public IP_ADDR_STRING PrimaryWinsServer;
        public IP_ADDR_STRING SecondaryWinsServer;
        public Int32 LeaseObtained;
        public Int32 LeaseExpires;
    }

    [DllImport("iphlpapi.dll", CharSet = CharSet.Ansi)]
    public static extern int GetAdaptersInfo(IntPtr pAdapterInfo, ref Int64 pBufOutLen);

    [DllImport("iphlpapi.dll", SetLastError = true)]
    public static extern int GetBestInterface(UInt32 DestAddr, out UInt32 BestIfIndex);
}


Thanks PInvoke.net for the samples!


Tuesday, 1 November 2011


Struct to byte array example:


    class Program
    {
        internal struct Header
        {
            public int Size;
            public byte Checksum;
        }

        internal struct Packet
        {
            public Header Header;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public byte[] Data;
        }
   
        static void Main(string[] args)
        {
            // --------------------------
            // Init
            // --------------------------
            Packet packet = new Packet
            {
                Header = new Header
                {
                    Size = 8,
                    Checksum = 9
                },
                Data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }
            };

            // --------------------------
            // Convert to bytes
            // --------------------------
            // Convert to "8 0 0 0 9 0 0 0 0 1 2 3 4 5 6 7"
            byte[] buffer = RawSerialize(packet);

            // --------------------------
            // Convert to object
            // --------------------------
            Packet newPacket = (Packet)RawDeserialize(buffer, 0, typeof(Packet));
        }

        public static object RawDeserialize(byte[] rawData, int position, Type anyType)
        {
            int rawsize = Marshal.SizeOf(anyType);
            if (rawsize > rawData.Length)
                return null;

            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            Marshal.Copy(rawData, position, buffer, rawsize);
            object retobj = Marshal.PtrToStructure(buffer, anyType);
            Marshal.FreeHGlobal(buffer);
            return retobj;
        }

        public static byte[] RawSerialize(object anything)
        {
            int rawSize = Marshal.SizeOf(anything);
            IntPtr buffer = Marshal.AllocHGlobal(rawSize);
            Marshal.StructureToPtr(anything, buffer, false);
            byte[] rawDatas = new byte[rawSize];
            Marshal.Copy(buffer, rawDatas, 0, rawSize);
            Marshal.FreeHGlobal(buffer);
            return rawDatas;
        }
    }