Programmatically retrieving MAC Address of IP-less Network Adapter

16 03 2011

There are many developers asking how to programmatically retrieve the MAC address of a network adapter on Windows. There are also many answer showing various solutions ranging from parsing ipconfig /ALL output, through using the .NET System.Net.NetworkInformation.NetworkInterface class or using the GetIfTable API.

These solutions work fine as long as the IP protocol is bound to the network adapter in question. But they cannot retrieve the MAC address of a network adapter which is not bound to the IP protocol stack.

One solution to this problem is to use WMI and query the Win32_NetworkAdapter or (better) Win32_NetworkAdapterConfiguration objects. These do also work with IP-less network adapters. One problem with WMI is, that it’s slow. Another problem is, that the Win32_NetworkAdapter objects cannot be retrieved by a service during system startup – not even when there is a service dependency on the winmgmt service. There seems to be some deadlock situation which causes the WMI querying service to hang for approx. 2 minutes. With the Win32_NetworkAdapterConfiguration class this problem does not occur though.

Just for reference, this is how to query the WMI Win32_NetworkAdapterConfiguration objects from the command line:

wmic path Win32_NetworkAdapterConfiguration get * /format:list

So how does WMI retrieve the MAC address?

After digging around with the amazing API Monitor tool, I learned that WMI is using a DeviceIoControl API call to retrieve the MAC address. This is the required call in C:

#include <WinIoCtl.h>
#include <NtDDNdis.h>

unsigned int code = OID_802_3_CURRENT_ADDRESS;
char macAddress[6];
unsigned int bytesReturned;

BOOL res = DeviceIoControl(
  hDevice,
  IOCTL_NDIS_QUERY_GLOBAL_STATS,
  &code,
  sizeof(code),
  macAddress,
  sizeof(macAddress),
  &bytesReturned,
  NULL
  );

Doing the same thing from C# requires some P/Invoke calls. There is an article How to query miniport driver information (802.11 OIDs) using the DeviceIOControl() function on CodeProject that helped to figure out how to get the DeviceIoControl call straight.

using System;
using System.ComponentModel;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using log4net;
using Microsoft.Win32.SafeHandles;

namespace NetworkUtils
{
  /// 
  /// Helper class which returns network adapter information such
  /// as the MAC address.
  /// 
  public static class NetworkAdapterInfo
  {
    private static readonly ILog _log = LogManager.GetLogger(typeof(NetworkAdapterInfo));

    /// 
    /// Contains enum types and native Win32 functions.
    /// 
    private static class SafeNativeMethods
    {
      [Flags]
      public enum FileAccess : uint
      {
        /// 
        /// Read access
        /// 
        GenericRead = 0x80000000,

        ///// 
        ///// Write access
        ///// 
        //GenericWrite = 0x40000000,
        
        ///// 
        ///// Execute access
        ///// 
        //GenericExecute = 0x20000000,

        ///// 
        ///// Read, write, and execute access
        ///// 
        //GenericAll = 0x10000000
      }

      [Flags]
      public enum ShareMode : uint
      {
        ///// 
        ///// Prevents other processes from opening a file or device if they request delete, read, or write access.
        ///// 
        //None = 0x00000000,

        /// 
        /// Enables subsequent open operations on an object to request read access. 
        /// Otherwise, other processes cannot open the object if they request read access. 
        /// If this flag is not specified, but the object has been opened for read access, the function fails.
        /// 
        Read = 0x00000001,

        /// 
        /// Enables subsequent open operations on an object to request write access. 
        /// Otherwise, other processes cannot open the object if they request write access. 
        /// If this flag is not specified, but the object has been opened for write access, the function fails.
        /// 
        Write = 0x00000002,

        ///// 
        ///// Enables subsequent open operations on an object to request delete access. 
        ///// Otherwise, other processes cannot open the object if they request delete access.
        ///// If this flag is not specified, but the object has been opened for delete access, the function fails.
        ///// 
        //Delete = 0x00000004
      }

      public enum CreationDisposition : uint
      {
        ///// 
        ///// Creates a new file. The function fails if a specified file exists.
        ///// 
        //CreateNew = 1,

        ///// 
        ///// Creates a new file, always. 
        ///// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes, 
        ///// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies.
        ///// 
        //CreateAlways = 2,

