Come ottenere in modo affidabile e veloce l’indirizzo MAC di una scheda di rete in base all’ID istanza del dispositivo

Dato un ID di istanza del dispositivo per una scheda di rete, vorrei conoscere il suo indirizzo MAC. Esempio di ID istanza dispositivo sul mio sistema per scheda Intel Gigabit integrata:

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

Finora, l’algoritmo che ho usato funziona come segue:

  1. Chiama SetupDiGetClassDevs con DIGCF_DEVICEINTERFACE .
  2. Chiama SetupDiEnumDeviceInfo per ottenere il dispositivo restituito in SP_DEVINFO_DATA .
  3. Chiama SetupDiEnumDeviceInterfaces con GUID_NDIS_LAN_CLASS per ottenere un’interfaccia dispositivo.
  4. Chiama SetupDiGetDeviceInterfaceDetail per questa interfaccia di dispositivo restituita. Questo ci porta il percorso del dispositivo come una stringa: \\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852}
  5. A questo punto abbiamo un indirizzo per l’interfaccia del driver della scheda di rete. Aprilo con CreateFile usando il risultato di # 4.
  6. Chiama DeviceIoControl con IOCTL_NDIS_QUERY_GLOBAL_STATS e OID di OID_802_3_PERMANENT_ADDRESS per ottenere l’indirizzo MAC.

Questo di solito funziona, ed è stato usato con successo su un numero piuttosto elevato di macchine. Tuttavia, sembra che poche macchine selezionate abbiano driver di rete che non rispondono correttamente alla richiesta DeviceIoControl al punto # 6; il problema persiste anche dopo aver aggiornato i driver della scheda di rete. Questi sono i più recenti computer basati su Windows 7. In particolare, DeviceIoControl completato correttamente, ma restituisce zero byte invece dei sei byte attesi contenenti l’indirizzo MAC.

Sembra che un indizio si trovi nella pagina MSDN per IOCTL_NDIS_QUERY_GLOBAL_STATS :

Questo IOCTL sarà deprecato nelle versioni successive del sistema operativo. È necessario utilizzare le interfacce WMI per interrogare le informazioni del driver miniport. Per ulteriori informazioni, consultare Supporto NDIS per WMI.

– Forse i più recenti driver di tabs di rete non stanno più implementando questo IOCTL?

Quindi, come devo farlo funzionare? È ansible che ci sia una svista nel mio approccio e sto facendo qualcosa di leggermente sbagliato? O ho bisogno di adottare un approccio molto più diverso? Alcuni approcci alternativi sembrano includere:

  • Query Classe WMI Win32_NetworkAdapter : fornisce le informazioni necessarie ma viene rifiutata a causa di prestazioni orribili. Consultare Sostituzione rapida per class WMI Win32_NetworkAdapter per ottenere l’indirizzo MAC del computer locale
  • Query MSNdis_EthernetPermanentAddress Classe WMI: sembra essere la sostituzione WMI per IOCTL_NDIS_QUERY_GLOBAL_STATS e richiede l’OID direttamente dal driver – e questo funziona sul driver di rete fastidioso. Sfortunatamente, le istanze delle classi restituite forniscono solo l’indirizzo MAC e l’ InstanceName , che è una stringa localizzata come Intel(R) 82567LM-2 Gigabit Network Connection . La query su MSNdis_EnumerateAdapter produce una lista che mette in relazione l’ InstanceName con un DeviceName , come \DEVICE\{28FD5409-15BD-4C06-B62F-004D3A06F852} . Non sono sicuro di come passare da DeviceName all’ID istanza del dispositivo plug-and-play ( PCI\VEN_8086...... ).
  • Chiama GetAdaptersAddresses o GetAdaptersInfo (deprecato). L’unico identificatore non localizzato che posso trovare nel valore restituito è il nome dell’adattatore, che è una stringa come {28FD5409-15BD-4C06-B62F-004D3A06F852} – uguale al DeviceName restituito dalle classi NDIS di WMI. Quindi, ancora una volta, non riesco a capire come collegarlo all’ID istanza del dispositivo. Non sono sicuro se funzionerebbe il 100% delle volte, ad esempio per gli adattatori senza protocollo TCP / IP configurato.
  • Metodo NetBIOS: richiede l’installazione di protocolli specifici sulla scheda, pertanto non funzionerà al 100% del tempo. Generalmente sembra hack-ish, e non è un modo per relazionarsi all’ID di istanza del dispositivo in ogni caso che io conosca. Rifiuterei questo approccio.
  • Metodo di generazione UUID: respinto per ragioni che non approfondirò qui.

Sembra che se potessi trovare un modo per ottenere il “GUID” per la scheda dall’ID dell’istanza del dispositivo, sarei sulla buona strada con uno dei due modi rimanenti di fare le cose. Ma non ho ancora capito come. Altrimenti, l’approccio WMI NDIS sembrerebbe molto promettente.

