How to reliably and quickly get the MAC address of a network card, given the device instance ID

Given the device instance ID for the network card, I would like to know its MAC address. An example of a device instance identifier on my system for an integrated Intel Gigabit card:

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

So far, the algorithm I use has worked as follows:

It usually works and is successfully used on a fairly large number of machines. However, it seems that very few machines have network drivers that do not respond properly to the DeviceIoControl request in step # 6; the problem persists even after updating the network card drivers to the latest version. These are new Windows 7 based computers. In particular, DeviceIoControl succeeds, but returns zero bytes instead of the expected six bytes containing the MAC address.

There seems to be a link on the MSDN page for IOCTL_NDIS_QUERY_GLOBAL_STATS :

This IOCTL will be deprecated in later versions of the operating system. You must use the WMI interfaces to request information about the miniport driver. For more details, see NDIS Support for WMI.

- Perhaps the new network card drivers no longer implement this IOCTL?

So how do I get this to work? Is it possible that there is oversight in my approach and I am doing something wrong? Or do I need to use a much more diverse approach? Some alternative approaches seem to include:

  • Win32_NetworkAdapter request Win32_NetworkAdapter class: provides the necessary information, but is rejected due to terrible performance. See Quick Replace Win32_NetworkAdapter WMI Class for MAC Address of Local Computer
  • Query MSNdis_EthernetPermanentAddress WMI class: it appears to be a WMI replacement for IOCTL_NDIS_QUERY_GLOBAL_STATS and requests the OID directly from the driver - and this one works on an unpleasant network driver. Unfortunately, the returned class instances provide only the MAC address and InstanceName , which is a localized string like Intel(R) 82567LM-2 Gigabit Network Connection . The MSNdis_EnumerateAdapter gives a list that associates InstanceName with a DeviceName , for example \DEVICE\{28FD5409-15BD-4C06-B62F-004D3A06F852} . I'm not sure how to go from DeviceName to the plug-and-play device instance id ( PCI\VEN_8086...... ).
  • Call to GetAdaptersAddresses or GetAdaptersInfo (deprecated). The only non-localized identifier I can find in the return value is the name of the adapter, which is a string like {28FD5409-15BD-4C06-B62F-004D3A06F852} - the same as the DeviceName returned by the WMI NDIS classes. So, I can’t figure out how to associate it with the device instance id. I'm not sure that it will work in 100% of cases - for example. for adapters without TCP / IP.
  • NetBIOS method: requires certain protocols that will be installed on the card, so it will not work for 100% of the time. This is usually a hack-ish, not a way to associate the identifier of a device instance anyway I know of. I would reject this approach.
  • UUID generation method: rejected for reasons that I will not stop here.

It seems like if I could find a way to get the "GUID" for the card from the device instance identifier, I would be fine with one of the two remaining ways to do something. But I still do not understand how to do this. Otherwise, the WMI NDIS approach seems to be the most promising.

Getting a list of network cards and MAC addresses is easy, and there are several ways to do this. Doing this in a quick way that allows me to associate it with a device instance id seems to be difficult ...

EDIT: An example IOCTL call code if it helps someone (ignore a leaked hFile descriptor):

 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(); } 

Code failed, print:

 GetMACAddress: invalid address length of 0. 

Thus, DeviceIoControl returns a non-zero value indicating success, but then returns zero bytes.

+8
source share
3 answers

I ended up using SetupDiGetDeviceRegistryProperty to read SPDRP_FRIENDLYNAME . If this is not found, I will read SPDRP_DEVICEDESC . Ultimately, this gives me a line such as "VirtualBox Host-Only Ethernet Adapter # 2". Then I map this to the InstanceName property in the WDNDIS classes ( MSNdis_EthernetPermanentAddress WMI class). Both properties should be read if there are several adapters that use the same driver (for example, "# 2", "# 3", etc.) - if there is only one adapter, then SPDRP_FRIENDLYNAME not available, but if there are more than one, then SPDRP_FRIENDLYNAME is required to differentiate them.

This method is a little nervous because I am comparing what looks like a localized string, and there is no documentation that I found that the guarantees that I am doing will always work. Unfortunately, I did not find the best ways that are documented to work.

A couple of other alternative methods include suppressing undocumented registration locations. One method is the spencercw method, and the other is to read SPDRP_DRIVER , which is the name of the subkey in HKLM\SYSTEM\CurrentControlSet\Control\Class . Under the driver key, find the Linkage\Export value, which seems to be mapped to the DeviceName property of the DeviceName class. But I can not find the documentation that says that these values ​​can be legally agreed. In addition, the only documentation I found about Linkage\Export was from a link to the Win2000 registry and explicitly stated that applications should not rely on it.

Another method would be to look at my initial question, step 4: " SetupDiGetDeviceInterfaceDetail for this interface with the returned device." In fact, the device interface path can be used to restore the path to the device. Start with the device interface path: \\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852} . Then delete everything to the last slash, leaving you with: {28fd5409-15bd-4c06-b62f-004d3a06f852} . Finally, add \Device\ to this line and map it to the WDNDIS classes. Again, this seems to be undocumented and relies on the implementation detail of the device interface path.

