c # - Hurtig udskiftning af Win32\_NetworkAdapter WMI klasse for at få MAC-adresse til den lokale computer

Indlæg af Hanne Mølgaard Plasc

Problem



TL; DR version af dette spørgsmål: WMI Win32\_NetworkAdapter klasse indeholder oplysninger, jeg har brug for, men er for langsom. Hvad er en hurtigere metode til at få oplysninger til MACAddress, ConfigManagerErrorCode og PNPDeviceID kolonner på Windows? [5]


Jeg skal hente oplysninger for vedhæftede netværksadaptere, så jeg kan få en MAC-adresse til unikt at identificere den lokale Microsoft Windows-computer. Klassen WMI Win32\_NetworkAdapter synes at have de oplysninger, jeg leder efter. MACAddress, ConfigManagerErrorCode og PNPDeviceID kolonner er de eneste, jeg virkelig har brug for: [6]



  • MACAddress: MAC-adressen (mål for denne operation)

  • ConfigManagerErrorCode: Tillader mig at afgøre, om adapteren er aktiveret og kører eller ej. (Hvis den er deaktiveret, skal jeg bruge en MAC-adresse, der tidligere er cachelad i min app, hvis den er tilgængelig).

  • PNPDeviceID: Ved at kontrollere et prefix af 'PCI' (og eventuelt andre grænseflader, hvis det er nødvendigt) kan jeg filtrere ud ikke-fysiske adaptere, hvoraf der er flere i min Windows 7-boks (herunder virtuelle adaptere, som for VMware/VirtualBox).



Min plan var at filtrere ud ikke-fysiske enheder ved hjælp af PNPDeviceID. Så ville jeg bruge MACAddress kolonnen for eventuelle resterende tabel poster (gemme adressen til en cache). Når enheden er deaktiveret (som muligvis angivet med en ikke-nul ConfigManagerErrorCode) og MACAddress er null, kan jeg bruge en tidligere set MACAddress til den pågældende enhed fra min cache.


Du kan se indholdet af denne tabel på min Windows 7-computer. Du kan se der er masser af junk derinde, men kun en post med et 'PCI' PNPDeviceID.


wmic:rootcli>NIC GET Caption, ConfigManagerErrorCode, MACAddress, PNPDeviceID
Caption                                                   ConfigManagerErrorCode  MACAddress         PNPDeviceID
[00000000] WAN Miniport (SSTP)                            0                                          ROOTMS\_SSTPMINIPORT000
[00000001] WAN Miniport (IKEv2)                           0                                          ROOTMS\_AGILEVPNMINIPORT000
[00000002] WAN Miniport (L2TP)                            0                                          ROOTMS\_L2TPMINIPORT000
[00000003] WAN Miniport (PPTP)                            0                                          ROOTMS\_PPTPMINIPORT000
[00000004] WAN Miniport (PPPOE)                           0                                          ROOTMS\_PPPOEMINIPORT000
[00000005] WAN Miniport (IPv6)                            0                                          ROOTMS\_NDISWANIPV6000
[00000006] WAN Miniport (Network Monitor)                 0                                          ROOTMS\_NDISWANBH000
[00000007] Intel(R) 82567LM-2 Gigabit Network Connection  0                       00:1C:C0:B0:C4:89  PCIVEN\_8086&DEV\_10CC&SUBSYS\_00008086&REV\_003&33FD14CA&0&C8
[00000008] WAN Miniport (IP)                              0                                          ROOTMS\_NDISWANIP000
[00000009] Microsoft ISATAP Adapter                       0                                          ROOT*ISATAP000
[00000010] RAS Async Adapter                              0                       20:41:53:59:4E:FF  SW{EEAB7790-C514-11D1-B42B-00805FC1270E}ASYNCMAC
[00000011] Microsoft Teredo Tunneling Adapter             0                                          ROOT*TEREDO000
[00000012] VirtualBox Bridged Networking Driver Miniport  0                       00:1C:C0:B0:C4:89  ROOTSUN\_VBOXNETFLTMP000
[00000013] VirtualBox Host-Only Ethernet Adapter          0                       08:00:27:00:C4:A1  ROOTNET000
[00000014] Microsoft ISATAP Adapter                       0                                          ROOT*ISATAP001
[00000015] VMware Virtual Ethernet Adapter for VMnet1     0                       00:50:56:C0:00:01  ROOTVMWARE000
[00000016] Microsoft ISATAP Adapter                       0                                          ROOT*ISATAP002
[00000017] VMware Virtual Ethernet Adapter for VMnet8     0                       00:50:56:C0:00:08  ROOTVMWARE001
[00000018] Microsoft ISATAP Adapter                       0                                          ROOT*ISATAP003


