Как надежно и быстро получить MAC-адрес сетевой карты по идентификатору экземпляра устройства

Учитывая идентификатор экземпляра устройства для сетевой карты я хотел бы знать ее MAC-адрес. Пример идентификатора экземпляра устройства в моей системе для интегрированной карты Intel Gigabit:

PCI\VEN_8086&DEV_10CC&SUBSYS_00008086&REV_00\3&33FD14CA&0&C8

Пока что алгоритм, который я использовал, работает следующим образом:

  1. Вызов SetupDiGetClassDevs с DIGCF_DEVICEINTERFACE,
  2. Вызов SetupDiEnumDeviceInfo вернуть возвращенное устройство в SP_DEVINFO_DATA.
  3. Вызов SetupDiEnumDeviceInterfaces с GUID_NDIS_LAN_CLASS чтобы получить интерфейс устройства.
  4. Вызов SetupDiGetDeviceInterfaceDetail для этого вернулся интерфейс устройства. Это дает нам путь к устройству в виде строки: \\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852}
  5. На данный момент у нас есть адрес интерфейса драйвера сетевой карты. Откройте его CreateFile используя результат из # 4.
  6. Вызов DeviceIoControl с IOCTL_NDIS_QUERY_GLOBAL_STATS и OID из OID_802_3_PERMANENT_ADDRESS чтобы получить MAC-адрес.

Обычно это работает и успешно используется на довольно большом количестве машин. Однако, похоже, что очень немногие машины имеют сетевые драйверы, которые не отвечают должным образом на DeviceIoControl запрос на шаге № 6; проблема сохраняется даже после обновления драйверов сетевой карты до последней версии. Это более новые компьютеры под управлением Windows 7. В частности, DeviceIoControl завершается успешно, но возвращает нулевые байты вместо ожидаемых шести байтов, содержащих MAC-адрес.

Подсказка, кажется, на странице MSDN для IOCTL_NDIS_QUERY_GLOBAL_STATS:

Этот IOCTL будет устаревшим в последующих выпусках операционной системы. Вы
следует использовать интерфейсы WMI для запроса информации о драйвере минипорта. За
более подробную информацию смотрите в разделе Поддержка NDIS для WMI.

— возможно, более новые драйверы сетевых карт больше не реализуют этот IOCTL?

Итак, как мне заставить это работать? Возможно ли, что в моем подходе есть недосмотр, и я делаю что-то не так? Или мне нужно использовать гораздо более разные подходы? Некоторые альтернативные подходы включают:

  • запрос Win32_NetworkAdapter Класс WMI: предоставляет необходимую информацию, но отклонен из-за ужасной производительности. Увидеть Быстрая замена WMI-класса Win32_NetworkAdapter для получения MAC-адреса локального компьютера
  • запрос MSNdis_EthernetPermanentAddress Класс WMI: представляется заменой WMI для IOCTL_NDIS_QUERY_GLOBAL_STATS и запрашивает OID непосредственно из драйвера — и этот работает на проблемном сетевом драйвере. К сожалению, возвращенные экземпляры класса предоставляют только MAC-адрес и InstanceName, которая является локализованной строкой, как Intel(R) 82567LM-2 Gigabit Network Connection, Запрос MSNdis_EnumerateAdapter дает список, который связывает InstanceName к DeviceName, лайк \DEVICE\{28FD5409-15BD-4C06-B62F-004D3A06F852}, Я не уверен, как идти от DeviceName к идентификатору экземпляра устройства plug-and-play (PCI\VEN_8086......).
  • Вызов GetAdaptersAddresses или же GetAdaptersInfo (Устаревшее). Единственный нелокализованный идентификатор, который я могу найти в возвращаемом значении, — это имя адаптера, которое выглядит как строка {28FD5409-15BD-4C06-B62F-004D3A06F852} — так же, как DeviceName возвращается классами WMI NDIS. Итак, еще раз, я не могу понять, как связать его с идентификатором экземпляра устройства. Я не уверен, будет ли это работать 100% времени, например для адаптеров без настроенного протокола TCP / IP.
  • Метод NetBIOS: требует, чтобы на карте были установлены определенные протоколы, поэтому он не будет работать 100% времени. Обычно кажется хакерским, а не каким-либо способом связываться с идентификатором экземпляра устройства в любом случае, о котором я знаю. Я бы отверг этот подход.
  • Метод генерации UUID: отклонен по причинам, которые я здесь не буду описывать.

Кажется, что если бы я мог найти способ получить «GUID» для карты из идентификатора экземпляра устройства, я бы справился с одним из оставшихся двух способов сделать что-то. Но я еще не понял, как. В противном случае подход WMI NDIS представляется наиболее перспективным.

Получить список сетевых карт и MAC-адресов легко, и есть несколько способов сделать это. Делать это быстро, что позволяет мне связать его с идентификатором экземпляра устройства, по-видимому, сложно …

