11

给定网卡的设备实例 ID,我想知道它的 MAC 地址。我的系统上用于集成英特尔千兆位卡的示例设备实例 ID:

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

到目前为止,我使用的算法如下:

  1. SetupDiGetClassDevs用调用DIGCF_DEVICEINTERFACE
  2. 调用SetupDiEnumDeviceInfo以获取返回的设备SP_DEVINFO_DATA
  3. 调用SetupDiEnumDeviceInterfaceswithGUID_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. 调用DeviceIoControlIOCTL_NDIS_QUERY_GLOBAL_STATSOIDOID_802_3_PERMANENT_ADDRESS以获取 MAC 地址。

这通常有效,并且已在相当多的机器上成功使用。但是,似乎极少数机器的网络驱动程序无法正确响应DeviceIoControl步骤#6 中的请求;即使将网卡驱动程序更新到最新版本,问题仍然存在。这些是较新的基于 Windows 7 的计算机。具体来说,DeviceIoControl成功完成,但返回零字节而不是包含 MAC 地址的预期六个字节。

MSDN页面上似乎有一条线索IOCTL_NDIS_QUERY_GLOBAL_STATS

此 IOCTL 将在以后的操作系统版本中弃用。您应该使用 WMI 接口来查询微型端口驱动程序信息。有关详细信息,请参阅 NDIS 对 WMI 的支持。

——也许更新的网卡驱动不再实现这个 IOCTL?

那么,我应该如何让这个工作?我的方法是否可能存在疏忽,我做错了什么?还是我需要采取更不同的方法?一些替代方法似乎包括:

  • 查询Win32_NetworkAdapterWMI 类:提供所需信息,但由于性能糟糕而被拒绝。请参阅快速替换 Win32_NetworkAdapter WMI 类以获取本地计算机的 MAC 地址
  • 查询MSNdis_EthernetPermanentAddressWMI 类:似乎是IOCTL_NDIS_QUERY_GLOBAL_STATS直接从驱动程序中查询 OID 的 WMI 替代品 - 这个适用于麻烦的网络驱动程序。不幸的是,返回的类实例只提供 MAC 地址和InstanceName,这是一个本地化字符串,如Intel(R) 82567LM-2 Gigabit Network Connection. 查询MSNdis_EnumerateAdapter产生一个与 a 相关的列表InstanceNameDeviceName比如\DEVICE\{28FD5409-15BD-4C06-B62F-004D3A06F852}。我不确定如何从DeviceName即插即用设备实例 ID ( PCI\VEN_8086......) 转到。
  • 调用GetAdaptersAddressesGetAdaptersInfo(已弃用)。我可以在返回值中找到的唯一非本地化标识符是适配器名称,它是一个类似于 - 的字符串,与 WMI NDIS 类返回的{28FD5409-15BD-4C06-B62F-004D3A06F852}相同。DeviceName再说一次,我不知道如何将它与设备实例 ID 关联起来。我不确定它是否会在 100% 的时间内工作——例如,对于没有配置 TCP/IP 协议的适配器。
  • NetBIOS 方法:需要在卡上设置特定协议,因此无法 100% 工作。通常看起来像hack-ish,而不是我所知道的与设备实例ID相关的方法。我会拒绝这种方法。
  • UUID 生成方法:拒绝,原因我这里不再详述。

似乎如果我能找到一种方法从设备实例 ID 中获取卡的“GUID”,那么剩下的两种做事方式中的一种我就可以顺利完成。但我还没弄清楚怎么做。否则,WMI NDIS 方法似乎最有前途。

获取网卡和 MAC 地址列表很容易,并且有几种方法可以做到。以一种让我将其与设备实例 ID 关联起来的快速方式进行操作显然很难......

编辑: 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 返回非零表示成功,但随后返回零字节。

4

3 回答 3

4

这是一种方法:

  1. 调用GetAdaptersAddresses以获取IP_ADAPTER_ADDRESSES结构列表
  2. 遍历每个适配器并从AdapterName字段中获取其 GUID(我不确定这种行为是否得到保证,但我系统中的所有适配器在此处都有一个 GUID,并且文档说这AdapterName是永久性的)
  3. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\<the adapter GUID>\Connection\PnPInstanceID对于每个适配器,从(如果存在)读取注册表项(从这里得到这个想法;在 Google 上搜索该密钥似乎有据可查,因此不太可能改变)
  4. 从此密钥中,您可以获得适配器的设备 ID(类似于PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10\4&2B8260C3&0&00E4:)
  5. 对每个适配器执行此操作,直到找到匹配项。当你拿到你的比赛时,回到球场IP_ADAPTER_ADDRESSES看看PhysicalAddress
  6. 喝啤酒(可选)

如果没有一百万种方法来做某事,就不会是 Windows!

于 2012-09-27T17:26:36.463 回答
2

我最终使用SetupDiGetDeviceRegistryPropertyto read SPDRP_FRIENDLYNAME。如果没有找到,那么我改为阅读SPDRP_DEVICEDESC。最终,这给了我一个像“VirtualBox Host-Only Ethernet Adapter #2”这样的字符串。MSNdis_EthernetPermanentAddress然后,我将其与 WMI NDIS 类( WMI 类)中的 InstanceName 属性进行匹配。如果有多个适配器共享同一个驱动程序(即“#2”、“#3”等),则必须读取这两个属性 - 如果只有一个适配器SPDRP_FRIENDLYNAME,则不可用,但如果有多个适配器,则SPDRP_FRIENDLYNAME需要区分它们。

该方法让我有点紧张,因为我正在比较看起来像本地化字符串的内容,并且我发现没有任何文档可以保证我正在做的事情总是有效的。不幸的是,我也没有找到任何更好的方法来记录工作。

其他几种替代方法涉及在未记录的注册表位置中卑躬屈膝。一种方法是 spencercw 的方法,另一种是 read 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 类进行匹配。

于 2012-10-01T15:37:17.410 回答
2

我猜您想获取 MAC 地址以实施某种 DRM、库存或分类系统,因为您尝试获取永久MAC 地址而不是当前 MAC 地址。

您似乎忘记了,甚至还有一个管理上强加的 MAC 地址(换句话说:“强制”MAC 地址)。
一些驱动程序允许您从“设备属性”页面的“高级”选项卡下执行此操作(例如:我的 Marvell 网络适配器让我执行此操作),而其他一些驱动程序不允许您执行此操作(阅读:它们不支持该属性)。

但是,这一切都以 Registry value: 结尾HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\xxxx\NetworkAddress,带有一个REG_SZ类型。您可以在此处设置与原始 MAC 地址不同的 MAC 地址,格式为“01020304abcd”(6 字节,纯十六进制,无:分隔符或0x前缀)。设置好后,重启机器,上电后新的MAC地址生效。

我碰巧有一块带有两个 Marvell 集成 NIC 和一个 NETGEAR USB WiFi NIC 的主板。Marvell 支持更改 MAC 地址:如果NetworkAddress在 Registry 中设置了值,在驱动属性页面也可以看到新的值,并且立即生效,无需重新启动(如果从 device 属性中更改)页)。下面是不同方法读取MAC地址的结果:

  • GetAdaptersInfo: 新的 MAC 地址
  • IOCTL_NDIS_QUERY_GLOBAL_STATS:原始MAC地址
  • MSNdis_EthernetPermanentAddress:原始MAC地址

我尝试NetworkAddress在 NETGEAR USB WiFi NIC 的注册表中添加值,结果是:

  • GetAdaptersInfo: 新的 MAC 地址
  • IOCTL_NDIS_QUERY_GLOBAL_STATS: 新的 MAC 地址
  • MSNdis_EthernetPermanentAddress: 新的 MAC 地址

原来的MAC地址不见了。

因此,为了不被“恶意”用户愚弄,您始终需要检查HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\xxxx\NetworkAddress注册表值。如果已设置,我想最好根本不信任该网络适配器,因为由驱动程序实现决定使用不同方法呈现给您的内容。

获取该注册表项的一些背景:

有关 HKLM\SYSTEM\CurrentControlSet\Class 键
的 Microsoft 文档根据该页面上的 Microsoft 文档,

每个类都有一个使用安装程序类的 GUID 命名的子项

所以我们选择{4D36E972-E325-11CE-BFC1-08002BE10318}子键(又名GUID_DEVCLASS_NET,在 中定义,并在此处<devguid.h>进一步记录)

同样,根据 Microsoft 文档,

每个类子项包含其他子项,称为软件密钥(或驱动程序密钥),用于系统中安装的该类的每个设备实例。这些软件密钥中的每一个都使用设备实例 ID 命名,该 ID 是一个以 10 为底的四位序数值xxxx部分是正整数的 4 字符文本表示,从 0 开始

因此,您可以遍历从 0000、0001、0002 到系统中网络适配器数量的子键。
文档到此为止:我没有找到有关不同注册表值等的任何其他文档。

但是,在每个子项中,您都可以找到 REG_SZ 值,这些值可以帮助您链接GetAdaptersInfo()MSNdis_EthernetPermanentAddressWin32_NetworkAdapter和 Device Instance ID 世界(这回答了您的问题)。

注册表值是:

  • DeviceInstanceID:毫无疑问,它的值是设备实例 ID
  • NetCfgInstanceId: 它的值是结构的AdapterName成员IP_ADAPTER_INFO,由 . 返回GetAdaptersInfo()。它也是WMI 类的GUID成员。Win32_NetworkAdapter
  • 不要忘记NetworkAddress:如果此处存在有效的 MAC 地址,驱动程序可能会将GetAdaptersInfo()其报告为、MSNdis_EthernetPermanentAddressIOCTL_NDIS_QUERY_GLOBAL_STATS!正在使用的 MAC 地址。

MSNdis_EthernetPermanentAddress然后,正如您已经说过的, WMI 类与“世界”其他部分之间的唯一联系是它的InstanceName成员。您可以将其与结构的Description成员相关联IP_ADAPTER_INFO,由GetAdaptersInfo(). 虽然它可能是一个本地化名称,但对于系统来说似乎是唯一的(对于我的两个集成 Marvell NIC,第二个在其名称后附加了“#2”)。

最后说明:

综上所述,用户可以选择禁用 WMI ...

于 2019-07-11T12:20:31.723 回答