(Hvis jeg deaktiverer min fysiske adapter, går MACAddress-kolonnen til null, og ConfigManagerErrorCode ændres til ikke-nul).


Desværre er denne klasse simpelthen for langsom. Enhver forespørgsel på Win32\_NetworkAdapter tager konsekvent 0,3 sekunder på min relativt moderne Windows 7 Core i7-baserede computer. Så bruger dette vil tilføje endnu 0,3 sekunder til applikation opstart (eller værre), som jeg finder uacceptabelt. Dette skyldes især, at jeg ikke kan tænke på en enkelt gyldig grund, hvorfor det skal tage så lang tid at finde ud af, hvad MAC-adresser og plug-and-play-enheds-id'er er på den lokale computer.


Søgning efter andre metoder til at få en MAC-adresse gav GetAdaptersInfo og de nyere GetAdaptersAddresses funktioner. De har ikke den 0,3 sekundære straf, som WMI pålægger. Disse funktioner er dem, der benyttes af .NET Framework 's NetworkInterface klasse (som bestemt ved at undersøge .NET source code) og 'ipconfig' kommandolinjeværktøjet (som bestemt ved hjælp af Dependency Walker). [7] [8] [9] [10]


Jeg lavede et simpelt eksempel i C #, der viser alle netværksadaptere ved hjælp af klassen NetworkInterface. Desværre synes at bruge disse API'er at have to mangler:



  • Disse API'er indeholder ikke engang liste over deaktiverede netværksadaptere til at begynde med. Det betyder, at jeg ikke kan se MAC-adressen til en deaktiveret adapter fra min cache.

  • Jeg kan ikke se, hvordan jeg kan få PNPDeviceID til at filtrere bort ikke-fysiske adaptere.



Mit spørgsmål er: Hvilken metode kan jeg bruge til at få MAC-adressen til den lokale computers fysiske adaptere (om det er aktiveret eller ej) i løbet af nogle få millisekunder?


