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

Get MAC address programmatically

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

Download sample application

3 comments

  1. Alatar   •  

    А можно немного покритиковать?
    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 – по нему найти нужный интерфейс уже не сложно.
    Всё это, конечно, моё личное ИМХО =)

  2. T-Rex   •  

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

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

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

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

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

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

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

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

  3. Alatar   •  

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

    Винда у меня 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Please leave these two fields as-is: