31

I try to figure out which devices are online and which are offline in our LAN.
I have seen many programs doing a kind of graphical network overview, presenting LAN IP and MAC addresses.

I would like to know if and how those (ARP?) information can be pulled from C#/.NET ?

Community
  • 1
  • 1
BerggreenDK
  • 4,915
  • 9
  • 39
  • 61
  • The data may, I don't know, be available via SNMP. – ChrisW Jul 19 '09 at 01:06
  • How are you defining LAN? Ethernet segment? Everything in an IP block? – Michael Donohue Jul 20 '09 at 16:08
  • I define LAN as local Ethernet, seen from "my network card" - I want to have a service/dll (something) that I can call from eg. a webserver or something that will report which IP's are active in the current IP segment (without pinging all combinations) and then get the MAC for each active IP to lookup WHAT is connected (which would allow us to do log/visualize the current network easily.) – BerggreenDK Jul 21 '09 at 03:32
  • If your device is a server of some type then you will see most of the rest of the network. However because of network switches your machine may only have ARP entries for device you communicate with. If you have some known IP then use the ping class to fill up the ARP table. – Rex Logan Jul 21 '09 at 04:09

4 Answers4

36

If you know which devices are out there you can use the Ping Class. This will allow you to at least fill up the ARP table. You can always execute ARP -a and parse the output if you have to. Here is also a link that shows how to pinvoke to call GetIpNetTable. I have included examples below of Ping Class and how to access the ARP table using the GetIpNetTable.

This is an example for the Ping Class

using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;

namespace Examples.System.Net.NetworkInformation.PingTest
{
    public class PingExample
    {
        // args[0] can be an IPaddress or host name.
        public static void Main (string[] args)
        {
            Ping pingSender = new Ping ();
            PingOptions options = new PingOptions ();

            // Use the default Ttl value which is 128,
            // but change the fragmentation behavior.
            options.DontFragment = true;

            // Create a buffer of 32 bytes of data to be transmitted.
            string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
            byte[] buffer = Encoding.ASCII.GetBytes (data);
            int timeout = 120;
            PingReply reply = pingSender.Send (args[0], timeout, buffer, options);
            if (reply.Status == IPStatus.Success)
            {
                Console.WriteLine ("Address: {0}", reply.Address.ToString ());
                Console.WriteLine ("RoundTrip time: {0}", reply.RoundtripTime);
                Console.WriteLine ("Time to live: {0}", reply.Options.Ttl);
                Console.WriteLine ("Don't fragment: {0}", reply.Options.DontFragment);
                Console.WriteLine ("Buffer size: {0}", reply.Buffer.Length);
            }
        }
    }
}

This is an example of the GetIpNetTable.

using System;
using System.Runtime.InteropServices;
using System.ComponentModel; 
using System.Net;

namespace GetIpNetTable
{
   class Program
   {
      // The max number of physical addresses.
      const int MAXLEN_PHYSADDR = 8;

      // Define the MIB_IPNETROW structure.
      [StructLayout(LayoutKind.Sequential)]
      struct MIB_IPNETROW
      {
         [MarshalAs(UnmanagedType.U4)]
         public int dwIndex;
         [MarshalAs(UnmanagedType.U4)]
         public int dwPhysAddrLen;
         [MarshalAs(UnmanagedType.U1)]
         public byte mac0;
         [MarshalAs(UnmanagedType.U1)]
         public byte mac1;
         [MarshalAs(UnmanagedType.U1)]
         public byte mac2;
         [MarshalAs(UnmanagedType.U1)]
         public byte mac3;
         [MarshalAs(UnmanagedType.U1)]
         public byte mac4;
         [MarshalAs(UnmanagedType.U1)]
         public byte mac5;
         [MarshalAs(UnmanagedType.U1)]
         public byte mac6;
         [MarshalAs(UnmanagedType.U1)]
         public byte mac7;
         [MarshalAs(UnmanagedType.U4)]
         public int dwAddr;
         [MarshalAs(UnmanagedType.U4)]
         public int dwType;
      }

      // Declare the GetIpNetTable function.
      [DllImport("IpHlpApi.dll")]
      [return: MarshalAs(UnmanagedType.U4)]
      static extern int GetIpNetTable(
         IntPtr pIpNetTable,
         [MarshalAs(UnmanagedType.U4)]
         ref int pdwSize,
         bool bOrder);

