如何通过.NET访问ARP协议信息?

31

我想找出我们局域网中哪些设备在线,哪些设备离线。
我见过许多程序展示了局域网的IP地址和MAC地址等图形化网络概述。

我想知道是否可以从C#/.NET中提取这些(ARP?)信息,以及如何提取?


数据可能通过SNMP获取,但我不确定。 - ChrisW
你是如何定义局域网的?以太网段?还是IP地址块中的所有内容? - Michael Donohue
我定义局域网为本地以太网,从“我的网络卡”中看到 - 我想要一个可以从Web服务器或其他东西调用的服务/ dll(某物),它将报告当前IP段中活动的IP是哪些(而无需ping所有组合),然后获取每个活动IP的MAC地址以查找连接的内容(这将使我们能够轻松记录/可视化当前网络)。 - BerggreenDK
如果您的设备是某种类型的服务器,则您将看到网络的大部分其余部分。但是,由于网络交换机的存在,您的计算机可能只有与您通信的设备的ARP条目。如果您有一些已知的IP地址,则可以使用ping类来填充ARP表。 - Rex Logan
4个回答

36
如果您知道哪些设备可用,可以使用Ping Class。 这将允许您至少填充ARP表。 如果必须执行ARP -a并解析输出,则始终可以执行此操作。 这里还有一个链接,展示如何使用pinvoke调用GetIpNetTable。 我已经包含了使用Ping Class的示例以及使用GetIpNetTable访问ARP表的方法。
这是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);
            }
        }
    }
}

这是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);
         }
      }
   }
}

比使用BitConverter.GetBytes更好的方法是直接将其转换为uint。 - Nathan Phillips
2
使用 static extern int FreeMibTable(IntPtr pIpNetTable); 代替 Marshal.FreeCoTaskMem(pIpNetTable); 让非托管代码释放它所分配的非托管内存。 - Jesse Chisholm
@JesseChisholm 非常感谢您的回复。请参阅:http://msdn.microsoft.com/en-us/library/windows/desktop/aa814408%28v=vs.85%29.aspx 。“FreeMibTable”函数在Windows Vista及更高版本中定义。 - Eli
1
@Eli - 关于 但Win-XP不支持FreeMibTable - 我不知道我们在谈论Vista及以后的版本。 :) - Jesse Chisholm
@Eli - 就是这样,我不知道。使用一个系统进行分配,另一个系统进行释放似乎有点奇怪。也许你可以找到一种方法来使用Marshal.AllocCoTaskMem或类似的东西。 - Jesse Chisholm
显示剩余10条评论

3
希望您是想从IP地址获取MAC地址,而不是反过来。这里有一个人的示例链接:ARP Resolver,我没有尝试过,请告诉我们它的工作原理。

谢谢提供链接,但这个例子不需要以下内容吗:using Tamir.IPLib; using Tamir.IPLib.Packets; using Tamir.IPLib.Util; - BerggreenDK
我也试图找出如何/是否可能制作一个“C#”版本的命令提示符“arp -a”...而不是通过调用隐藏的命令提示符,而是通过某种方式通过代码执行ARP命令。据我了解,ARP命令列出从“该网络适配器”看到的当前可用IP+它们的MAC地址...这将完全满足我们的需求。 - BerggreenDK
1
ARP命令通过套接字发送原始字节来实现此功能。链接中的类可以从IP地址解析MAC地址,这是您需要的吗?还是您需要以某种方式“发现”IP地址?顶部的using语句来自SharpPcap.dll,可以从我在上面发布的链接中下载并使用开源版本。 - jonathanpeppers
1
我还想提一下,存储在您的网络卡中的ARP表并不总是最新的。它可以在发送TCP/IP流量之前随机透明地刷新。根据您要做什么,可能有更好的方法来完成它。 - jonathanpeppers
首先,我想发现与我正在构建的“服务”处于同一IP段中的哪些网络卡是“在线”的。当我有一个活动IP地址或正在网络上通信的IP地址列表时,我想查找它们各自的MAC地址,以保持某种“简单”的验证,了解谁当前拥有哪个IP地址。我知道这不是一个安全的解决方案,但我想制作一个动态的MAC地址/用户/机器在线列表,以便服务器可以相应地激活某些“隧道”或“服务”,并关闭其余部分。 - BerggreenDK
1
提供的链接已失效。 - BerggreenDK

3
我曾遇到类似问题,想要在Asp.Net Core项目中给定IP地址获取MAC地址。我希望这个解决方案适用于Windows和Linux系统。由于没有找到易于使用的解决方案,我决定自己创建一个名为ArpLookup的小型库(NuGet)

它能够在Windows和Linux上将MAC地址分配给IP地址。在Windows上,它使用IpHlpApi.SendARP api。在Linux上,它从/proc/net/arp读取arp表。如果找不到IP地址,它会尝试ping它(强制操作系统进行arp请求),然后再次查看arp缓存。这样做无需任何依赖项(托管或非托管),也无需启动进程并解析其stdout等。

Windows版本不是异步的,因为底层API不是异步的。由于Linux版本是真正的异步(arp缓存的异步文件io + corefx异步ping api),所以我决定提供异步api,并在Windows上返回完成的Task。

这很容易使用。一个现实世界的用例示例在这里可用


这是关于在Windows上进行ARP查找以映射IP地址和MAC地址的摘录
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);
    }
}

这里可以找到在Linux上实现相同功能的代码here。我以上链接的库添加了一个薄的抽象层,提供了一个跨平台的方法来执行类似这样的arp查找。

1
是的,我在我的工作场所使用这个“生产中”的 WoL 工具。请参见 https://github.com/georg-jung/BlazorWoL。我还致力于根据 SemVer 进行版本控制,并且有一个相当完整的 DevOps 生命周期。由于我认为这个项目的范围有限且非常特定,因此没有太多的活跃开发。不过,最新的提交时间差不多是 3 小时前,因为我确实会跟上更新的 dev 依赖等。如果有什么可以改进的地方,请随时提出问题或发送 PR。 - Georg Jung
你是使用Windows还是Linux?如果有堆栈跟踪,请随时打开一个问题,我会研究一下是否可能更改它。 - Georg Jung
谢谢,我想你是对的。在Linux的情况下,我会有一些关于如何批量处理事物的想法。 - Georg Jung
1
也许你是对的,有些人只是在寻找一些代码。我已经在上面添加了Windows部分。 - Georg Jung
1
又是我。我的实现有问题。Windows 版本可以完美地支持多线程。 - marsh-wiggle

2
在我的情况下,我想要查看网络上所有ARP广播流量以检测在我的网络上广播冲突的IP和MAC地址的设备。我发现“arp -a”轮询实现会导致过时信息,这使得检测IP地址冲突尤为具有挑战性。例如,两个设备都响应ARP请求,但由于一个响应总是稍后到达,它将在“arp -a”表中隐藏较早的响应。
我使用SharpPcap创建了一个捕获服务,并使用捕获过滤器捕获ARP流量。然后我使用Packet.Net解析ARP数据包。最后,我在数据包到达时记录并生成有关IP和MAC地址冲突的警报。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接