In the end, the other methods I explored had their undocumented complications, which sounded at least as serious as string matching SPDRP_FRIENDLYNAME / SPDRP_DEVICEDESC . So I chose a simpler approach, which was just to map these strings to the WMI NDIS classes.

+2
source

Here is one way to do this:

  • Call GetAdaptersAddresses to get a list of IP_ADAPTER_ADDRESSES structs
  • Iterate over each adapter and get its GUID from the AdapterName field (I'm not sure that this behavior is guaranteed, but all the adapters on my system have a GUID here, and the documentation says that AdapterName constant)
  • For each adapter, read the registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\<the adapter GUID>\Connection\PnPInstanceID (if it exists) (got this idea from here ; search on Google this key seems to be well documented, so it’s unlikely to change).
  • From this key, you will get the device identifier for the adapter (something like: PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10\4&2B8260C3&0&00E4 )
  • Do this for each adapter until you find a match. When you get your match, go back to IP_ADAPTER_ADDRESSES and look in the PhysicalAddress field
  • Get a beer (optional)

It would not be Windows if there were no million ways to do something!

+4
source

I assume that you want to get the MAC address in order to implement some kind of DRM, inventory or classification system, since you tried to get a permanent MAC address instead of the current one.

You seem to have forgotten that there is even an administratively superimposed MAC address (in other words: a "forced" MAC address).
Some drivers allow you to do this on the Device Properties page of the Advanced tab (for example, my Marvell network adapter allows me to do this), while others do not allow you to do this (read: they do not support this property).

However, all this ends with a registry value: HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\xxxx\NetworkAddress with type REG_SZ . Here you can set a different MAC address, different from the original one, in the form of "01020304abcd" (6 bytes, normal hexadecimal without : delimiters or 0x prefix). After you install it, restart the computer, and when you turn it on, the new MAC address will work.

I have a motherboard with two integrated Marvell network cards and a NETGEAR USB WiFi network interface. Marvell one supports changing the MAC address: if you set the NetworkAddress value in the registry, you will also see the new value on the driver properties page, and it will take effect immediately, without having to restart (if you change it from the device's Property page). The following are the results of reading the MAC address using various methods:

  • GetAdaptersInfo : New MAC Address
  • IOCTL_NDIS_QUERY_GLOBAL_STATS : original MAC address
  • MSNdis_EthernetPermanentAddress : Source MAC Address

I tried to add the NetworkAddress value to the registry for the NETGEAR USB WiFi network adapter, and the result was the following:

  • GetAdaptersInfo : New MAC Address
  • IOCTL_NDIS_QUERY_GLOBAL_STATS : new MAC address
  • MSNdis_EthernetPermanentAddress : New MAC Address

The original MAC addresses have disappeared.

Thus, in order not to be deceived by the "malicious" user, you always need to check the registry value HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\xxxx\NetworkAddress . If this is installed, I think it’s better not to trust this network adapter at all, since it is the driver implementation that decides what will be presented to you using various methods.

Some information to get this registry key:

Microsoft Documentation on HKLM \ SYSTEM \ CurrentControlSet \ Class
According to Microsoft documentation on this page,

There is a subkey for each class that is named using the installation class GUID

Therefore, we select the subsection {4D36E972-E325-11CE-BFC1-08002BE10318} (aka GUID_DEVCLASS_NET , defined in <devguid.h> and more <devguid.h> described here )

Again, according to Microsoft documentation,

Each subsection of a class contains other subsections, known as software keys (or driver keys) for each device instance of this class installed on the system. Each of these software keys is assigned a name using the device instance identifier, which is a four-digit ordinal value with a base of-10. The xxxx part is a 4-character text representation of a positive integer, starting at 0

Thus, you can navigate through the subsections from 0000, 0001, 0002 to the number of network adapters in your system.
The documentation stops here: I did not find any other documentation about various registry values ​​or something like that.

However, in each of these subsections, you can find REG_SZ values ​​that can help you link GetAdaptersInfo() , MSNdis_EthernetPermanentAddress , Win32_NetworkAdapter and Device Instance ID worlds (and this answers your question).

Registry Values:

  • DeviceInstanceID : its value, which is not surprising, is the device instance ID
  • NetCfgInstanceId : its value is the AdapterName member of the IP_ADAPTER_INFO structure returned by GetAdaptersInfo() . He is also a member of the Win32_NetworkAdapter WMI class GUID .
  • Remember NetworkAddress : if there is a valid MAC address, the driver can report it as the MAC address used by GetAdaptersInfo() , MSNdis_EthernetPermanentAddress and IOCTL_NDIS_QUERY_GLOBAL_STATS !

Then, as you said, the only connection between MSNdis_EthernetPermanentAddress WMI MSNdis_EthernetPermanentAddress and the rest of the "world" is made by its member InstanceName . You can associate it with the Description member of the IP_ADAPTER_INFO structure returned by GetAdaptersInfo() . Although it may be a localized name, it seems unique to the system (for my two integrated Marvell network adapters, "# 2" is added to the second name).

Final note:

Having said all of the above, the user can disable WMI ...

0
source

All Articles