如何在给定设备实例ID的情况下可靠,快速地获取网卡的MAC地址

时间:2012-09-26 19:11:13

标签: c++ windows networking wmi

鉴于网卡的device instance ID,我想知道它的MAC地址。集成Intel千兆卡的系统上的设备实例ID示例:

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

到目前为止,我使用的算法的工作原理如下:

  1. 使用DIGCF_DEVICEINTERFACE致电SetupDiGetClassDevs
  2. 致电SetupDiEnumDeviceInfo以获取SP_DEVINFO_DATA
  3. 中返回的设备
  4. 使用GUID_NDIS_LAN_CLASS致电SetupDiEnumDeviceInterfaces以获取设备界面。
  5. 为此返回的设备界面调用SetupDiGetDeviceInterfaceDetail。这会将设备路径作为字符串:\\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852}
  6. 此时我们有一个网卡驱动程序接口的地址。使用#4。
  7. 的结果,使用CreateFile打开它
  8. 使用DeviceIoControlIOCTL_NDIS_QUERY_GLOBAL_STATS的OID呼叫OID_802_3_PERMANENT_ADDRESS以获取MAC地址。
  9. 这通常有效,并已在相当多的机器上成功使用。但是,似乎很少有机器的网络驱动程序在步骤#6中没有正确响应DeviceIoControl请求;即使将网卡驱动程序更新到最新版本后问题仍然存在。这些是较新的基于Windows 7的计算机。具体来说,DeviceIoControl成功完成,但返回零字节而不是包含MAC地址的预期六个字节。

    一条线索似乎出现在IOCTL_NDIS_QUERY_GLOBAL_STATS的MSDN页面上:

      

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

    - 也许更新的网卡驱动程序不再实现这个IOCTL了?

    那么,我应该怎么做呢?是否有可能在我的方法中存在疏忽并且我做了一些稍微错误的事情?或者我需要采取更加不同的方法吗?一些替代方法似乎包括:

    • 查询Win32_NetworkAdapter WMI类:提供所需信息但由于性能糟糕而被拒绝。请参阅Fast replacement for Win32_NetworkAdapter WMI class for getting MAC address of local computer
    • 查询MSNdis_EthernetPermanentAddress WMI类:似乎是IOCTL_NDIS_QUERY_GLOBAL_STATS的WMI替代品,并直接从驱动程序查询OID - 这一个适用于麻烦的网络驱动程序。不幸的是,返回的类实例仅提供MAC地址和InstanceName,这是一个本地化的字符串,如Intel(R) 82567LM-2 Gigabit Network Connection。查询MSNdis_EnumerateAdapter会产生一个与InstanceNameDeviceName相关的列表,例如\DEVICE\{28FD5409-15BD-4C06-B62F-004D3A06F852}。我不确定如何从DeviceName转到即插即用设备实例ID(PCI\VEN_8086......)。
    • 致电GetAdaptersAddressesGetAdaptersInfo(已弃用)。我可以在返回值中找到的唯一非本地化标识符是适配器名称,它是一个类似{28FD5409-15BD-4C06-B62F-004D3A06F852}的字符串 - 与WMI NDIS类返回的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返回非零表示成功,但随后返回零字节。

3 个答案:

答案 0 :(得分:4)

这是一种方法:

  1. 致电GetAdaptersAddresses以获取IP_ADAPTER_ADDRESSES结构列表
  2. 遍历每个适配器并从AdapterName字段获取其GUID(我不确定这种行为是否得到保证,但我的系统中的所有适配器都有GUID,文档说明{{ 1}}是永久性的)
  3. 对于每个适配器,从AdapterName读取注册表项(如果存在)(从here获得此想法;在Google上搜索该键似乎已有详细记录,因此不太可能更改)
  4. 从此键获取适配器的设备ID(类似于:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\<the adapter GUID>\Connection\PnPInstanceID
  5. 为每个适配器执行此操作,直到找到匹配项为止。获得比赛后,请返回PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10\4&2B8260C3&0&00E4并查看IP_ADAPTER_ADDRESSES字段
  6. 喝啤酒(可选)
  7. 如果没有百万种方法可以做某事,那就不是Windows了!

答案 1 :(得分:2)

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

这个方法让我有点紧张,因为我正在比较看似本地化的字符串,而且没有我发现的文档可以保证我正在做的事情总能奏效。不幸的是,我还没有找到任何更好的方法来记录工作。

其他一些替代方法涉及在未记录的注册表位置进行groveling。一种方法是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 :(得分:0)

我想您想获取MAC地址以便实施某种DRM,清单或分类系统,因为您试图获取永久 MAC地址而不是当前的MAC地址。 / p>

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

但是,所有操作都以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值,那么您也可以在驱动程序属性页中看到新值,并且该新值将立即生效,而无需重新启动(如果您需要从设备属性页更改它)。 以下是使用不同方法读取MAC地址的结果:

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

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

  • 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 documentation about the HKLM\SYSTEM\CurrentControlSet\Class key
根据该页面上的Microsoft文档,

  

每个类都有一个子项,该子项使用   设置类

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

再次,根据Microsoft文档,

  

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

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

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

注册表值是:

  • DeviceInstanceID:毫无疑问,它的值是设备实例ID
  • NetCfgInstanceId:其值为AdapterName返回的IP_ADAPTER_INFO结构的GetAdaptersInfo()成员。它也是GUID WMI类的Win32_NetworkAdapter成员。
  • 别忘了NetworkAddress:如果此处存在有效的MAC地址,驱动程序可以将其报告为GetAdaptersInfo(),{{ 1}}和MSNdis_EthernetPermanentAddress

然后,正如您已经说过的那样,IOCTL_NDIS_QUERY_GLOBAL_STATS WMI类与“世界”其余部分之间的唯一连接是由其MSNdis_EthernetPermanentAddress成员组成的。您可以将其与InstanceName返回的Description结构的IP_ADAPTER_INFO成员相关。尽管它可能是本地化的名称,但对于系统来说似乎是唯一的(对于我的两个集成式Marvell NIC,第二个在其名称后附加一个“#2”)。

最后的笔记:

以上所述,the user could choose to disable WMI...