      [DllImport("IpHlpApi.dll", SetLastError = true, CharSet = CharSet.Auto)]
      internal static extern int FreeMibTable(IntPtr plpNetTable);

      // The insufficient buffer error.
      const int ERROR_INSUFFICIENT_BUFFER = 122;

      static void Main(string[] args)
      {
         // The number of bytes needed.
         int bytesNeeded = 0;

         // The result from the API call.
         int result = GetIpNetTable(IntPtr.Zero, ref bytesNeeded, false);

         // Call the function, expecting an insufficient buffer.
         if (result != ERROR_INSUFFICIENT_BUFFER)
         {
            // Throw an exception.
            throw new Win32Exception(result);
         }

         // Allocate the memory, do it in a try/finally block, to ensure
         // that it is released.
         IntPtr buffer = IntPtr.Zero;

         // Try/finally.
         try
         {
            // Allocate the memory.
            buffer = Marshal.AllocCoTaskMem(bytesNeeded);

            // Make the call again. If it did not succeed, then
            // raise an error.
            result = GetIpNetTable(buffer, ref bytesNeeded, false);

            // If the result is not 0 (no error), then throw an exception.
            if (result != 0)
            {
               // Throw an exception.
               throw new Win32Exception(result);
            }

            // Now we have the buffer, we have to marshal it. We can read
            // the first 4 bytes to get the length of the buffer.
            int entries = Marshal.ReadInt32(buffer);

            // Increment the memory pointer by the size of the int.
            IntPtr currentBuffer = new IntPtr(buffer.ToInt64() +
               Marshal.SizeOf(typeof(int)));

            // Allocate an array of entries.
            MIB_IPNETROW[] table = new MIB_IPNETROW[entries];

            // Cycle through the entries.
            for (int index = 0; index < entries; index++)
            {
               // Call PtrToStructure, getting the structure information.
               table[index] = (MIB_IPNETROW) Marshal.PtrToStructure(new
                  IntPtr(currentBuffer.ToInt64() + (index *
                  Marshal.SizeOf(typeof(MIB_IPNETROW)))), typeof(MIB_IPNETROW));
            }

            for (int index = 0; index < entries; index++)
            {
               MIB_IPNETROW row = table[index];
               IPAddress ip=new IPAddress(BitConverter.GetBytes(row.dwAddr));
               Console.Write("IP:"+ip.ToString()+"\t\tMAC:");

               Console.Write( row.mac0.ToString("X2") + '-');
               Console.Write( row.mac1.ToString("X2") + '-');
               Console.Write( row.mac2.ToString("X2") + '-');
               Console.Write( row.mac3.ToString("X2") + '-');
               Console.Write( row.mac4.ToString("X2") + '-');
               Console.WriteLine( row.mac5.ToString("X2"));

            }
         }
         finally
         {
            // Release the memory.
            FreeMibTable(buffer);
         }
      }
   }
}
DrBB
  • 133
  • 6
