1

我想通过 C#(.NET 3.5,x86)应用程序从我们的 DHCP 服务器(Windows 2008 R2)枚举所有 DHCP 租约。

由于 .NET 中没有 DHCP 服务器类,因此我使用 P/Invoke (dhcpsapi.dll) 从 DHCP 服务器获取数据。

如果我将 DhcpEnumSubnetClients 函数与 DHCP_CLIENT_INFO 和 DHCP_CLIENT_INFO_ARRAY 结构一起使用,一切都很好。

但是,如果我使用相应的 V5 函数和结构(DhcpEnumSubnetClientsV5、DHCP_CLIENT_INFO_V5 和 DHCP_CLIENT_INFO_ARRAY_V5),我会遇到严重的内存泄漏(每个枚举租约大约 280-290 字节)。

我需要使用 V5 版本,因为我需要来自 DHCP_CLIENT_INFO_V5 结构的信息,这些信息在 DHCP_CLIENT_INFO 结构(bClientType 和 AddressState)中不可用。

在调用 DhcpEnumSubnetClientsV5 之后,我根据需要调用 DhcpRpcFreeMemory,但 DhcpRpcFreeMemory 在与 DhcpEnumSubnetClientsV5 一起使用时似乎没有做任何事情。

这是 .NET 3.5 或 dhcpsapi.dll 中的错误,还是我的代码中缺少某些内容?

非常感谢任何帮助!

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() & 0xFFFFFFFF);
            this.dwHighDateTime = (UInt32)((date.ToFileTime() & 0x7FFFFFFF00000000) >> 32);
        }
        public DateTime Convert()
        {
            if (this.dwHighDateTime == 0 && this.dwLowDateTime == 0)
                return DateTime.MinValue;
            if (this.dwHighDateTime == int.MaxValue && this.dwLowDateTime == UInt32.MaxValue)
                return DateTime.MaxValue;
            return DateTime.FromFileTime((((long)this.dwHighDateTime) << 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)
        // -> 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)
        // -> 305-307 KB leaked per iteration for DhcpEnumSubnetClientsV5; 283-285 bytes per lease


        // -> around 280-290 bytes leaked per lease enumerated by DhcpEnumSubnetClientsV5

        for (int k = 0; k < 10; k++)
        {
            DataTable dhcpLeases = new DataTable();
            dhcpLeases = GetSubnetLeases(server, subnet);

            if (dhcpLeases == null || dhcpLeases.Rows.Count < 1)
            {
                Console.WriteLine("No Leases found");
                Environment.Exit(0);
            }


            for (int i = 0; i < 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 && 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 < 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;
    }


}

编辑: 即使我删除了所有使用从 DhcpEnumSubnetClientsV5 返回的 DHCP_CLIENT_INFO_ARRAY_V5 的代码(即我只调用 DhcpEnumSubnetClientsV5 并且不关心返回的内容),内存泄漏仍然是相同的。

    /////////////////////////////////////////////////////////////////////////////////
    //
    // 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 && 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;
    }

编辑:根据 David Heffernan 的输入,我还尝试在释放整个结构使用的内存之前释放 Clients 数组使用的内存,但这没有帮助。

    /////////////////////////////////////////////////////////////////////////////////
    //
    // 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 && 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;
    }
4

0 回答 0