(Jeg har erfaring med både C # og C ++ og har det fint med at læse andre sprog, så jeg er virkelig ikke sikker på hvilket sprog der kan bruges i svar).


REDIGER: Som svar på Alex Ks forslag til at bruge returnering kun øjeblikkelig og fremad, og også for at give nogle eksempler på WMI-kode for det jeg laver - her er nogle C # -koder, der viser de interessante kolonner :


    public static void NetTest() {
        System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
        EnumerationOptions opt = new EnumerationOptions();
        // WMI flag suggestions from Alex K:
        opt.ReturnImmediately = true;
        opt.Rewindable = false;
        ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\cimv2", "select MACAddress, PNPDeviceID, ConfigManagerErrorCode from Win32\_NetworkAdapter", opt);
        foreach (ManagementObject obj in searcher.Get()) {
            Console.WriteLine("=========================================");
            foreach (PropertyData pd in obj.Properties) {
                Console.WriteLine("{0} = {1}", pd.Name, pd.Value);
            }
        }
        Console.WriteLine(sw.Elapsed.TotalSeconds);
    }


Jeg kaldte denne funktion 3 gange, og hver gang fik jeg ca. 0,36 sekunder trykt på den sidste linje. Så de foreslåede flag synes ikke at have nogen effekt: positivt eller negativt. Det er ikke så overraskende, da svaret på Hvordan man laver fremad kun skrivebeskyttede WMI-forespørgsler i C #? Synes at angive, at ingen ændring i ydeevne vil blive overholdt, medmindre der er et stort antal poster (f.eks. hundreder til tusinder), hvilket ikke er tilfældet med Win32\_NetworkAdapter-tabellen.


EDIT 2: Flere svar er blevet foreslået for at bruge SendARP fra IP hjælper API (dette er det samme API, der har funktionen GetAdaptersInfo). Hvilke fordele giver dette over GetAdaptersInfo for at finde den lokale MAC-adresse? Jeg kan ikke tænke på nogen - på overfladen synes GetAdaptersInfo at returnere et mere grundigt sæt information end SendARP gør for lokale adaptere. Nu når jeg tænker over det, tror jeg, at en stor del af mit spørgsmål centrerer over begrebet opregning: hvilke adaptere findes der på computeren i første omgang? SendARP udfører ikke opregning: det forudsætter at du allerede kender IP-adressen på den adapter, du vil have en MAC til. Jeg skal finde ud af, hvilke adaptere der findes på systemet. Nogle problemer dette rejser: [12]



  • Hvad sker der, hvis netværkskabel er frakoblet? Dette ville være meget almindeligt på en bærbar computer, for eksempel (unplugged Ethernet, frakoblet WiFi-kort). Jeg forsøgte at bruge NetworkInterface.GetAllNetworkInterfaces () og notere alle unicast-adresserne ved hjælp af GetIPProperties (). UnicastAddresses, når mediet er frakoblet. Ingen adresser er angivet af Windows, så jeg kan ikke tænke på nogen adresse, der kunne sendes til SendARP. Intuitivt er det fornuftigt, at en unplugged adapter stadig har en fysisk adresse, men ingen IP-adresse (da den ikke er på en netværk med en DHCP-server).

  • Hvilket bringer mig til: Hvordan får jeg en liste over lokale IP-adresser til test med SendARP?

  • Hvordan får jeg PNPDeviceID (eller lignende ID, som kan bruges til at filtrere ikke-fysiske adaptere) til hver adapter?

  • Hvordan viser jeg deaktiverede adaptere, så jeg kan slå op en MAC-adresse fra min cache (dvs. MAC-adressen jeg fandt, da den sidst blev aktiveret)?



Disse problemer ser ikke ud til at blive behandlet af SendARP, og er hovedårsagen til, at jeg stillede dette spørgsmål (ellers bruger jeg GetAdaptersInfo og fortsætter med ting ...). [13]

Bedste reference


Jeg afviklede opskæring WMI helt ud af ligningen og gjorde en betydelig forbedring, mens jeg stadig fik de oplysninger, jeg ønskede. Som bemærket med WMI tog det> 0,30 sekunder for at få resultater. Med min version kan jeg få de samme oplysninger om cirka 0,01 sekunder.


Jeg brugte installationsprogrammet API, konfigurationsadministrator API og lavede derefter OID-anmodninger direkte på NDIS-netværksdriveren for at få MAC-adressen. Installations-API'en virker ubemærket langsomt, især når man får ting som ejendomsværdier. Det er absolut nødvendigt at holde setup API-opkald på et minimum. (Du kan faktisk se lige hvor dårlig det er ved at se på, hvor lang tid det tager at indlæse fanen 'Detaljer' på en enhed i Enhedshåndtering).


En gætte på hvorfor WMI var så langsomt: Jeg bemærkede, at WMIs Win32\_NetworkAdapter altid tog samme tid uanset hvilken delmængde af egenskaber jeg forespurgte. Det ser ud til, at WMI Win32\_NetworkAdapter klasse programmører var dovne og ikke optimerede deres klasse til kun indsamle ønskede oplysninger som andre WMI-klasser gør. De samler nok alle oplysninger, uanset om de bliver bedt om det eller ej. De afhænger sandsynligvis væsentligt af Setup API for at gøre dette, og de overdrevne opkald til den langsomme Setup API for at få uønsket information er, hvad der gør det så langsomt.


Højt overblik over hvad jeg gjorde:



  1. Brug SetupDiGetClassDevs til at få alle netværksenheder, der er til stede på systemet.

  2. Jeg filtrerer ud alle resultater, der ikke har en opregner af 'PCI' (brug SetupDiGetDeviceRegistryProperty med SPDRP\_ENUMERATOR\_NAME for at få opkalderen).

  3. I resten kan jeg bruge CM\_Get\_DevNode\_Status til at få enhedens status og fejlkode. Alle enheder med statuskoder, der kan flyttes, filtreres ud.

  4. Hvis DN\_HAS\_PROBLEM er indstillet sådan, at der er en fejlkode uden fejl, er enheden sandsynligvis deaktiveret (eller har et andet problem). Føreren er ikke ladet, så vi kan ikke stille en anmodning til chaufføren. Derfor lægger jeg i dette tilfælde MAC-adressen til netværkskortet fra et cache, som jeg vedligeholder.

  5. En forældreenhed kan fjernes, så jeg filtrerer dem ud ved at rekursivt undersøge enheds træet ved hjælp af CM\_Get\_Parent og CM\_Get\_DevNode\_Status for at søge efter flyttbare enheder til moderne.

  6. Alle resterende enheder er ikke-flytbare netværkskort på PCI-bussen.

  7. For hver netværksenhed bruger jeg SetupDiGetClassDevs med GUID\_NDIS\_LAN\_CLASS GUID og DIGCF\_DEVICEINTERFACE flag for at få dens grænseflader (dette virker kun, hvis enheden er aktiveret/ikke har noget problem).

  8. Brug IOCTL\_NDIS\_QUERY\_GLOBAL\_STATS med OID\_802\_3\_PERMANENT\_ADDRESS på driverens grænseflade for at få den permanente MAC-adresse. Gem det i cachen og returner derefter.



Resultatet er en robust indikation af MAC-adresser på pc'en, som skal være immun mod 'falske' netværkskort lavet af VMware, VirtualBox, stort set immune til netværkskort, der er midlertidigt deaktiveret, og immun over for forbigående netværkskort, der er vedhæftet via USB, ExpressCard, PC-kort eller enhver fremtidig flytbar grænseflade.


EDIT: IOCTL\_NDIS\_QUERY\_GLOBAL\_STATS understøttes ikke af alle netværkskort. Det store flertal fungerer, men nogle Intel-kort gør det ikke. Se Sådan får du pålideligt og hurtigt adgang til MAC-adressen på et netværkskort givet sin enhedsinstans ID

Andre referencer 1


Du skal kunne få alt hvad du behøver fra navnet System.Net. For eksempel løftes følgende prøve fra MSDN og gør hvad du bad om i den originale version af spørgsmålet. Den viser de fysiske adresser på alle grænseflader på den lokale computer. [15]


public static void ShowNetworkInterfaces()
{
    IPGlobalProperties computerProperties = IPGlobalProperties.GetIPGlobalProperties();
    NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
    Console.WriteLine("Interface information for {0}.{1}     ",
            computerProperties.HostName, computerProperties.DomainName);
    if (nics == null || nics.Length < 1)
    {
        Console.WriteLine("  No network interfaces found.");
        return;
    }

    Console.WriteLine("  Number of interfaces .................... : {0}", nics.Length);
    foreach (NetworkInterface adapter in nics)
    {
        IPInterfaceProperties properties = adapter.GetIPProperties(); //  .GetIPInterfaceProperties();
        Console.WriteLine();
        Console.WriteLine(adapter.Description);
        Console.WriteLine(String.Empty.PadLeft(adapter.Description.Length,'='));
        Console.WriteLine("  Interface type .......................... : {0}", adapter.NetworkInterfaceType);
        Console.Write("  Physical address ........................ : ");
        PhysicalAddress address = adapter.GetPhysicalAddress();
        byte[] bytes = address.GetAddressBytes();
        for(int i = 0; i< bytes.Length; i++)
        {
            // Display the physical address in hexadecimal.
            Console.Write("{0}", bytes[i].ToString("X2"));
            // Insert a hyphen after each byte, unless we are at the end of the 
            // address.
            if (i != bytes.Length -1)
            {
                 Console.Write("-");
            }
        }
        Console.WriteLine();
    }
}