Быстрая замена WMI-класса Win32_NetworkAdapter для получения MAC-адреса локального компьютера - PullRequest
4 голосов
/ 06 сентября 2011

TL; DR версия этого вопроса: WMI Win32_NetworkAdapter класс содержит информацию, которая мне нужна, но слишком медленная. Какой более быстрый способ получения информации для столбцов MACAddress, ConfigManagerErrorCode и PNPDeviceID в Windows?

Мне нужно получить информацию для подключенных сетевых адаптеров, чтобы я мог получить MAC-адрес для уникальной идентификации локального компьютера с Microsoft Windows. Класс WMI Win32_NetworkAdapter , похоже, содержит информацию, которую я ищу. Мне действительно нужны столбцы MACAddress, ConfigManagerErrorCode и PNPDeviceID:

  • MAC-адрес: MAC-адрес (цель этой операции)
  • ConfigManagerErrorCode: позволяет определить, включен адаптер и работает он или нет. (Если он отключен, я должен использовать MAC-адрес, ранее кэшированный моим приложением, если он доступен).
  • PNPDeviceID: проверяя префикс «PCI» (и, возможно, другие интерфейсы, если необходимо), я могу отфильтровать нефизические адаптеры, которых на моем Windows 7 несколько (включая виртуальные адаптеры, как для VMware). / VirtualBox).

Мой план состоял в том, чтобы отфильтровать нефизические устройства с использованием PNPDeviceID. Тогда я бы использовал столбец MACAddress для всех оставшихся записей таблицы (сохранение адреса в кеше). Когда устройство отключено (как, возможно, указано ненулевым ConfigManagerErrorCode), а MAC-адрес равен нулю, я могу использовать ранее увиденный MAC-адрес для этого устройства из своего кэша.

Вы можете увидеть содержимое этой таблицы на моем компьютере с Windows 7. Вы можете видеть там тонны мусора, но только одну запись с PNPDeviceID «PCI».

wmic:root\cli>NIC GET Caption, ConfigManagerErrorCode, MACAddress, PNPDeviceID
Caption                                                   ConfigManagerErrorCode  MACAddress         PNPDeviceID
[00000000] WAN Miniport (SSTP)                            0                                          ROOT\MS_SSTPMINIPORT\0000
[00000001] WAN Miniport (IKEv2)                           0                                          ROOT\MS_AGILEVPNMINIPORT\0000
[00000002] WAN Miniport (L2TP)                            0                                          ROOT\MS_L2TPMINIPORT\0000
[00000003] WAN Miniport (PPTP)                            0                                          ROOT\MS_PPTPMINIPORT\0000
[00000004] WAN Miniport (PPPOE)                           0                                          ROOT\MS_PPPOEMINIPORT\0000
[00000005] WAN Miniport (IPv6)                            0                                          ROOT\MS_NDISWANIPV6\0000
[00000006] WAN Miniport (Network Monitor)                 0                                          ROOT\MS_NDISWANBH\0000
[00000007] Intel(R) 82567LM-2 Gigabit Network Connection  0                       00:1C:C0:B0:C4:89  PCI\VEN_8086&DEV_10CC&SUBSYS_00008086&REV_00\3&33FD14CA&0&C8
[00000008] WAN Miniport (IP)                              0                                          ROOT\MS_NDISWANIP\0000
[00000009] Microsoft ISATAP Adapter                       0                                          ROOT\*ISATAP\0000
[00000010] RAS Async Adapter                              0                       20:41:53:59:4E:FF  SW\{EEAB7790-C514-11D1-B42B-00805FC1270E}\ASYNCMAC
[00000011] Microsoft Teredo Tunneling Adapter             0                                          ROOT\*TEREDO\0000
[00000012] VirtualBox Bridged Networking Driver Miniport  0                       00:1C:C0:B0:C4:89  ROOT\SUN_VBOXNETFLTMP\0000
[00000013] VirtualBox Host-Only Ethernet Adapter          0                       08:00:27:00:C4:A1  ROOT\NET\0000
[00000014] Microsoft ISATAP Adapter                       0                                          ROOT\*ISATAP\0001
[00000015] VMware Virtual Ethernet Adapter for VMnet1     0                       00:50:56:C0:00:01  ROOT\VMWARE\0000
[00000016] Microsoft ISATAP Adapter                       0                                          ROOT\*ISATAP\0002
[00000017] VMware Virtual Ethernet Adapter for VMnet8     0                       00:50:56:C0:00:08  ROOT\VMWARE\0001
[00000018] Microsoft ISATAP Adapter                       0                                          ROOT\*ISATAP\0003

(Если я отключу свой физический адаптер, столбец MACAddress обнулится, а ConfigManagerErrorCode изменится на ненулевой).

К сожалению, этот класс просто слишком медленный. Любой запрос в Win32_NetworkAdapter последовательно занимает 0,3 секунды на моем относительно современном компьютере под управлением Windows 7 Core i7. Таким образом, использование этого добавит еще 0,3 секунды к запуску приложения (или хуже), что я считаю недопустимым. Это связано с тем, что я не могу придумать единственной уважительной причины, по которой выясняется, какие MAC-адреса и идентификаторы устройств plug-and-play находятся на локальном компьютере.

