Как надежно и быстро получить MAC-адрес сетевой карты по идентификатору экземпляра устройства
Учитывая идентификатор экземпляра устройства для сетевой карты, я хотел бы знать его MAC-адрес. Пример идентификатора экземпляра устройства в моей системе для интегрированной карты Intel Gigabit:
PCI\VEN_8086&DEV_10CC&SUBSYS_00008086&REV_00\3&33FD14CA&0&C8
Пока что алгоритм, который я использовал, работает следующим образом:
- Вызов
SetupDiGetClassDevs
сDIGCF_DEVICEINTERFACE
, - Вызов
SetupDiEnumDeviceInfo
вернуть возвращенное устройство вSP_DEVINFO_DATA
, - Вызов
SetupDiEnumDeviceInterfaces
сGUID_NDIS_LAN_CLASS
чтобы получить интерфейс устройства. - Вызов
SetupDiGetDeviceInterfaceDetail
для этого вернулся интерфейс устройства. Это дает нам путь к устройству в виде строки:\\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852}
- На данный момент у нас есть адрес интерфейса драйвера сетевой карты. Откройте его
CreateFile
используя результат из #4. - Вызов
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: предоставляет необходимую информацию, но отклонен из-за ужасной производительности. См. Быстрая замена для Win32_NetworkAdapter WMI класс для получения 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 возвращает ненулевое значение, указывающее успех, но затем возвращает нулевые байты.
2 ответа
Я завелась с помощью 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.
Вот один из способов сделать это:
- Вызов
GetAdaptersAddresses
чтобы получить списокIP_ADAPTER_ADDRESSES
Структуры - Выполните итерацию по каждому адаптеру и получите его GUID из
AdapterName
(я не уверен, гарантируется ли такое поведение, но все адаптеры в моей системе имеют здесь GUID, а в документации сказано, чтоAdapterName
постоянно) - Для каждого адаптера прочитайте раздел реестра из
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\<the adapter GUID>\Connection\PnPInstanceID
(если он существует) (получил эту идею отсюда; поиск в Google, этот ключ, кажется, хорошо документирован, поэтому он вряд ли изменится) - С помощью этого ключа вы получаете идентификатор устройства для адаптера (что-то вроде:
PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10\4&2B8260C3&0&00E4
) - Делайте это для каждого адаптера, пока не найдете соответствие. Когда вы получите свой матч, просто вернитесь к
IP_ADAPTER_ADDRESSES
и посмотрите наPhysicalAddress
поле - Получить пиво (по желанию)
Это не было бы Windows, если бы не было миллиона способов что-то сделать!
Я предполагаю, что вы хотите получить MAC-адрес, чтобы реализовать какую-то систему DRM, инвентаризации или классификации, поскольку вы пытались получить постоянный MAC-адрес вместо текущего.
Вы, кажется, забываете, что существует даже административно наложенный MAC-адрес (другими словами: "принудительный" MAC-адрес).
Некоторые драйверы позволяют сделать это на странице свойств устройства на вкладке "Дополнительно" (например: мой сетевой адаптер Marvell позволяет мне это сделать), в то время как некоторые другие не позволяют это делать (читайте: они не поддерживают это свойство.).
Однако все заканчивается значением реестра: HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\xxxx\NetworkAddress
, с REG_SZ
тип. Здесь вы можете установить MAC-адрес, отличный от исходного, в виде "01020304abcd" (6 байт, обычное шестнадцатеричное, без:
разделители или 0x
префикс). После установки перезагрузите машину, и при включении новый MAC-адрес вступит в силу.
У меня есть материнская плата с двумя интегрированными сетевыми картами Marvell и сетевой картой USB WiFi NETGEAR. Marvell поддерживает изменение MAC-адреса: если вы установитеNetworkAddress
значение в реестре, вы также увидите новое значение на странице свойств драйвера, и оно вступит в силу немедленно, без необходимости перезапуска (если вы измените его на странице свойств устройства). Вот результаты чтения MAC-адреса разными методами:
GetAdaptersInfo
: новый MAC-адресIOCTL_NDIS_QUERY_GLOBAL_STATS
: исходный MAC-адресMSNdis_EthernetPermanentAddress
: исходный MAC-адрес
Я пробовал добавить NetworkAddress
значение в реестре для сетевой карты NETGEAR USB WiFi, и результаты будут следующими:
GetAdaptersInfo
: новый MAC-адресIOCTL_NDIS_QUERY_GLOBAL_STATS
: новый MAC-адресMSNdis_EthernetPermanentAddress
: новый MAC-адрес
Исходный адрес MAC пропал.
Итак, чтобы вас не обманул "злонамеренный" пользователь, всегда нужно проверять HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\xxxx\NetworkAddress
Значение реестра. Если это установлено, я думаю, что лучше вообще не доверять этому сетевому адаптеру, поскольку реализация драйвера должна решить, что будет представлено вам с использованием различных методов.
Некоторые сведения о том, как добраться до этого ключа реестра:
Документация Microsoft о ключе HKLM\SYSTEM\CurrentControlSet\Class
Согласно документации Microsoft на этой странице,
Для каждого класса существует подраздел, который назван с использованием идентификатора GUID класса установки.
Итак, мы выбираем {4D36E972-E325-11CE-BFC1-08002BE10318}
подключ (он же GUID_DEVCLASS_NET
, определенный в <devguid.h>
, и дополнительно документировано здесь)
Опять же, согласно документации Microsoft,
Каждый подраздел класса содержит другие подразделы, известные как программные ключи (или ключи драйверов) для каждого экземпляра устройства этого класса, установленного в системе. Каждый из этих программных ключей назван с использованием идентификатора экземпляра устройства, который представляет собой четырехзначное порядковое значение по основанию 10. Часть xxxx - это 4-символьное текстовое представление положительного целого числа, начиная с 0.
Таким образом, вы можете перемещать подключи вверх от 0000, 0001, 0002 до количества сетевых адаптеров в вашей системе.
На этом документация заканчивается: я не нашел другой документации о различных значениях реестра и т. Д.
Однако в каждом из этих подключей вы можете найти значения REG_SZ, которые помогут вам связать GetAdaptersInfo()
, MSNdis_EthernetPermanentAddress
, Win32_NetworkAdapter
, и миры идентификаторов экземпляров устройства (и это отвечает на ваш вопрос).
Значения реестра:
DeviceInstanceID
: его значением, что неудивительно, является идентификатор экземпляра устройства.NetCfgInstanceId
: его значение - этоAdapterName
членIP_ADAPTER_INFO
структура, возвращеннаяGetAdaptersInfo()
. Это такжеGUID
членWin32_NetworkAdapter
WMI класс.- Не забывайте
NetworkAddress
один: если здесь существует действующий MAC-адрес, драйвер может сообщить его как MAC-адрес, используемыйGetAdaptersInfo()
,MSNdis_EthernetPermanentAddress
, а такжеIOCTL_NDIS_QUERY_GLOBAL_STATS
!
Тогда, как вы уже сказали, единственная связь между MSNdis_EthernetPermanentAddress
Класс WMI и остальной "мир" - это его InstanceName
член. Вы можете связать это сDescription
член IP_ADAPTER_INFO
структура, возвращенная GetAdaptersInfo()
. Хотя это может быть локализованное имя, оно кажется уникальным для системы (для двух моих интегрированных сетевых адаптеров Marvell к имени второй добавлен " #2").
Заключительное примечание:
Сказав все вышесказанное, пользователь может отключить WMI...