        /// 
        /// Opens a file. The function fails if the file does not exist. 
        /// 
        OpenExisting = 3,

        ///// 
        ///// Opens a file, always. 
        ///// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW.
        ///// 
        //OpenAlways = 4,

        ///// 
        ///// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist.
        ///// The calling process must open the file with the GENERIC_WRITE access right. 
        ///// 
        //TruncateExisting = 5
      }

      [Flags]
      public enum FileAttributes : uint
      {
        None = 0x00000000,
        //Readonly = 0x00000001,
        //Hidden = 0x00000002,
        //System = 0x00000004,
        //Directory = 0x00000010,
        //Archive = 0x00000020,
        //Device = 0x00000040,
        //Normal = 0x00000080,
        //Temporary = 0x00000100,
        //SparseFile = 0x00000200,
        //ReparsePoint = 0x00000400,
        //Compressed = 0x00000800,
        //Offline = 0x00001000,
        //NotContentIndexed = 0x00002000,
        //Encrypted = 0x00004000,
        //Write_Through = 0x80000000,
        //Overlapped = 0x40000000,
        //NoBuffering = 0x20000000,
        //RandomAccess = 0x10000000,
        //SequentialScan = 0x08000000,
        //DeleteOnClose = 0x04000000,
        //BackupSemantics = 0x02000000,
        //PosixSemantics = 0x01000000,
        //OpenReparsePoint = 0x00200000,
        //OpenNoRecall = 0x00100000,
        //FirstPipeInstance = 0x00080000
      }