Rex Logan
  • 26,248
  • 10
  • 35
  • 48
  • Sorry, but the idea was to discover which machines are online currently. We dont know the IP at the moment they connect, as they might come from wireless and get the IP via DHCP. I know that we could lock the routers, but the idea was to look for the MAC address from the ARP + gather the current list of devices attached to the network. – BerggreenDK Jul 19 '09 at 14:45
  • Do you know how to get the MAC address from an IP-number? – BerggreenDK Jul 19 '09 at 14:46
  • Great stuff! This I gotta test out. I have marked it as answer now. Thanks a lot! Brings me forward... "one giant leap" :o) – BerggreenDK Jul 21 '09 at 03:14
  • The second example isn't correct. If `dwAddr` is a negative number (when treated as a signed int), `new IPAddress` will fail. To fix it: `IPAddress ip=new IPAddress( BitConverter.GetBytes(table[index].dwAddr) );`. – GSerg Oct 30 '11 at 11:40
  • Better than using BitConverter.GetBytes, just cast to uint. – Nathan Phillips Nov 25 '11 at 12:12
  • FYI I've added a VB.NET version of the GetIpNetTable code [here](http://stackoverflow.com/a/10792370/256431). – Mark Hurd May 30 '12 at 15:08
  • 2
    Use **static extern int FreeMibTable(IntPtr pIpNetTable);** instead of **Marshal.FreeCoTaskMem(pIpNetTable);** Let the unmanaged code free the unmanaged memory it allocated. – Jesse Chisholm Apr 04 '13 at 14:33
  • @JesseChisholm your comment about managing is surely correct. But Win-XP doesn't support `FreeMibTable`... so I'm using `Marshal.FreeCoTaskMem`. if you know a better way for it I'll be glad – Eli Aug 14 '14 at 15:30
  • @Eli - FreeMibtable is in the IPHLPAPI.DLL, so would have to be imported and used via P/Invoke. Put `[DllImport("iphlpapi.dll")]` before the `static extern int FreeMibTable(IntPtr pIpNetTable);` – Jesse Chisholm Dec 16 '14 at 16:40
  • @JesseChisholm many thanks for the response. Please see: http://msdn.microsoft.com/en-us/library/windows/desktop/aa814408%28v=vs.85%29.aspx *"The FreeMibTable function is defined on Windows Vista and later."* – Eli Dec 28 '14 at 16:08
  • 1
    @Eli - re: **But Win-XP doesn't support `FreeMibTable`.** - I didn't know we were talking about Vista and later. :) – Jesse Chisholm Dec 30 '14 at 14:19
  • @JesseChisholm do you think I would have a memory leak because I'm using `Marshal.FreeCoTaskMem`? (in Win-XP) – Eli Dec 31 '14 at 08:35
  • @Eli - That's just it, I _don't_ know. It just seems a little hinky to use one system for allocation and another for freeing. Perhaps you can find a way to use Marshal.AllocCoTaskMem or some such. – Jesse Chisholm Jan 02 '15 at 16:45
  • @JesseChisholm but that's exactly the code above: `Marshal.AllocCoTaskMem` with my XP-solution of `Marshal.FreeCoTaskMem` – Eli Jan 04 '15 at 09:00
  • ;-D it does now! My comment was in April, and the edit of the code was in October. I'm just saying that `AllocCoTaskMem` is balanced by `FreeCoTaskMem`, and `GetIpNetTable2` is balanced by `FreeMibTable`. And yes, some of the GetIpNet*** methods allocate memory. See the example code in http://msdn.microsoft.com/en-us/library/windows/desktop/aa814420(v=vs.85).aspx – Jesse Chisholm Jan 05 '15 at 19:06
3

Hopefully you are trying to get the MAC Addresses from a IP Addresses and not the other way around.

Here is a link of a guy's example:

ARP Resolver

I have not tried it, let us know how it works.

jonathanpeppers
  • 26,115
  • 21
  • 99
  • 182
  • Thanks for the link, but would that example not need the following: using Tamir.IPLib; using Tamir.IPLib.Packets; using Tamir.IPLib.Util; ??? – BerggreenDK Jul 21 '09 at 03:16
  • I am also trying to find out how/if its possible to make a "C#" version of commandprompt "arp -a" ... not by calling a hidden commandprompt, but merly by doing the ARP command by code somehow. As I've understood so far, the ARP command lists the current available IP+their MAC addresses seen from "this network card"... and that would suit our needs perfectly. – BerggreenDK Jul 21 '09 at 03:29
  • 1
    The ARP command is sending raw bytes over a socket to accomplish this. The class in the link can resolve a MAC Address from an IP Address, is this what you need, or do you need to somehow "discover" IP Addresses? The using statements at the top come from SharpPcap.dll which is downloadable and open source from the link I posted above. – jonathanpeppers Jul 21 '09 at 13:13
  • 1
    I also wanted to mention that the ARP table stored with your network card is not necessarily up to date all of the time. It can refresh randomly and transparently before sending TCP/IP traffic. Depending on what you're trying to do, there might be a better way to accomplish it. – jonathanpeppers Jul 21 '09 at 13:16
  • Well, first of all I want to discover what Network cards are "online" in the same IP segment as the "service I am trying to construct". When I have a list of the active IP's or those communicating on the network, I want to lookup their respective MAC numbers to keep some sort of "simple" validation of who has which IP right now. Its not a safe solution I know, but I want to make a dynamic list of MAC-numbers/users/machines online currently, so the servers can activate certain "tunnels" or "services" accordingly and shut the rest down. – BerggreenDK Jul 24 '09 at 21:34
  • 1
    Link provided has died – BerggreenDK Nov 21 '18 at 13:28