Поиск других методов для получения MAC-адреса дал функции GetAdaptersInfo и более новые GetAdaptersAddresses функции. У них нет 0,3-секундного штрафа, налагаемого WMI. Эти функции используются классом *. 1030 * NetworkInterface .NET Framework (как определено при изучении исходного кода .NET) и инструментом командной строки "ipconfig" (как определено с помощью Dependency Walker ).

Я сделал простой пример на C #, который перечисляет все сетевые адаптеры, используя класс NetworkInterface. К сожалению, использование этих API имеет два недостатка:

  • Эти API даже не перечисляют отключенные сетевые адаптеры для начала. Это означает, что я не могу найти MAC-адрес для отключенного адаптера из моего кэша.
  • Я не понимаю, как мне получить PNPDeviceID для фильтрации нефизических адаптеров.

Мой вопрос: каким способом я могу получить MAC-адрес физических адаптеров локального компьютера (включен он или нет) в течение нескольких десятков миллисекунд максимум?

(у меня есть опыт работы как на C #, так и на C ++, и я хорошо читаю на других языках, поэтому мне все равно, какой язык может использоваться в ответах).

РЕДАКТИРОВАТЬ: В ответ на предложение Алекса К использовать только немедленный возврат и пересылку, а также предоставить пример кода WMI для того, что я делаю, - вот код C #, в котором перечислены интересующие столбцы :

    public static void NetTest() {
        System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
        EnumerationOptions opt = new EnumerationOptions();
        // WMI flag suggestions from Alex K:
        opt.ReturnImmediately = true;
        opt.Rewindable = false;
        ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\cimv2", "select MACAddress, PNPDeviceID, ConfigManagerErrorCode from Win32_NetworkAdapter", opt);
        foreach (ManagementObject obj in searcher.Get()) {
            Console.WriteLine("=========================================");
            foreach (PropertyData pd in obj.Properties) {
                Console.WriteLine("{0} = {1}", pd.Name, pd.Value);
            }
        }
        Console.WriteLine(sw.Elapsed.TotalSeconds);
    }

Я вызывал эту функцию 3 раза, и каждый раз в последней строке печаталось около 0,36 секунды.Таким образом, предложенные флаги, кажется, не имеют никакого эффекта: положительный или отрицательный.Это не так уж и удивительно, поскольку ответ на Как сделать WMI-запросы только для чтения и только для чтения в C #? , кажется, указывает на то, что никаких изменений в производительности не будет наблюдаться, если не будет большого количества записей(например, от сотен до тысяч), что не относится к таблице Win32_NetworkAdapter.

РЕДАКТИРОВАТЬ 2: Было предложено несколько ответов для использования SendARP из API-помощника IP(это тот же API, который имеет функцию GetAdaptersInfo).Какие преимущества это дает по сравнению с GetAdaptersInfo для поиска локального MAC-адреса?Я не могу думать ни о чем - на первый взгляд, GetAdaptersInfo, похоже, возвращает более полный набор информации, чем SendARP для локальных адаптеров.Теперь, когда я думаю об этом, я думаю, что большая часть моего вопроса сосредоточена на концепции перечисления: какие адаптеры существуют на компьютере в первую очередь?SendARP не выполняет перечисление: предполагается, что вы уже знаете IP-адрес адаптера, для которого вы хотите MAC.Мне нужно выяснить, какие адаптеры существуют в системе.Возникают некоторые проблемы:

  • Что произойдет, если сетевой кабель отключен?Например, это очень часто встречается на ноутбуке (отключенный Ethernet, отключенная карта WiFi).Я попытался использовать NetworkInterface.GetAllNetworkInterfaces () и перечислить все одноадресные адреса, используя GetIPProperties (). UnicastAddresses , когда носитель отключен.Windows не указывает ни одного адреса, поэтому я не могу вспомнить ни одного адреса, который можно было бы передать SendARP.Интуитивно понятно, что у отключенного адаптера все равно будет физический адрес, но нет IP-адреса (поскольку он не находится в сети с сервером DHCP).
  • Что подводит меня к следующему: как получитьсписок локальных IP-адресов для проверки с помощью SendARP?
  • Как получить PNPDeviceID (или аналогичный идентификатор, который можно использовать для фильтрации нефизических адаптеров) для каждого адаптера?
  • Как мнеВывести список отключенных адаптеров, чтобы я мог найти MAC-адрес из своего кэша (т. е. MAC-адрес, который я нашел, когда он последний раз включался)?

Эти проблемы, похоже, не решаются с помощью SendARP,и являются основной причиной, по которой я задал этот вопрос (в противном случае я бы использовал GetAdaptersInfo и продолжал с вещами ...).

Ответы [ 2 ]

5 голосов
/ 17 сентября 2011

Я полностью исключил WMI из уравнения и добился значительного улучшения, все еще получая нужную информацию.Как отмечалось в WMI, получение результатов заняло> 0,30 секунды.С моей версией я могу получить ту же информацию за 0,01 секунды.

Я использовал API настройки, API менеджера конфигурации, а затем сделал запросы OID непосредственно на сетевой драйвер NDIS, чтобы получить MAC-адрес.API настройки кажется отвратительно медленным, особенно при получении таких вещей, как значения свойств.Обязательно, чтобы настройки API-вызовов были минимальными.(Вы действительно можете увидеть, насколько это плохо, посмотрев, сколько времени занимает загрузка вкладки «Подробности» устройства в диспетчере устройств).

Предположение, почему WMI был таким медленным: я заметил, чтоWin32_NetworkAdapter WMI всегда занимал одинаковое количество времени, независимо от того, какое подмножество свойств я запрашивал.Похоже, что программисты класса WMI Win32_NetworkAdapter были ленивы и не оптимизировали свой класс для сбора только запрошенной информации, как это делают другие классы WMI.Они, вероятно, собирают всю информацию, запрошенную или нет.Вероятно, они в значительной степени полагаются на API настройки, чтобы сделать это, и чрезмерные вызовы медленного API установки для получения нежелательной информации делают его таким медленным.

Обзор высокого уровня того, что я сделал:

  1. Используйте SetupDiGetClassDevs для получения всех сетевых устройств, присутствующих в системе.
  2. Я отфильтровываю все результаты, у которых нет перечислителя "PCI" (используйте SetupDiGetDeviceRegistryProperty с SPDRP_ENUMERATOR_NAME, чтобы получить перечислитель).
  3. В остальном я могу использовать CM_Get_DevNode_Status для получения статуса устройства и кода ошибки.Все устройства со съемными кодами состояния устройства отфильтровываются.
  4. Если DN_HAS_PROBLEM установлен так, что существует ненулевой код ошибки, то устройство, вероятно, отключено (или имеет какую-то другую проблему).Драйвер не загружен, поэтому мы не можем сделать запрос к драйверу.Поэтому в этом случае я загружаю MAC-адрес для сетевой карты из кеша, который я храню.
  5. Родительское устройство может быть съемным, поэтому я тоже отфильтрую их, рекурсивно исследуя дерево устройств, используя CM_Get_Parent и CM_Get_DevNode_Status, чтобы найтиродительские съемные устройства.
  6. Все остальные устройства являются несъемными сетевыми картами на шине PCI.
  7. Для каждого сетевого устройства я использую SetupDiGetClassDevs с флагом GUID_NDIS_LAN_CLASS и флагом DIGCF_DEVICEINTERFACE, чтобы получить его интерфейсы (это работает толькоесли устройство включено / не имеет проблем).
  8. Используйте IOCTL_NDIS_QUERY_GLOBAL_STATS с OID_802_3_PERMANENT_ADDRESS на интерфейсе драйвера для получения постоянного MAC-адреса.Сохраните его в кеше, а затем верните.

Результат - надежное указание MAC-адресов на ПК, которые должны быть защищены от «поддельных» сетевых карт от VMware, VirtualBox, в значительной степени защищенных отсетевые карты, которые временно отключены и невосприимчивы к переходным сетевым картам, подключенным через USB, ExpressCard, PC Card или любые другие съемные интерфейсы в будущем.

EDIT: IOCTL_NDIS_QUERY_GLOBAL_STATS не поддерживается всей сетьюкарты.Подавляющее большинство работает, но некоторые карты Intel нет.См. Как надежно и быстро получить MAC-адрес сетевой карты по идентификатору экземпляра устройства

2 голосов
/ 06 сентября 2011

Вы должны быть в состоянии получить все необходимое из пространства имен System.Net.Например, следующий образец взят из MSDN и выполняет то, что вы просили в исходной версии вопроса.Он отображает физические адреса всех интерфейсов на локальном компьютере.

public static void ShowNetworkInterfaces()
{
    IPGlobalProperties computerProperties = IPGlobalProperties.GetIPGlobalProperties();
    NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
    Console.WriteLine("Interface information for {0}.{1}     ",
            computerProperties.HostName, computerProperties.DomainName);
    if (nics == null || nics.Length < 1)
    {
        Console.WriteLine("  No network interfaces found.");
        return;
    }

    Console.WriteLine("  Number of interfaces .................... : {0}", nics.Length);
    foreach (NetworkInterface adapter in nics)
    {
        IPInterfaceProperties properties = adapter.GetIPProperties(); //  .GetIPInterfaceProperties();
        Console.WriteLine();
        Console.WriteLine(adapter.Description);
        Console.WriteLine(String.Empty.PadLeft(adapter.Description.Length,'='));
        Console.WriteLine("  Interface type .......................... : {0}", adapter.NetworkInterfaceType);
        Console.Write("  Physical address ........................ : ");
        PhysicalAddress address = adapter.GetPhysicalAddress();
        byte[] bytes = address.GetAddressBytes();
        for(int i = 0; i< bytes.Length; i++)
        {
            // Display the physical address in hexadecimal.
            Console.Write("{0}", bytes[i].ToString("X2"));
            // Insert a hyphen after each byte, unless we are at the end of the 
            // address.
            if (i != bytes.Length -1)
            {
                 Console.Write("-");
            }
        }
        Console.WriteLine();
    }
}
...