Ottenere un elenco di tabs di rete e indirizzi MAC è facile e ci sono diversi modi per farlo. Facendolo in un modo veloce che mi consente di collegarlo all’ID istanza del dispositivo è apparentemente difficile …

EDIT: codice di esempio della chiamata IOCTL se aiuta qualcuno (ignorare l’handle hFile trapelato):

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

Il codice non funziona, stampando:

 GetMACAddress: invalid address length of 0. 

Quindi DeviceIoControl restituisce un valore diverso da zero che indica il successo, ma restituisce zero byte.

Ecco un modo per farlo:

  1. Chiama GetAdaptersAddresses per ottenere un elenco di strutture IP_ADAPTER_ADDRESSES
  2. Iterare su ogni adattatore e ottenere il suo GUID dal campo AdapterName (non sono sicuro se questo comportamento è garantito, ma tutti gli adattatori nel mio sistema hanno un GUID qui e la documentazione dice che AdapterName è permanente)
  3. Per ogni adattatore, leggere la chiave del Registro di HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\\Connection\PnPInstanceID da HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\\Connection\PnPInstanceID (se esiste) (ha ottenuto questa idea da qui ; cercare su Google quella chiave sembra essere ben documentata, quindi non è probabile che cambi)
  4. Da questo tasto si ottiene l’ID del dispositivo per l’adattatore (qualcosa come: PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10\4&2B8260C3&0&00E4 )
  5. Fallo per ogni adattatore finché non trovi una corrispondenza. Quando ottieni la tua corrispondenza, torna indietro al IP_ADAPTER_ADDRESSES e guarda il campo PhysicalAddress
  6. Prendi una birra (opzionale)

Non sarebbe Windows se non ci fossero milioni di modi per fare qualcosa!

Ho finito con SetupDiGetDeviceRegistryProperty per leggere SPDRP_FRIENDLYNAME . Se non è stato trovato, allora ho letto SPDRP_DEVICEDESC invece. In definitiva, questo mi dà una stringa come “VirtualBox Host-Only Ethernet Adapter # 2”. Quindi abbinarlo alla proprietà InstanceName nelle classi NDIS WMI (class WMI MSNdis_EthernetPermanentAddress ). Entrambe le proprietà devono essere lette nel caso in cui ci siano più adattatori che condividono lo stesso driver (es. “# 2”, “# 3”, ecc.) – se c’è solo un adattatore allora SPDRP_FRIENDLYNAME non è disponibile, ma se ce n’è più di uno quindi SPDRP_FRIENDLYNAME è necessario per differenziarli.

Il metodo mi rende un po ‘nervoso perché sto confrontando quella che sembra una stringa localizzata, e non c’è documentazione che ho trovato che garantisce quello che sto facendo funzionerà sempre. Sfortunatamente, non ho trovato nessun modo migliore che sia documentato per funzionare.

Un altro paio di metodi alternativi coinvolgono il groveling nelle posizioni di registro non documentate. Un metodo è il metodo SPDRP_DRIVER e l’altro dovrebbe leggere SPDRP_DRIVER , che è il nome di una sottochiave in HKLM\SYSTEM\CurrentControlSet\Control\Class . Sotto la chiave del driver, cerca il valore Linkage\Export che sembra essere DeviceName proprietà MSNdis_EnumerateAdapter class MSNdis_EnumerateAdapter . Ma non ho trovato alcuna documentazione che dica che questi valori possono essere abbinati legalmente. Inoltre, l’unica documentazione che ho trovato su Linkage\Export proveniva dal riferimento del registro di Win2000 e diceva esplicitamente che le applicazioni non dovrebbero fare affidamento su di esso.

Un altro metodo sarebbe quello di guardare la mia domanda originale, passaggio 4: ” SetupDiGetDeviceInterfaceDetail per questa interfaccia di dispositivo restituita”. Il percorso dell’interfaccia del dispositivo può essere effettivamente utilizzato per ribuild il percorso del dispositivo. Inizia con il percorso dell’interfaccia del dispositivo: \\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852} . Quindi rimuovi tutto prima della barra finale, lasciandoti con: {28fd5409-15bd-4c06-b62f-004d3a06f852} . Infine, anteporre \Device\ a questa stringa e confrontarla con le classi NDIS di WMI. Di nuovo, tuttavia, questo sembra non documentato e si basa su un dettaglio di implementazione di un percorso di interfaccia del dispositivo.

Alla fine, gli altri metodi che ho esaminato avevano le loro stesse complicazioni non documentate che sembravano altrettanto gravi delle corrispondenze con le stringhe SPDRP_FRIENDLYNAME / SPDRP_DEVICEDESC . Quindi ho optato per l’approccio più semplice, che era quello di abbinare quelle stringhe contro le classi NDIS WMI.