РЕДАКТИРОВАТЬ: Пример кода вызова IOCTL, если он кому-либо помогает (игнорируйте утечку дескриптора hFile):

HANDLE hFile = CreateFile(dosDevice.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
DWORD err = GetLastError();
wcout << "GetMACAddress: CreateFile on " << dosDevice << " failed." << endl;
return MACAddress();
}
BYTE address[6];
DWORD oid = OID_802_3_PERMANENT_ADDRESS, returned = 0;
//this fails too: DWORD oid = OID_802_3_CURRENT_ADDRESS, returned = 0;
if (!DeviceIoControl(hFile, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid), address, 6, &returned, NULL)) {
DWORD err = GetLastError();
wcout << "GetMACAddress: DeviceIoControl on " << dosDevice << " failed." << endl;
return MACAddress();
}
if (returned != 6) {
wcout << "GetMACAddress: invalid address length of " << returned << "." << endl;
return MACAddress();
}

Код не работает, печать:

GetMACAddress: invalid address length of 0.

Таким образом, DeviceIoControl возвращает ненулевое значение, указывающее успех, но затем возвращает нулевые байты.

7

Решение

Вот один из способов сделать это:

  1. Вызов GetAdaptersAddresses чтобы получить список IP_ADAPTER_ADDRESSES Структуры
  2. Выполните итерацию по каждому адаптеру и получите его GUID из AdapterName (я не уверен, гарантируется ли такое поведение, но все адаптеры в моей системе имеют здесь GUID, а в документации сказано: AdapterName постоянно)
  3. Для каждого адаптера прочитайте раздел реестра из HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\<the adapter GUID>\Connection\PnPInstanceID (если он существует) (получил эту идею от Вот; Поиск в Google, этот ключ, кажется, хорошо документирован, поэтому он вряд ли изменится)
  4. С помощью этого ключа вы получаете идентификатор устройства для адаптера (что-то вроде: PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10\4&2B8260C3&0&00E4)
  5. Делайте это для каждого адаптера, пока не найдете соответствие. Когда вы получите свой матч, просто вернитесь к IP_ADAPTER_ADDRESSES и посмотрите на PhysicalAddress поле
  6. Получить пиво (по желанию)

Это не было бы Windows, если бы не было миллиона способов что-то сделать!

4

Другие решения

Я завелась с помощью SetupDiGetDeviceRegistryProperty читать SPDRP_FRIENDLYNAME, Если это не найдено, то я читаю SPDRP_DEVICEDESC вместо. В конечном итоге это приводит меня к строке типа «VirtualBox Host-Only Ethernet Adapter # 2». Затем я сопоставляю это со свойством InstanceName в классах WMI NDIS (MSNdis_EthernetPermanentAddress WMI класс). Оба свойства должны быть прочитаны в случае, если несколько адаптеров используют один и тот же драйвер (т. Е. «# 2», «# 3» и т. Д.) — если есть только один адаптер, то SPDRP_FRIENDLYNAME недоступно, но если их более одного, SPDRP_FRIENDLYNAME требуется дифференцировать их.

Этот метод заставляет меня немного нервничать, потому что я сравниваю то, что кажется локализованной строкой, и я не нашел документации, которая бы гарантировала, что я делаю, всегда будет работать. К сожалению, я также не нашел более подходящих способов работы.

Несколько других альтернативных методов включают унижение в недокументированных местах реестра. Один метод — это метод spencercw, а другой — читать SPDRP_DRIVER, который является именем подраздела под HKLM\SYSTEM\CurrentControlSet\Control\Class, Под ключом водителя ищите Linkage\Export значение, которое тогда кажется, что оно может быть сопоставлено с DeviceName собственность MSNdis_EnumerateAdapter учебный класс. Но я не могу найти никакой документации, в которой говорится, что эти значения могут быть юридически сопоставлены. Кроме того, единственная документация, которую я нашел о Linkage\Export был из справочника реестра Win2000 и явно сказал, что приложения не должны полагаться на него.

Другой способ — посмотреть на мой оригинальный вопрос, шаг 4: «SetupDiGetDeviceInterfaceDetail для этого возвращенного интерфейса устройства «. Путь интерфейса устройства фактически может использоваться для восстановления пути устройства. Начнем с пути интерфейса устройства: \\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852}, Затем удалите все до последней косой черты, оставив вас с: {28fd5409-15bd-4c06-b62f-004d3a06f852}, Наконец, готовый \Device\ к этой строке и сопоставьте ее с классами WMI NDIS. Однако, опять же, это кажется недокументированным и основанным на деталях реализации пути интерфейса устройства.

В конце концов, другие методы, которые я исследовал, имели свои недокументированные осложнения, которые звучали по крайней мере так же серьезно, как и соответствие SPDRP_FRIENDLYNAME / SPDRP_DEVICEDESC строки. Поэтому я выбрал более простой подход, который состоял в том, чтобы просто сопоставить эти строки с классами WMI NDIS.

2

По вопросам рекламы [email protected]