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.
3 Comments
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 – по нему найти нужный интерфейс уже не сложно.
Всё это, конечно, моё личное ИМХО =)
T-Rex
Под винду не собирается? А какая у вас винда?
http://msdn.microsoft.com/en-us/library/aa379322%28VS.85%29.aspx
говорит что минимальное требование Windows 2000 Professional.
Ммм? Так это.. не надо было мне ФриБСД, только линух/мак/винда. Если есть вариант под BSD – ок. 🙂 я только за.
Угу.. может. Задача у меня стояла как “запретить повторный коннект с одного мак адреса”. Если два раза одним и тем же алгоритмом выбрать первый мак-адрес то не важно какой там интерфейс идет первым.
Ну а так – код в public domain – каждый дорабатывает как хочет. 🙂
Даете вариант с параметром? Согласен выложить для общего обозрения если есть 🙂
И еще, на пасторге сорцы долго хранятся? А то если удалят вариант для FreeBSD будет печально. Может сюда запостите?
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.