首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >给定网卡设备实例ID如何可靠、快速地获取网卡MAC地址

给定网卡设备实例ID如何可靠、快速地获取网卡MAC地址
EN

Stack Overflow用户
提问于 2012-09-26 19:11:12
回答 3查看 7.9K关注 0票数 11

给定网卡的设备实例ID,我想知道它的MAC地址。在我的系统上集成Intel Gigabit卡的设备实例ID:

代码语言:javascript
运行
复制
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. 此时,我们有到网卡驱动程序接口的地址。使用#4的结果用CreateFile打开它。
  6. 使用IOCTL_NDIS_QUERY_GLOBAL_STATSOID_802_3_PERMANENT_ADDRESS的OID调用IOCTL_NDIS_QUERY_GLOBAL_STATS来获取MAC地址。

这通常是可行的,并已成功地在相当多的机器上使用。但是,似乎很少有机器的网络驱动程序没有正确响应步骤6中的DeviceIoControl请求;即使在将网卡驱动程序更新为最新的驱动程序之后,问题仍然存在。这些是更新的、基于Windows 7的计算机.具体来说,DeviceIoControl成功完成,但是返回零字节,而不是包含MAC地址的预期的6个字节。

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

这个IOCTL将在以后的操作系统版本中被废弃。您应该使用WMI接口来查询小型端口驱动程序信息。有关更多信息,请参见NDIS对WMI的支持。

--也许较新的网卡驱动程序不再执行此IOCTL?

那么,我该怎么做呢?我的方法有可能有疏忽,我做了一些轻微的错误吗?还是我需要采取一种更不同的方法?一些替代办法似乎包括:

  • 查询Win32_NetworkAdapter WMI类:提供所需的信息,但由于性能糟糕而被拒绝。请参阅用于获取本地计算机MAC地址的NetworkAdapter WMI类
  • 查询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类返回的DeviceName相同。因此,我不知道如何将它与设备实例ID联系起来,我也不确定它是否能100%地工作--例如,对于没有配置TCP/IP协议的适配器。
  • NetBIOS方法:要求在卡上设置特定的协议,这样就不会有100%的时间工作。据我所知,通常情况下似乎是黑客式的,而不是一种与设备实例ID相关联的方式。我会拒绝这种方法。
  • UUID生成方法:由于不详细说明的原因,拒绝。

如果我能找到一种方法从设备实例ID中获取卡的"GUID“,我将很好地使用剩下的两种方法中的一种。但我还没弄清楚怎么回事。否则,WMI NDIS方法看起来将是最有希望的。

获取网卡和MAC地址的列表很容易,有几种方法可以做到。以一种快速的方式让我把它与设备实例ID联系起来显然很难.

编辑: IOCTL调用的示例代码,如果它帮助任何人(忽略泄漏的hFile句柄):

代码语言:javascript
运行
复制
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();
}

代码失败,打印:

代码语言:javascript
运行
复制
GetMACAddress: invalid address length of 0.

因此,DeviceIoControl返回非零表示成功,但随后返回零字节.

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-10-01 15:37:17

最后,我使用SetupDiGetDeviceRegistryProperty来阅读SPDRP_FRIENDLYNAME。如果没有找到,那么我会阅读SPDRP_DEVICEDESC。最终,这会给我一个字符串,比如"VirtualBox主机-只有以太网适配器#2“。然后,我将其与WMI类中的InstanceName属性(MSNdis_EthernetPermanentAddress WMI类)进行匹配。如果有多个适配器共享同一个驱动程序(即"#2“、"#3”等),则必须读取这两个属性--如果只有一个适配器,则SPDRP_FRIENDLYNAME不可用,但如果有多个适配器,则需要SPDRP_FRIENDLYNAME来区分它们。

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

其他几种替代方法包括在无文档的注册表位置中卑躬屈膝。一个方法是spencercw的方法,另一个方法是读取SPDRP_DRIVER,它是HKLM\SYSTEM\CurrentControlSet\Control\Class下一个子键的名称。在驱动键下面,查找Linkage\Export值,该值看起来可能与MSNdis_EnumerateAdapter类的DeviceName属性相匹配。但我没有发现任何文件表明这些值可以合法匹配。此外,我找到的关于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类匹配。然而,这似乎是无文档化的,并且依赖于设备接口路径的实现细节。

最后,我所研究的其他方法都有它们自己的未记录的复杂情况,这听起来至少和匹配SPDRP_FRIENDLYNAME / SPDRP_DEVICEDESC字符串一样严重。因此,我选择了更简单的方法,即只将这些字符串与WMI类匹配。

票数 2
EN

Stack Overflow用户

发布于 2012-09-27 17:26:36

有一种方法可以做到:

  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读取注册表项(如果存在的话)(这是从这里获得的;在谷歌上搜索该键似乎有很好的文档记录,因此不太可能更改)
  4. 从这个键中可以获得适配器的设备ID (类似于:PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10\4&2B8260C3&0&00E4)
  5. 在找到匹配之前,对每个适配器执行此操作。当您得到匹配时,只需返回到IP_ADAPTER_ADDRESSES并查看PhysicalAddress字段
  6. 喝杯啤酒(可选)

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

票数 4
EN

Stack Overflow用户

发布于 2019-07-11 12:20:31

我想您希望获得MAC地址,以便实现某种DRM、库存或分类系统,因为您试图获得永久的MAC地址而不是当前的MAC地址。

您似乎忘记了,甚至还有一个管理上附加的MAC地址(换句话说:一个“强制的”MAC地址)。

一些驱动程序允许您在设备属性页的Advanced (例如:我的Marvell网络适配器允许我这样做)下执行此操作,而另一些驱动程序则不允许您这样做(请阅读:他们不支持该属性)。

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

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

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

我尝试在NETGEAR的注册表中添加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注册表值。如果设置好了,我想最好完全不信任Network,因为需要由驱动程序实现来决定使用不同方法提供给您的是什么。

获取注册表密钥的背景信息:

有关HKLM\SYSTEM\CurrentControlSet\Class键的Microsoft文档

根据该页面上的Microsoft文档,

每个类都有一个子项,它使用安装类的GUID命名。

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

同样,根据微软的文档,

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

因此,您可以遍历从0000,0001,0002到系统中的网络适配器数量的子项。

文档在这里结束:我没有找到任何关于不同注册表值的其他文档,或者类似的文档。

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

书记官处的价值是:

  • DeviceInstanceID:它的值是,毫不奇怪,设备实例ID
  • NetCfgInstanceId:它的值是IP_ADAPTER_INFO结构的AdapterName成员,由GetAdaptersInfo()返回。它也是GUID WMI类的成员。
  • 不要忘记NetworkAddress地址:如果这里存在有效的MAC地址,驱动程序可以将其报告为GetAdaptersInfo()MSNdis_EthernetPermanentAddressIOCTL_NDIS_QUERY_GLOBAL_STATS使用的MAC地址!

然后,正如您已经说过的,MSNdis_EthernetPermanentAddress WMI类与"world“的其他部分之间的唯一连接是它的InstanceName成员。您可以将其关联到IP_ADAPTER_INFO结构的IP_ADAPTER_INFO成员(由GetAdaptersInfo()返回)。虽然它可能是一个本地化的名称,但它对于系统似乎是唯一的(对于我的两个集成的Marvell NIC,第二个在它的名称后面有一个“#2”)。

最后说明:

以上所说的,用户可以选择禁用WMI..。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/12608757

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档