3

I had a similar problem and wanted to get MAC addresses, given IP addresses for an Asp.Net Core project. I wanted this to work on windows and linux too. As I found no easy to use solution I decided to create a small library called ArpLookup myself (NuGet).

It is able to assign macs to ips on windows and linux. On windows it uses the IpHlpApi.SendARP api. On linux it reads the arp table from /proc/net/arp. If it does not find the ip, it tries pinging it (to froce the OS doing the arp request) and looks in the arp cache again afterwards. This works without taking any dependencies (managed or unmanaged) and without starting processes and parsing their stdout etc..

The windows version is not async, as the underlying API isn't. As the linux version is truly async (async file io for the arp cache + corefx async ping api) I decided to provide an async api anyways and return a finished Task on windows.

It's quite easy to use. A real world usage example is available here.


This is an excerpt of the ARP lookup on windows to map IP -> MAC address:

internal static class ArpLookupService
{
    /// <summary>
    /// Call ApHlpApi.SendARP to lookup the mac address on windows-based systems.
    /// </summary>
    /// <exception cref="Win32Exception">If IpHlpApi.SendARP returns non-zero.</exception>
    public static PhysicalAddress Lookup(IPAddress ip)
    {
        if (ip == null)
            throw new ArgumentNullException(nameof(ip));

        int destIp = BitConverter.ToInt32(ip.GetAddressBytes(), 0);

        var addr = new byte[6];
        var len = addr.Length;

        var res = NativeMethods.SendARP(destIp, 0, addr, ref len);

        if (res == 0)
            return new PhysicalAddress(addr);
        throw new Win32Exception(res);
    }

    private static class NativeMethods
    {
        private const string IphlpApi = "iphlpapi.dll";

        [DllImport(IphlpApi, ExactSpelling = true)]
        [SecurityCritical]
        internal static extern int SendARP(int destinationIp, int sourceIp, byte[] macAddress, ref int physicalAddrLength);
    }
}

The code achieving the same on Linux can be found here. My above-linked library adds a thin abstraction layer that provides one single cross-platform method to do arp lookups like these.

Georg Jung
  • 949
  • 10
  • 27
  • 1
    Yes I do. I'm using this "in production" for the WoL tool at my workplace. See https://github.com/georg-jung/BlazorWoL. I'm also commited to versioning it according to SemVer and there is a rather complete DevOps lifecycle. Theres not much active development going on because I consider this project feature complete, given it's limited and quite specific scope. Still, kind of coincidentally, the latest commit is just about 3h ago, because I do keep up with updated dev dependencies etc.. If theres anything I can improve feel free to open an issue/send a PR. – Georg Jung Apr 28 '20 at 12:25
  • Are you using windows or linux? Feel free to open an issue with a stacktrace and I'll look into if it's possible to change that. – Georg Jung May 04 '20 at 21:00
  • Thanks; I guess you're right. In the linux case I'd have had some ideas how to bulk things. – Georg Jung May 04 '20 at 22:10
  • 1
    Probably you're right and a few people are just looking for some code. Added the windows part above. – Georg Jung May 05 '20 at 08:24
  • 1
    It's me again. My implementation was faulty. The windows version is perfectly working multy threaded. – marsh-wiggle May 06 '20 at 09:48
2

In my case, I wanted to see all ARP broadcast traffic on my network to detect devices broadcasting conflicting IP and MAC addresses on my network. I found "arp -a" polling implementations result in stale information, which makes it particularly challenging to detect IP address conflicts. For instance, two devices were responding to ARP requests, but as one response always arrived later, it would conceal the earlier response in the "arp -a" table.

I used SharpPcap to create a capture service with a capture filter for ARP traffic. Then I use Packet.Net to parse the ARP packets. Finally, I log and generate alerts about IP and MAC address conflicts as the packets come in.

Ben Adams
  • 121
  • 1
  • 5