Cross-Platform Way of Obtaining MAC Address of Your Machine

In one of my current projects I had to implement client-server communication and protection by MAC address when client machine can’t connect to server if its MAC address is not allowed, regardless of network or broadband connection. But what was a surprise that wxWidgets does not have API which allows obtaining MAC address in cross-platform way. So, I decided to write a small class which allows obtainig MAC address for Windows, Linux, Mac OS and Windows Mobile. Here it is:

MACAddressUtility.h

#ifndef _MACADDRESS_UTILITY_H
#define _MACADDRESS_UTILITY_H

class MACAddressUtility
{
public:
 static long GetMACAddress(unsigned char * result);
private:
#if defined(WIN32) || defined(UNDER_CE)
 static long GetMACAddressMSW(unsigned char * result);
#elif defined(__APPLE__)
 static long GetMACAddressMAC(unsigned char * result);
#elif defined(LINUX) || defined(linux)
 static long GetMACAddressLinux(unsigned char * result);
#endif
};

#endif

MACAddressUtility.cpp

#include "MACAddressUtility.h"

#include <stdio.h>

#if defined(WIN32) || defined(UNDER_CE)
# include <windows.h>
# if defined(UNDER_CE)
#  include <Iphlpapi.h>
# endif
#elif defined(__APPLE__)
# include <CoreFoundation/CoreFoundation.h>
# include <IOKit/IOKitLib.h>
# include <IOKit/network/IOEthernetInterface.h>
# include <IOKit/network/IONetworkInterface.h>
# include <IOKit/network/IOEthernetController.h>
#elif defined(LINUX) || defined(linux)
# include <string.h>
# include <net/if.h>
# include <sys/ioctl.h>
#   include <sys/socket.h>
#   include <arpa/inet.h>
#endif

long MACAddressUtility::GetMACAddress(unsigned char * result)
{
 // Fill result with zeroes
 memset(result, 0, 6);
 // Call appropriate function for each platform
#if defined(WIN32) || defined(UNDER_CE)
 return GetMACAddressMSW(result);
#elif defined(__APPLE__)
 return GetMACAddressMAC(result);
#elif defined(LINUX) || defined(linux)
 return GetMACAddressLinux(result);
#endif
 // If platform is not supported then return error code
 return -1;
}

#if defined(WIN32) || defined(UNDER_CE)

inline long MACAddressUtility::GetMACAddressMSW(unsigned char * result)
{

#if defined(UNDER_CE)
 IP_ADAPTER_INFO AdapterInfo[16]; // Allocate information
 DWORD dwBufLen = sizeof(AdapterInfo); // Save memory size of buffer
 if(GetAdaptersInfo(AdapterInfo, &dwBufLen) == ERROR_SUCCESS)
 {
  memcpy(result, AdapterInfo->Address, 6);
 }
 else return -1;
#else
 UUID uuid;
 if(UuidCreateSequential(&uuid) == RPC_S_UUID_NO_ADDRESS) return -1;
 memcpy(result, (char*)(uuid.Data4+2), 6);
#endif
 return 0;
}

#elif defined(__APPLE__)

static kern_return_t FindEthernetInterfaces(io_iterator_t *matchingServices)
{
    kern_return_t  kernResult;
    CFMutableDictionaryRef matchingDict;
    CFMutableDictionaryRef propertyMatchDict;

    matchingDict = IOServiceMatching(kIOEthernetInterfaceClass);

    if (NULL != matchingDict)
 {
        propertyMatchDict =
   CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
    &kCFTypeDictionaryKeyCallBacks,
    &kCFTypeDictionaryValueCallBacks);

        if (NULL != propertyMatchDict)
  {
            CFDictionarySetValue(propertyMatchDict,
    CFSTR(kIOPrimaryInterface), kCFBooleanTrue);
            CFDictionarySetValue(matchingDict,
    CFSTR(kIOPropertyMatchKey), propertyMatchDict);
            CFRelease(propertyMatchDict);
        }
    }
    kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault,
  matchingDict, matchingServices);
    return kernResult;
}

