Reading ARP entries with C#

Recently, I needed to locate a device on my network using ARP (Address Resolution Protocol). I could have written code that directly uses the protocol to discover the device, by essentially probing a range of addresses on my network until it found the right one. But, that proved to be too much work. So, I decided to use Window’s arp command line tool.

To demonstrate the arp command, open a command prompt, type the following, and then press enter:

arp -a

This is a simple way to display a list of devices and their associated MAC addresses. The goal of my C# program would be to invoke this command and parse the output.

To begin, I wrote this simple method:

public static StreamReader ExecuteCommandLine(String file, String arguments = "") {
    ProcessStartInfo startInfo = new ProcessStartInfo();
    startInfo.CreateNoWindow = true;
    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
    startInfo.UseShellExecute = false;
    startInfo.RedirectStandardOutput = true;
    startInfo.FileName = file;
    startInfo.Arguments = arguments;

    Process process = Process.Start(startInfo);

    return process.StandardOutput;
}

Given the name of a program to execute, and a list of arguments to pass to that program, the above function returns the StreamReader associated with the output of the program. The next step is to parse the output and find the device you want:

var arpStream = Utility.ExecuteCommandLine(“arp”, “-a”);

// Consume first three lines
for (int i = 0; i < 3; i++) { arpStream.ReadLine(); } // Read entries while (!arpStream.EndOfStream) { var line = arpStream.ReadLine().Trim(); while (line.Contains(" ")) { line = line.Replace(" ", " "); } var parts = line.Split(' '); // Match device's MAC address if (parts[1].Replace("-", "").ToUpper().Trim() == "UPPERCASE_MAC_ADDRESS_TO_FIND_GOES_HERE") { // Your device is on the network // Access its IP address with: parts[0].Trim() } } [/csharp] The first line makes use of the function I defined above to execute the arp command and get its standard output stream. If you've tried the arp command out for yourself, you will notice that the first three lines are garbage. The next three lines of code account for this by advancing past the garbage lines. Finally, I iterate over each entry in the table and try to match the given MAC address. If the MAC address is found, you can fetch the IP address of the associated device. Please note that this code has no error checking. I'm not sure, for example, what would happen if the arp tool lists no devices, or if the user doesn't have administrative privileges.

Join the Conversation

4 Comments

  1. Also you are passing back a stream object and need to make sure you dispose of it properly. Best to have your helper function wrap access in a using statement and extract the stream contents fully to a string and return the string.

  2. Thx for posting this….No to keen on the command line parsing but I havn’t found any other way to get the information.

  3. Should be easy enough to convert to C#

    Imports System.Runtime.InteropServices
    Imports System.ComponentModel
    Imports System.Net

    Module GetIpNetTable
    ‘ The max number of physical addresses.
    Const MAXLEN_PHYSADDR As Integer = 8

    ‘ Define the MIB_IPNETROW structure.
    _
    Private Structure MIB_IPNETROW
    Public dwIndex As Integer
    Public dwPhysAddrLen As Integer
    Public mac0 As Byte
    Public mac1 As Byte
    Public mac2 As Byte
    Public mac3 As Byte
    Public mac4 As Byte
    Public mac5 As Byte
    Public mac6 As Byte
    Public mac7 As Byte
    Public dwAddr As Integer
    Public dwType As Integer
    End Structure

    ‘ Declare the GetIpNetTable function.
    _
    Public Function GetIpNetTable(pIpNetTable As IntPtr, ByRef pdwSize As Integer, bOrder As Boolean) As Integer
    End Function

    _
    Public Function FreeMibTable(plpNetTable As IntPtr) As Integer
    End Function

    ‘ The insufficient buffer error.
    Const ERROR_INSUFFICIENT_BUFFER As Integer = 122

    Public Sub Main(args As String())
    ‘ The number of bytes needed.
    Dim bytesNeeded As Integer = 0

    ‘ The result from the API call.
    Dim result As Integer = GetIpNetTable(IntPtr.Zero, bytesNeeded, False)

    ‘ Call the function, expecting an insufficient buffer.
    If result ERROR_INSUFFICIENT_BUFFER Then
    ‘ Throw an exception.
    Throw New Win32Exception(result)
    End If

    ‘ Allocate the memory, do it in a try/finally block, to ensure
    ‘ that it is released.
    Dim buffer As IntPtr = 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, bytesNeeded, False)

    ‘ If the result is not 0 (no error), then throw an exception.
    If result 0 Then
    ‘ Throw an exception.
    Throw New Win32Exception(result)
    End If

    ‘ Now we have the buffer, we have to marshal it. We can read
    ‘ the first 4 bytes to get the length of the buffer.
    Dim entries As Integer = Marshal.ReadInt32(buffer)

    ‘ Increment the memory pointer by the size of the int.
    Dim currentBuffer As New IntPtr(buffer.ToInt64() + Marshal.SizeOf(GetType(Integer)))

    ‘ Allocate an array of entries.
    Dim table As MIB_IPNETROW() = New MIB_IPNETROW(entries – 1) {}

    ‘ Cycle through the entries.
    For index As Integer = 0 To entries – 1
    ‘ Call PtrToStructure, getting the structure information.
    table(index) = CType(Marshal.PtrToStructure(New IntPtr(currentBuffer.ToInt64() + (index * Marshal.SizeOf(GetType(MIB_IPNETROW)))), GetType(MIB_IPNETROW)), MIB_IPNETROW)
    Next

    For index As Integer = 0 To entries – 1
    Dim row As MIB_IPNETROW = table(index)
    Dim ip As New IPAddress(BitConverter.GetBytes(row.dwAddr))
    Dim mac As String = “{0}-{1}-{2}-{3}-{4}-{5}”

    With row
    mac = String.Format(mac, .mac0.ToString(“X2”), .mac1.ToString(“X2”), .mac2.ToString(“X2”), .mac3.ToString(“X2”), .mac4.ToString(“X2”), .mac5.ToString(“X2”))
    End With

    Console.Write(“IP:” + ip.ToString() + vbTab & vbTab & “MAC:” & mac)

    Next
    Catch ex As Exception

    ‘send the error to the masses
    Console.ForegroundColor = ConsoleColor.Red
    Console.WriteLine(ex.Message)
    Finally
    ‘ Release the memory.
    FreeMibTable(buffer)
    End Try

    ‘if we are debugging, give me time to see the output
    If Debugger.IsAttached Then
    Console.WriteLine(“press any key to continue.”)
    Console.Read()
    End If

    End Sub

    End Module

Leave a comment

Leave a Reply