Querying Windows DHCP Server With C#

I was working on a project recently which gave me cause to try and extract DHCP lease information from Microsoft Server using C#.

It didn’t sound too hard and I was sure there would be a way built in to the .NET framework to do what I wanted … however it turns out there isn’t!

What you can do though is use some ‘interop’ code, i.e. make .NET use some non-native libraries. In this case the functionality is available in dhcpsapi.dll.

I managed to find a few forum posts where people had done what I wanted but the code had no comments to speak of (and chunks of it were redundant) so it took me a long time to get it doing what I wanted.

The code below is the results of my labour, it’s now neater and fully commented! If you want you can cut and paste it into your own program and just use the function. The main portion of the program is just a few lines demonstrating its use.

Code

Download the code here.

I have tested this against Windows 2003 servers, I can’t guarantee the code on any other version!

using System;
using System.Runtime.InteropServices;
using System.Collections;
using System.Net;

namespace dhcp
{
    // c# class for processed clients

    public class dhcpClient
    {
        public string hostname { get; set; }
        public string ip       { get; set; }
        public string mac      { get; set; }
    }

    // structs for use with call to unmanaged code

    [StructLayout(LayoutKind.Sequential)]
    public struct DHCP_CLIENT_INFO_ARRAY
    {
        public uint NumElements;
        public IntPtr Clients;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct DHCP_CLIENT_UID
    {
        public uint DataLength;
        public IntPtr Data;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct DHCP_CLIENT_INFO
    {
        public uint ip;
        public uint subnet;

        public DHCP_CLIENT_UID mac;

        [MarshalAs(UnmanagedType.LPWStr)]
        public string ClientName;

        [MarshalAs(UnmanagedType.LPWStr)]
        public string ClientComment;
    }

    // main

    class Program
    {
        static void Main()
        {
            try
            {
                // get settings

                String server, subnet;

                Console.Write("Enter server : ");
                server = Console.ReadLine();
                Console.Write("Enter subnet : ");
                subnet = Console.ReadLine();

                // gather clients

                ArrayList clients = findDhcpClients(server, subnet);

                // output results

                Console.WriteLine();

                foreach (dhcpClient d in clients)
                    Console.WriteLine(String.Format("{0,-35} {1,-15} {2,-15}", d.hostname, d.ip, d.mac));

                Console.WriteLine('\n' + clients.Count.ToString() + " lease(s) in total");
            }

            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            Console.ReadLine();
        }

        static ArrayList findDhcpClients(string server, string subnet)
        {
            // set up container for processed clients

            ArrayList foundClients = new ArrayList();

            // make call to unmanaged code

            uint parsedMask     = StringIPAddressToUInt32(subnet);
            uint resumeHandle   = 0;
            uint numClientsRead = 0;
            uint totalClients   = 0;

            IntPtr info_array_ptr;

            uint response = DhcpEnumSubnetClients(
                server,
                parsedMask,
                ref resumeHandle,
                65536,
                out info_array_ptr,
                ref numClientsRead,
                ref totalClients
                );

            // set up client array casted to a DHCP_CLIENT_INFO_ARRAY
            // using the pointer from the response object above

            DHCP_CLIENT_INFO_ARRAY rawClients =
                (DHCP_CLIENT_INFO_ARRAY)Marshal.PtrToStructure(info_array_ptr, typeof(DHCP_CLIENT_INFO_ARRAY));

            // loop through the clients structure inside rawClients 
            // adding to the dchpClient collection

            IntPtr current = rawClients.Clients;

            for (int i = 0; i < (int)rawClients.NumElements; i++)
            {
                // 1. Create machine object using the struct

                DHCP_CLIENT_INFO rawMachine =
                    (DHCP_CLIENT_INFO)Marshal.PtrToStructure(Marshal.ReadIntPtr(current), typeof(DHCP_CLIENT_INFO));

                // 2. create new C# dhcpClient object and add to the 
                // collection (for hassle-free use elsewhere!!)

                dhcpClient thisClient = new dhcpClient();

                thisClient.ip = UInt32IPAddressToString(rawMachine.ip);

                thisClient.hostname = rawMachine.ClientName;

                thisClient.mac = String.Format("{0:x2}{1:x2}.{2:x2}{3:x2}.{4:x2}{5:x2}",
                    Marshal.ReadByte(rawMachine.mac.Data),
                    Marshal.ReadByte(rawMachine.mac.Data, 1),
                    Marshal.ReadByte(rawMachine.mac.Data, 2),
                    Marshal.ReadByte(rawMachine.mac.Data, 3),
                    Marshal.ReadByte(rawMachine.mac.Data, 4),
                    Marshal.ReadByte(rawMachine.mac.Data, 5));

                foundClients.Add(thisClient);

                // 3. move pointer to next machine

                current = (IntPtr)((int)current + (int)Marshal.SizeOf(typeof(IntPtr)));
            }

            return foundClients;
        }

        public static uint StringIPAddressToUInt32(string ip)
        {
            // convert string IP to uint IP e.g. "1.2.3.4" -> 16909060

            IPAddress i = System.Net.IPAddress.Parse(ip);
            byte[] ipByteArray = i.GetAddressBytes();

            uint ipUint = (uint)ipByteArray[0] << 24;
            ipUint += (uint)ipByteArray[1] << 16;
            ipUint += (uint)ipByteArray[2] << 8;
            ipUint += (uint)ipByteArray[3];

            return ipUint;
        }

        public static string UInt32IPAddressToString(uint ip)
        {
            // convert uint IP to string IP e.g. 16909060 -> "1.2.3.4"

            IPAddress i = new IPAddress(ip);
            string[] ipArray = i.ToString().Split('.');

            return ipArray[3] + "." + ipArray[2] + "." + ipArray[1] + "." + ipArray[0];
        }

        [DllImport("dhcpsapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern uint DhcpEnumSubnetClients(
                string ServerIpAddress,
                uint SubnetAddress,
            ref uint ResumeHandle,
                uint PreferredMaximum,
            out IntPtr ClientInfo,
            ref uint ElementsRead,
            ref uint ElementsTotal
        );
    }
}

Example

Here is an (abbreviated) example of the output:

H:\simpleDHCP>csc dhcp.cs
Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.1
for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.


H:\simpleDHCP>dhcp.exe
Enter server : 172.10.1.9
Enter subnet : 172.10.0.0

BW-WKSPRINT-02.academic.lcad        172.10.3.2      0017.f20c.2f66
BW-ADV-03.academic.lcad             172.10.3.3      0016.cbc9.7b03
<snip>
BC-WKSID-12.academic.lcad           172.10.4.46     0019.bbdf.03fe
BC-WKSID-15.academic.lcad           172.10.4.47     0019.bbdf.23a0

289 lease(s) in total

The subnet entered will obviously need to match whatever is set up on your DHCP server, you can see them in angle brackets after ‘Scope’ in the configuration tree.

DHCP Scope