static kern_return_t GetMACAddress(io_iterator_t intfIterator,
           UInt8 *MACAddress, UInt8 bufferSize)
{
    io_object_t  intfService;
    io_object_t  controllerService;
    kern_return_t kernResult = KERN_FAILURE;

 if (bufferSize < kIOEthernetAddressSize) {
  return kernResult;
 }

    bzero(MACAddress, bufferSize);

    while (intfService = IOIteratorNext(intfIterator))
    {
        CFTypeRef MACAddressAsCFData;

        // IONetworkControllers can't be found directly by the IOServiceGetMatchingServices call,
        // since they are hardware nubs and do not participate in driver matching. In other words,
        // registerService() is never called on them. So we've found the IONetworkInterface and will
        // get its parent controller by asking for it specifically.

        // IORegistryEntryGetParentEntry retains the returned object,
  // so release it when we're done with it.
        kernResult =
   IORegistryEntryGetParentEntry(intfService,
    kIOServicePlane,
    &controllerService);

        if (KERN_SUCCESS != kernResult) {
            printf("IORegistryEntryGetParentEntry returned 0x%08x\n", kernResult);
        }
        else {
            // Retrieve the MAC address property from the I/O Registry in the form of a CFData
            MACAddressAsCFData =
    IORegistryEntryCreateCFProperty(controllerService,
     CFSTR(kIOMACAddress),
     kCFAllocatorDefault,
     0);
            if (MACAddressAsCFData) {
                CFShow(MACAddressAsCFData); // for display purposes only; output goes to stderr

                // Get the raw bytes of the MAC address from the CFData
                CFDataGetBytes((CFDataRef)MACAddressAsCFData,
     CFRangeMake(0, kIOEthernetAddressSize), MACAddress);
                CFRelease(MACAddressAsCFData);
            }

            // Done with the parent Ethernet controller object so we release it.
            (void) IOObjectRelease(controllerService);
        }

        // Done with the Ethernet interface object so we release it.
        (void) IOObjectRelease(intfService);
    }

    return kernResult;
}

long MACAddressUtility::GetMACAddressMAC(unsigned char * result)
{
 io_iterator_t intfIterator;
 kern_return_t kernResult = KERN_FAILURE;
 do
 {
  kernResult = ::FindEthernetInterfaces(&intfIterator);
  if (KERN_SUCCESS != kernResult) break;
     kernResult = ::GetMACAddress(intfIterator, (UInt8*)result, 6);
    }
 while(false);
    (void) IOObjectRelease(intfIterator);
}

#elif defined(LINUX) || defined(linux)

long MACAddressUtility::GetMACAddressLinux(unsigned char * result)
{
 struct ifreq ifr;
    struct ifreq *IFR;
    struct ifconf ifc;
    char buf[1024];
    int s, i;
    int ok = 0;

    s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s == -1)
 {
        return -1;
    }

    ifc.ifc_len = sizeof(buf);
    ifc.ifc_buf = buf;
    ioctl(s, SIOCGIFCONF, &ifc);

    IFR = ifc.ifc_req;
    for (i = ifc.ifc_len / sizeof(struct ifreq); --i >= 0; IFR++)
 {
        strcpy(ifr.ifr_name, IFR->ifr_name);
        if (ioctl(s, SIOCGIFFLAGS, &ifr) == 0)
  {
            if (! (ifr.ifr_flags & IFF_LOOPBACK))
   {
                if (ioctl(s, SIOCGIFHWADDR, &ifr) == 0)
    {
                    ok = 1;
                    break;
                }
            }
        }
    }

    shutdown(s, SHUT_RDWR);
    if (ok)
 {
        bcopy( ifr.ifr_hwaddr.sa_data, result, 6);
    }
    else
 {
        return -1;
    }
    return 0;
}

#endif

wxMACAddressUtility.h

#ifndef _WX_MACADDRESS_UTILITY_H
#define _WX_MACADDRESS_UTILITY_H

#include "MACAddressUtility.h"
#include <wx/wx.h>

class wxMACAddressUtility
{
public:
 static wxString GetMACAddress()
 {
  unsigned char result[6];
  if(MACAddressUtility::GetMACAddress(result) == 0)
  {
   return wxString::Format(wxT("%02X:%02X:%02X:%02X:%02X:%02X"),
    (unsigned int)result[0], (unsigned int)result[1], (unsigned int)result[2],
    (unsigned int)result[3], (unsigned int)result[4], (unsigned int)result[5]);
  }
  return wxEmptyString;
 }
};

#endif

Sample of usage:

m_MACAddress = wxMACAddressUtility::GetMACAddress();

As you can see, core class does not depend on wxWidgets and can be used in native applications written without wxWidgets.

Download sample application

T-Rex