      [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
      internal static extern SafeFileHandle CreateFile(
            string lpFileName,
            FileAccess dwDesiredAccess,
            ShareMode dwShareMode,
            IntPtr SecurityAttributes,
            CreationDisposition dwCreationDisposition,
            FileAttributes dwFlagsAndAttributes,
            IntPtr hTemplateFile);

      [Flags]
      private enum DeviceTypes : uint
      {
        PhysicalNetcard = 0x00000017,
      }

      [Flags]
      private enum Methods : uint
      {
        //Buffered = 0,
        //InDirect = 1,
        OutDirect = 2,
        //Neither = 3
      }

      public enum NdisOids : uint
      {
        //OID_802_3_PERMANENT_ADDRESS = 0x01010101,
        OID_802_3_CURRENT_ADDRESS = 0x01010102,
        //OID_802_3_MULTICAST_LIST = 0x01010103,
        //OID_802_3_MAXIMUM_LIST_SIZE = 0x01010104,
      }

      [Flags]
      public enum IOControlCode : uint
      {
        // Physical Netcard
        IOCTL_NDIS_QUERY_GLOBAL_STATS = (DeviceTypes.PhysicalNetcard << 16) | (0x0000 << 2) | Methods.OutDirect,
        //IOCTL_NDIS_QUERY_ALL_STATS = (DeviceTypes.PhysicalNetcard << 16) | (0x0001 << 2) | Methods.OutDirect,
        //IOCTL_NDIS_DO_PNP_OPERATION = (DeviceTypes.PhysicalNetcard << 16) | (0x0002 << 2) | Methods.Buffered,
        //IOCTL_NDIS_QUERY_SELECTED_STATS = (DeviceTypes.PhysicalNetcard << 16) | (0x0003 << 2) | Methods.OutDirect,
        //IOCTL_NDIS_ENUMERATE_INTERFACES = (DeviceTypes.PhysicalNetcard << 16) | (0x0004 << 2) | Methods.Buffered,
        //IOCTL_NDIS_ADD_TDI_DEVICE = (DeviceTypes.PhysicalNetcard << 16) | (0x0005 << 2) | Methods.Buffered,
        //IOCTL_NDIS_GET_LOG_DATA = (DeviceTypes.PhysicalNetcard << 16) | (0x0007 << 2) | Methods.OutDirect,
        //IOCTL_NDIS_GET_VERSION = (DeviceTypes.PhysicalNetcard << 16) | (0x0008 << 2) | Methods.Buffered,
      };

      [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
      public static extern bool DeviceIoControl(
          SafeFileHandle hDevice,
          [MarshalAs(UnmanagedType.U4)] IOControlCode dwIoControlCode,
          IntPtr lpInBuffer,
          [MarshalAs(UnmanagedType.U4)]uint nInBufferSize,
          IntPtr lpOutBuffer,
          [MarshalAs(UnmanagedType.U4)] uint nOutputBufferSize,
          [MarshalAs(UnmanagedType.U4)] out uint pBytesReturned,
          IntPtr pOverlapped);
    }

    /// 
    /// Gets the MAC address of the specified network adapter instance.
    /// 
    /// The network adapter guid.
    /// The network adapter MAC address.
    /// 
    /// 
    /// This implementation uses low level system calls. Though there are
    /// many other options available (like e. g. )
    /// these do only work with network adapters which are bound to the IP stack.
    /// The implementation provided here also works with IP less network adapters.
    /// It uses a DeviceIoControl system call to the network adapter's driver instance.
    /// 
    /// 
    /// An alternate solution could use WMI and query the Win32_NetworkAdapterConfiguration
    /// object, but this is slow and introduces a dependency on the WMI infrastructure.
    /// This in turn causes delays during system startup.
    /// 
    /// 
    public static PhysicalAddress GetMacAddress(string guid)
    {
      _log.DebugFormat("Retrieving MAC address for network adapter: {0}", guid);
      SafeFileHandle hFile = null;
      try
      {
        // Open device driver
        var fileName = string.Format(@"\\.\{0}", guid);
        _log.DebugFormat("Opening network adapter driver: {0}", fileName);
        hFile = SafeNativeMethods.CreateFile(
          fileName,
          SafeNativeMethods.FileAccess.GenericRead,
          SafeNativeMethods.ShareMode.Read | SafeNativeMethods.ShareMode.Write,
          IntPtr.Zero,
          SafeNativeMethods.CreationDisposition.OpenExisting,
          SafeNativeMethods.FileAttributes.None,
          IntPtr.Zero);
        if (hFile.IsInvalid)
        {
          var errorCode = Marshal.GetLastWin32Error();
          var msg = string.Format("CreateFile('{0}') failed with error code {1}", fileName, errorCode);
          _log.Error(msg);
          throw new Win32Exception(errorCode, msg);
        }

        // Execute DeviceIoControl
        _log.DebugFormat("Calling DeviceIoControl on network adapter driver: {0}", fileName);
        var handle = GCHandle.Alloc((uint)SafeNativeMethods.NdisOids.OID_802_3_CURRENT_ADDRESS, GCHandleType.Pinned);
        var ptrInput = handle.AddrOfPinnedObject();
        var address = new byte[6];
        var ptrOutput = Marshal.AllocHGlobal(address.Length);
        uint bytesReturned;
        var res = SafeNativeMethods.DeviceIoControl(
          hFile,
          SafeNativeMethods.IOControlCode.IOCTL_NDIS_QUERY_GLOBAL_STATS,
          ptrInput,
          (uint)Marshal.SizeOf(typeof(uint)),
          ptrOutput,
          (uint)address.Length,
          out bytesReturned,
          IntPtr.Zero);
        handle.Free();
        if (!res)
        {
          Marshal.FreeHGlobal(ptrOutput);
          var errorCode = Marshal.GetLastWin32Error();
          var msg = string.Format("DeviceIoControl failed with error code {0}", errorCode);
          _log.Error(msg);
          throw new Win32Exception(errorCode, msg);
        }
        
        // Validate length of returned value
        if (bytesReturned != address.Length)
        {
          Marshal.FreeHGlobal(ptrOutput);
          var msg = string.Format("DeviceIoControl() returned unexpected size: {0} (expected was: {1}", bytesReturned, address.Length);
          _log.Error(msg);
          throw new InvalidOperationException(msg);
        }

        Marshal.Copy(ptrOutput, address, 0, address.Length);
        Marshal.FreeHGlobal(ptrOutput);

        var macAddress = new PhysicalAddress(address);
        _log.InfoFormat("Retrieved MAC address {0} for network adapter: {1}", macAddress, guid);
        return macAddress;
      }
      catch (Exception ex)
      {
        var msg = string.Format("Failed to retrieve MAC address for network adapter: {0}", guid);
        _log.Error(msg, ex);
        throw;
      }
      finally
      {
        if (hFile != null)
        {
          hFile.Close();
        }
      }
    }
  }
}

Aktionen

Information

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s




%d Bloggern gefällt das: