title: Memory leak in DhcpEnumSubnetClientsV5 via P/Invoke
body: <p>I want to enumerate all DHCP Leases from our DHCP Server (Windows 2008 R2) via a C# (.NET 3.5, x86) application.</p>
  
  <p>As there are unfortunately no DHCP Server Classes in .NET, I use P/Invoke (dhcpsapi.dll) to get data from the DHCP Server.</p>
  
  <p>Everything's fine if I use the DhcpEnumSubnetClients function together with the DHCP_CLIENT_INFO and DHCP_CLIENT_INFO_ARRAY structs.</p>
  
  <p>But if I use the corresponding V5 functions and structs (DhcpEnumSubnetClientsV5, DHCP_CLIENT_INFO_V5 and DHCP_CLIENT_INFO_ARRAY_V5), I get a nasty memory leak (roughly around 280-290 Bytes per enumerated Lease).</p>
  
  <p>I need to use the V5 versions since I need information from the DHCP_CLIENT_INFO_V5 structure which is not available in the DHCP_CLIENT_INFO structure (bClientType and AddressState).</p>
  
  <p>After the call to DhcpEnumSubnetClientsV5, I call DhcpRpcFreeMemory as required but DhcpRpcFreeMemory doesn't seem to do anything when used with DhcpEnumSubnetClientsV5.</p>
  
  <p>Is this a bug in .NET 3.5 or in dhcpsapi.dll or is something missing in my code?</p>
  
  <p>Very grateful for any help!</p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  
  
  using System.Data;
  using System.Runtime.InteropServices;
  using System.Net.NetworkInformation;
  using System.Text.RegularExpressions;
  
  
  class Program
  {
      [DllImport("dhcpsapi.dll", CharSet = CharSet.Unicode)]
      static extern UInt32 DhcpEnumSubnetClients(
      string ServerIpAddress,
      UInt32 SubnetAddress,
      ref UInt32 ResumeHandle,
      UInt32 PreferredMaximum,
      out IntPtr ClientInfo,
      out UInt32 ElementsRead,
      out UInt32 ElementsTotal);
  
      [DllImport("dhcpsapi.dll", CharSet = CharSet.Unicode)]
      static extern UInt32 DhcpEnumSubnetClientsV5(
      string ServerIpAddress,
      UInt32 SubnetAddress,
      ref UInt32 ResumeHandle,
      UInt32 PreferredMaximum,
      out IntPtr ClientInfo,
      out UInt32 ElementsRead,
      out UInt32 ElementsTotal);
  
      [DllImport("dhcpsapi.dll")]
      static extern void DhcpRpcFreeMemory(IntPtr BuffPtr);
  
      [StructLayout(LayoutKind.Sequential)]
      private struct DHCP_CLIENT_INFO_ARRAY_V5
      {
          public UInt32 NumElements;
          public IntPtr Elements;
      }
  
      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
      private struct DHCP_CLIENT_INFO_V5
      {
          public UInt32 ClientIpAddress;
          public UInt32 SubnetMask;
          public DHCP_CLIENT_UID ClientHardwareAddress;
          public string ClientName;
          public string ClientComment;
          public DATE_TIME ClientLeaseExpires;
          public DHCP_HOST_INFO OwnerHost;
          public byte bClientType;
          public byte AddressState;
      }
  
      [StructLayout(LayoutKind.Sequential)]
      private struct DHCP_CLIENT_INFO_ARRAY
      {
          public UInt32 NumElements;
          public IntPtr Elements;
      }
  
      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
      private struct DHCP_CLIENT_INFO
      {
          public UInt32 ClientIpAddress;
          public UInt32 SubnetMask;
          public DHCP_CLIENT_UID ClientHardwareAddress;
          public string ClientName;
          public string ClientComment;
          public DATE_TIME ClientLeaseExpires;
          public DHCP_HOST_INFO OwnerHost;
      }
  
      [StructLayout(LayoutKind.Sequential)]
      private struct DHCP_CLIENT_UID
      {
          public UInt32 DataLength;
          public IntPtr Data;
      }
  
      [StructLayout(LayoutKind.Sequential)]
      private struct DATE_TIME
      {
          public UInt32 dwLowDateTime;
          public UInt32 dwHighDateTime;
          public DATE_TIME(DateTime date)
          {
              if (date == DateTime.MaxValue)
              {
                  this.dwLowDateTime = this.dwHighDateTime = UInt32.MaxValue;
                  return;
              }
              if (date == DateTime.MinValue)
              {
                  this.dwLowDateTime = this.dwHighDateTime = UInt32.MinValue;
                  return;
              }
              this.dwLowDateTime = (UInt32)(date.ToFileTime() &amp; 0xFFFFFFFF);
              this.dwHighDateTime = (UInt32)((date.ToFileTime() &amp; 0x7FFFFFFF00000000) &gt;&gt; 32);
          }
          public DateTime Convert()
          {
              if (this.dwHighDateTime == 0 &amp;&amp; this.dwLowDateTime == 0)
                  return DateTime.MinValue;
              if (this.dwHighDateTime == int.MaxValue &amp;&amp; this.dwLowDateTime == UInt32.MaxValue)
                  return DateTime.MaxValue;
              return DateTime.FromFileTime((((long)this.dwHighDateTime) &lt;&lt; 32) | (UInt32)this.dwLowDateTime);
          }
          public override string ToString()
          {
              return this.Convert().ToString();
          }
      }
  
      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
      private struct DHCP_HOST_INFO
      {
          public UInt32 IpAddress;
          public string NetBiosName;
          public string HostName;
      }
  
  
  
  
      /////////////////////////////////////////////////////////////////////////////////
      //
      // Main
      //
      /////////////////////////////////////////////////////////////////////////////////
      static void Main(string[] args)
      {
          Console.Write("Server: ");
          string server = Console.ReadLine();
  
          Console.Write("Subnet: ");
          string subnet = Console.ReadLine();
  
  
          // 10 iterations for subnet with 150 leases:    5 MB RAM (DhcpEnumSubnetClients),     5 MB RAM (DhcpEnumSubnetClientsV5)
          // 100 iterations for subnet with 150 leases:   7 MB RAM (DhcpEnumSubnetClients),    11 MB RAM (DhcpEnumSubnetClientsV5)
          // 200 iterations for subnet with 150 leases:   7 MB RAM (DhcpEnumSubnetClients),    16 MB RAM (DhcpEnumSubnetClientsV5)
          // 500 iterations for subnet with 150 leases:   7 MB RAM (DhcpEnumSubnetClients),    28 MB RAM (DhcpEnumSubnetClientsV5)
          // -&gt; 40-45 KB leaked per iteration for DhcpEnumSubnetClientsV5; 273-307 bytes per lease
  
          // 10 iterations for subnet with 1100 leases:   8 MB RAM (DhcpEnumSubnetClients),   11 MB RAM (DhcpEnumSubnetClientsV5)
          // 100 iterations for subnet with 1100 leases:  8 MB RAM (DhcpEnumSubnetClients),   38 MB RAM (DhcpEnumSubnetClientsV5)
          // 200 iterations for subnet with 1100 leases:  8 MB RAM (DhcpEnumSubnetClients),   68 MB RAM (DhcpEnumSubnetClientsV5)
          // 500 iterations for subnet with 1100 leases:  8 MB RAM (DhcpEnumSubnetClients),   157 MB RAM (DhcpEnumSubnetClientsV5)
          // -&gt; 305-307 KB leaked per iteration for DhcpEnumSubnetClientsV5; 283-285 bytes per lease
  
  
          // -&gt; around 280-290 bytes leaked per lease enumerated by DhcpEnumSubnetClientsV5
  
          for (int k = 0; k &lt; 10; k++)
          {
              DataTable dhcpLeases = new DataTable();
              dhcpLeases = GetSubnetLeases(server, subnet);
  
              if (dhcpLeases == null || dhcpLeases.Rows.Count &lt; 1)
              {
                  Console.WriteLine("No Leases found");
                  Environment.Exit(0);
              }
  
  
              for (int i = 0; i &lt; dhcpLeases.Rows.Count; i++)
              {
                  Console.WriteLine("MAC Address: " + dhcpLeases.Rows[i]["macAddress"] + ", IP Address: " + dhcpLeases.Rows[i]["ipAddress"]);
              }
  
              Console.WriteLine(dhcpLeases.Rows.Count + " Leases found");
          }
  
          Console.Write("Any key to exit");
          Console.ReadLine();
      }
  
  
  
  
      /////////////////////////////////////////////////////////////////////////////////
      //
      // Get all Leases for "subnet" on "server"
      // Returns DataTable containing Leases or null if there was a problem
      //
      /////////////////////////////////////////////////////////////////////////////////
      public static DataTable GetSubnetLeases(string server, string subnet)
      {
          UInt32 response = 0, resumeHandle = 0;
          UInt32 nRead = 0, nTotal = 0;
  
          DataTable leases = new DataTable();
          leases.Columns.Add("ipAddress");
          leases.Columns.Add("macAddress");
          leases.Columns.Add("subnet");
          leases.Columns.Add("clientName");
  
          for (; ; )
          {
              IntPtr retPtr = IntPtr.Zero;
  
              response = DhcpEnumSubnetClientsV5(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal);      // ---- Memory Leak ----
  //            response = DhcpEnumSubnetClients(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal);      // No Memory Leak
  
              if (response == 259)                                                //ERROR_NO_MORE_ITEMS = 259
              {
                  if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
                  break;
              }
  
              if (response != 0 &amp;&amp; response != 234 || retPtr == IntPtr.Zero)      //ERROR_MORE_DATA = 234
              {
                  if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
                  return null;
              }
  
              DHCP_CLIENT_INFO_ARRAY_V5 nativeArray = (DHCP_CLIENT_INFO_ARRAY_V5)Marshal.PtrToStructure(retPtr, typeof(DHCP_CLIENT_INFO_ARRAY_V5));   // ---- Memory Leak ----
  //            DHCP_CLIENT_INFO_ARRAY nativeArray = (DHCP_CLIENT_INFO_ARRAY)Marshal.PtrToStructure(retPtr, typeof(DHCP_CLIENT_INFO_ARRAY));          // No Memory Leak
  
              for (int i = 0; i &lt; nativeArray.NumElements; i++)
              {
                  IntPtr iPtr = Marshal.ReadIntPtr(nativeArray.Elements, (i * Marshal.SizeOf(typeof(IntPtr))));
  
                  DHCP_CLIENT_INFO_V5 nativeClient = (DHCP_CLIENT_INFO_V5)Marshal.PtrToStructure(iPtr, typeof(DHCP_CLIENT_INFO_V5));  // ---- Memory Leak ----
  //                DHCP_CLIENT_INFO nativeClient = (DHCP_CLIENT_INFO)Marshal.PtrToStructure(iPtr, typeof(DHCP_CLIENT_INFO));         // No Memory Leak
  
                  string ipAddress = ConvertIP(nativeClient.ClientIpAddress);
                  string clientName = nativeClient.ClientName;
                  byte[] macBytes = new byte[nativeClient.ClientHardwareAddress.DataLength];
                  Marshal.Copy(nativeClient.ClientHardwareAddress.Data, macBytes, 0, macBytes.Length);
                  PhysicalAddress mac = new PhysicalAddress(macBytes);
                  string macAddress = mac.ToString().ToUpper();
                  macAddress = Regex.Replace(macAddress, "(.{2})(.{2})(.{2})(.{2})(.{2})(.{2})", "$1:$2:$3:$4:$5:$6");
  
                  leases.Rows.Add(ipAddress, macAddress, subnet, clientName);
              }
  
              if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
              if (response == 0) break;
          }
          return leases;
      }
  
  
      /////////////////////////////////////////////////////////////////////////////////
      //
      // Convert IP Address in w.x.y.z format to UInt32
      //
      /////////////////////////////////////////////////////////////////////////////////
      public static UInt32 ConvertIP(string ipString)
      {
          string[] ipArray = ipString.Split('.');
          UInt32 ipUint32 = (Convert.ToUInt32(ipArray[0]) * 16777216) + (Convert.ToUInt32(ipArray[1]) * 65536) + (Convert.ToUInt32(ipArray[2]) * 256) + (Convert.ToUInt32(ipArray[3]));
          return ipUint32;
      }
  
  
      /////////////////////////////////////////////////////////////////////////////////
      //
      // Convert IP Address from UInt32 to string in w.x.y.z format
      //
      /////////////////////////////////////////////////////////////////////////////////
      public static string ConvertIP(UInt32 ipUInt32)
      {
          string ipString = "";
          UInt32 octet = 0;
  
          octet = ipUInt32 / 16777216;
          ipString = ipString + octet.ToString() + ".";
          ipUInt32 = ipUInt32 - (octet * 16777216);
  
          octet = ipUInt32 / 65536;
          ipString = ipString + octet.ToString() + ".";
          ipUInt32 = ipUInt32 - (octet * 65536);
  
          octet = ipUInt32 / 256;
          ipString = ipString + octet.ToString() + ".";
          ipUInt32 = ipUInt32 - (octet * 256);
  
          ipString = ipString + ipUInt32.ToString();
          return ipString;
      }
  
  
  }
  </code></pre>
  
  <p><strong>Edit:</strong>
  Even if I remove all code that uses the DHCP_CLIENT_INFO_ARRAY_V5 returned from DhcpEnumSubnetClientsV5 (i.e. I'm only calling DhcpEnumSubnetClientsV5 and don't care about what's returned), the memory leak is still the same.</p>
  
  <pre><code>    /////////////////////////////////////////////////////////////////////////////////
      //
      // Get all Leases for "subnet" on "server"
      // Returns DataTable containing Leases or null if there was a problem
      //
      /////////////////////////////////////////////////////////////////////////////////
      public static DataTable GetSubnetLeases(string server, string subnet)
      {
          UInt32 response = 0, resumeHandle = 0;
          UInt32 nRead = 0, nTotal = 0;
  
          DataTable leases = new DataTable();
          leases.Columns.Add("ipAddress");
          leases.Columns.Add("macAddress");
          leases.Columns.Add("subnet");
          leases.Columns.Add("clientName");
  
          for (; ; )
          {
              IntPtr retPtr = IntPtr.Zero;
  
              response = DhcpEnumSubnetClientsV5(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal);      // ---- Memory Leak ----
  //            response = DhcpEnumSubnetClients(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal);      // No Memory Leak
  
              if (response == 259)                                                //ERROR_NO_MORE_ITEMS = 259
              {
                  if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
                  break;
              }
  
              if (response != 0 &amp;&amp; response != 234 || retPtr == IntPtr.Zero)      //ERROR_MORE_DATA = 234
              {
                  if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
                  return null;
              }
  
              if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
              if (response == 0) break;
          }
  
          leases.Rows.Add("1.1.1.1", "11:11:11:11:11:11", subnet, "clientName");
          return leases;
      }
  </code></pre>
  
  <p><strong>Edit:</strong> Based on David Heffernan's input, I also tried to release the memory used by the Clients array before releasing the memory used by the whole struct, but it didn't help.</p>
  
  <pre><code>    /////////////////////////////////////////////////////////////////////////////////
      //
      // Get all Leases for "subnet" on "server"
      // Returns DataTable containing Leases or null if there was a problem
      //
      /////////////////////////////////////////////////////////////////////////////////
      public static DataTable GetSubnetLeases(string server, string subnet)
      {
          UInt32 response = 0, resumeHandle = 0;
          UInt32 nRead = 0, nTotal = 0;
  
          DataTable leases = new DataTable();
          leases.Columns.Add("ipAddress");
          leases.Columns.Add("macAddress");
          leases.Columns.Add("subnet");
          leases.Columns.Add("clientName");
  
          for (; ; )
          {
              IntPtr retPtr = IntPtr.Zero;
  
              response = DhcpEnumSubnetClientsV5(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal);      // ---- Memory Leak ----
  //            response = DhcpEnumSubnetClients(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal);      // No Memory Leak
  
              if (response == 259)                                                //ERROR_NO_MORE_ITEMS = 259
              {
                  if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
                  break;
              }
  
              if (response != 0 &amp;&amp; response != 234 || retPtr == IntPtr.Zero)      //ERROR_MORE_DATA = 234
              {
                  if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
                  return null;
              }
  
  
              IntPtr clientsArrayPtr = Marshal.ReadIntPtr(retPtr, sizeof(UInt32));    // Get Pointer to the Clients Array
  
              if (clientsArrayPtr != IntPtr.Zero) DhcpRpcFreeMemory(clientsArrayPtr); // Release Memory used by Cients Array
              if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);                   // Release Memory used by the DHCP_CLIENT_INFO_ARRAY_V5 struct
              if (response == 0) break;
          }
  
          leases.Rows.Add("1.1.1.1", "11:11:11:11:11:11", subnet, "clientName");
          return leases;
      }
  </code></pre>