View Comments

  • А можно немного покритиковать?
    1) Если уж делаем кросплатформенно, то пусть будет кроссплатформенно, а то что это такое? Под виндой у меня не собирается, говорит, что не знает кто такой UuidCreateSequential, про мою любимую FreeBSD вообще забыли... Под Линуксом и Маком не проверял - под рукой не оказалось. Кстати, если не секрет, зачем так сложно под Mac?
    Короче, мой вариант см тут - http://paste.org/pastebin/view/10239
    источники, из которых собирал:
    http://support.microsoft.com/kb/118623
    http://www.cs.williams.edu/~morgan/code/C++/getip.cpp
    2) Что вообще такое MAC-адрес компьютера? MAC-адрес может быть сетевой карточки, а их в компе может быть несколько. Получается, адрес первой попавшейся карточки. А если я захочу ещё одну сетевушку воткнуть? Или VPN поднять? Или ещё чего? Аутентификация может начать обламывать, что не есть хорошо. =) Для идентификации лучше бы использовать что-нить типа идентификатора материнки, хотя это, конечно, уже сложнее. Что же касается этих функций - неплохо бы было в качестве параметра передовать интерфейс, для которого нужно получить MAC. Либо IP - по нему найти нужный интерфейс уже не сложно.
    Всё это, конечно, моё личное ИМХО =)

  • Под винду не собирается? А какая у вас винда?
    http://msdn.microsoft.com/en-us/library/aa379322%28VS.85%29.aspx
    говорит что минимальное требование Windows 2000 Professional.

    FreeBSD вообще забыли…

    Ммм? Так это.. не надо было мне ФриБСД, только линух/мак/винда. Если есть вариант под BSD - ок. :) я только за.

    MAC-адрес может быть сетевой карточки, а их в компе может быть несколько

    Угу.. может. Задача у меня стояла как "запретить повторный коннект с одного мак адреса". Если два раза одним и тем же алгоритмом выбрать первый мак-адрес то не важно какой там интерфейс идет первым.
    Ну а так - код в public domain - каждый дорабатывает как хочет. :)

    то же касается этих функций – неплохо бы было в качестве параметра передовать интерфейс, для которого нужно получить MAC. Либо IP – по нему найти нужный интерфейс уже не сложно.

    Даете вариант с параметром? Согласен выложить для общего обозрения если есть :)

    И еще, на пасторге сорцы долго хранятся? А то если удалят вариант для FreeBSD будет печально. Может сюда запостите?

  • Я не знаю, сколько там хранят - первый раз пользуюсь, раньше не было нужды. Просто постить большие куски кода в комменты не красиво, да и не знаю я, как тут форматирование нормальное сделать, а для отдельного поста в своём ЖЖ материала маловато. Так что Вы уж сами запостите оттуда сюда, если не сложно.

    Винда у меня XP. Проверял на двух компах -
    g++ main.cc
    main.cc: In static member function `static long int MACAddressUtility::GetMACAddressMSW(unsigned char*)':
    main.cc:67: error: `UuidCreateSequential' was not declared in this scope
    При этом если заменить UuidCreateSequential на UuidCreate - собирается, но, естественно, выдаёт ерунду. По всей видимости, надо ставить VS со свежим SDK. (у меня стоит только MinGW)
    По поводу BSD - это я просто справедливости ради - всё время про неё забывают. Тем более, обратите внимание, в моём варианте для *BSD/MacOS/Linux используется одна и таже функция с небольшим ifdef`ом.
    Вариант с параметрами? Был бы - выложил бы его, чего мудрить =) Я этот-то на скорую руку слепил из Вашего кода. Будет свободное время, сделаю, интереса ради =) Только у меня возможности по тестированию ограничены - Windows, FreeBSD и, в ограниченных количествах, MacOS.

Share
Published by
T-Rex

Recent Posts

Разработка кроссплатформенных модульных приложений на C++ с библиотекой wxWidgets

Введение Уже долгое время не пишу статьи о разработке, хотя сам процесс написания мне очень…

10 years ago

wxWidgets App With Plugins (Windows/Linux/Mac) – Sample Source Code

I can see that there is still a lot of topics at wxWidgets forums related…

11 years ago

wxToolBox is Now Open-Source!

I've just published the source code of wxToolBox component and a couple of sample apps at…

11 years ago

Microsoft Kinect Helper Library and Sample for wxWidgets

Microsoft released their Kinect SDK several days ago. So, for those wxWidgets developers who are…

13 years ago

wxJSON 1.1.0 Released

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to…

14 years ago

wxRuby. Оно даже работает!

Вдохновленнный читаемой нынче книгой My Job Went to India: 52 Ways to Save Your Job…

15 years ago