How to get MAC address from OSX using Swift
DISCLAIMER: This is not production ready. This will likely be rejected by the App Store. It is also error prone if the outcome ifconfig
changes in the future. I did this because I lacked the skills to translate the C code given in the links. It does not replace Swift's complete solution. That being said, it works ...
Get the ifconfig
output and parse it to get the MAC address associated with the interface ( en0
in this example):
let theTask = NSTask()
let taskOutput = NSPipe()
theTask.launchPath = "/sbin/ifconfig"
theTask.standardOutput = taskOutput
theTask.standardError = taskOutput
theTask.arguments = ["en0"]
theTask.launch()
theTask.waitUntilExit()
let taskData = taskOutput.fileHandleForReading.readDataToEndOfFile()
if let stringResult = NSString(data: taskData, encoding: NSUTF8StringEncoding) {
if stringResult != "ifconfig: interface en0 does not exist" {
let f = stringResult.rangeOfString("ether")
if f.location != NSNotFound {
let sub = stringResult.substringFromIndex(f.location + f.length)
let range = Range(start: advance(sub.startIndex, 1), end: advance(sub.startIndex, 18))
let result = sub.substringWithRange(range)
println(result)
}
}
}
source to share
Apple's sample code from https://developer.apple.com/library/mac/samplecode/GetPrimaryMACAddress/Introduction/Intro.html to get the Ethernet MAC address can be translated into Swift. I have kept only the most important comments, more explanation can be found in the source code.
// Returns an iterator containing the primary (built-in) Ethernet interface. The caller is responsible for
// releasing the iterator after the caller is done with it.
func FindEthernetInterfaces() -> io_iterator_t? {
let matchingDictUM = IOServiceMatching("IOEthernetInterface");
// Note that another option here would be:
// matchingDict = IOBSDMatching("en0");
// but en0: isn't necessarily the primary interface, especially on systems with multiple Ethernet ports.
if matchingDictUM == nil {
return nil
}
let matchingDict = matchingDictUM.takeUnretainedValue() as NSMutableDictionary
matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]
var matchingServices : io_iterator_t = 0
if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
return nil
}
return matchingServices
}
// Given an iterator across a set of Ethernet interfaces, return the MAC address of the last one.
// If no interfaces are found the MAC address is set to an empty string.
// In this sample the iterator should contain just the primary interface.
func GetMACAddress(intfIterator : io_iterator_t) -> [UInt8]? {
var macAddress : [UInt8]?
var intfService = IOIteratorNext(intfIterator)
while intfService != 0 {
var controllerService : io_object_t = 0
if IORegistryEntryGetParentEntry(intfService, "IOService", &controllerService) == KERN_SUCCESS {
let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress", kCFAllocatorDefault, 0)
if dataUM != nil {
let data = dataUM.takeRetainedValue() as! NSData
macAddress = [0, 0, 0, 0, 0, 0]
data.getBytes(&macAddress!, length: macAddress!.count)
}
IOObjectRelease(controllerService)
}
IOObjectRelease(intfService)
intfService = IOIteratorNext(intfIterator)
}
return macAddress
}
if let intfIterator = FindEthernetInterfaces() {
if let macAddress = GetMACAddress(intfIterator) {
let macAddressAsString = ":".join(macAddress.map( { String(format:"%02x", $0) } ))
println(macAddressAsString)
}
IOObjectRelease(intfIterator)
}
The only "tricky" part is working with objects Unmanaged
, they have a suffix UM
in my code.
Instead of returning an error code, functions return an optional value nil
if the function fails.
Update for Swift 3:
func FindEthernetInterfaces() -> io_iterator_t? {
let matchingDict = IOServiceMatching("IOEthernetInterface") as NSMutableDictionary
matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]
var matchingServices : io_iterator_t = 0
if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
return nil
}
return matchingServices
}
func GetMACAddress(_ intfIterator : io_iterator_t) -> [UInt8]? {
var macAddress : [UInt8]?
var intfService = IOIteratorNext(intfIterator)
while intfService != 0 {
var controllerService : io_object_t = 0
if IORegistryEntryGetParentEntry(intfService, "IOService", &controllerService) == KERN_SUCCESS {
let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0)
if let data = dataUM?.takeRetainedValue() as? NSData {
macAddress = [0, 0, 0, 0, 0, 0]
data.getBytes(&macAddress!, length: macAddress!.count)
}
IOObjectRelease(controllerService)
}
IOObjectRelease(intfService)
intfService = IOIteratorNext(intfIterator)
}
return macAddress
}
if let intfIterator = FindEthernetInterfaces() {
if let macAddress = GetMACAddress(intfIterator) {
let macAddressAsString = macAddress.map( { String(format:"%02x", $0) } )
.joined(separator: ":")
print(macAddressAsString)
}
IOObjectRelease(intfIterator)
}
source to share
Different approach through if_msghdr
func MACAddressForBSD(bsd : String) -> String?
{
let MAC_ADDRESS_LENGTH = 6
let separator = ":"
var length : size_t = 0
var buffer : [CChar]
let BSDIndex = Int32(if_nametoindex(bsd))
if BSDIndex == 0 {
println("Error: could not find index for bsd name \(bsd)")
return nil
}
let bsdData = bsd.dataUsingEncoding(NSUTF8StringEncoding)!
var managementInfoBase = [CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, BSDIndex]
if sysctl(&managementInfoBase, 6, nil, &length, nil, 0) < 0 {
println("Error: could not determine length of info data structure");
return nil;
}
buffer = [CChar](count: length, repeatedValue: 0)
if sysctl(&managementInfoBase, 6, &buffer, &length, nil, 0) < 0 {
println("Error: could not read info data structure");
return nil;
}
let infoData = NSData(bytes: buffer, length: length)
var interfaceMsgStruct = if_msghdr()
infoData.getBytes(&interfaceMsgStruct, length: sizeof(if_msghdr))
let socketStructStart = sizeof(if_msghdr) + 1
let socketStructData = infoData.subdataWithRange(NSMakeRange(socketStructStart, length - socketStructStart))
let rangeOfToken = socketStructData.rangeOfData(bsdData, options: NSDataSearchOptions(0), range: NSMakeRange(0, socketStructData.length))
let macAddressData = socketStructData.subdataWithRange(NSMakeRange(rangeOfToken.location + 3, MAC_ADDRESS_LENGTH))
var macAddressDataBytes = [UInt8](count: MAC_ADDRESS_LENGTH, repeatedValue: 0)
macAddressData.getBytes(&macAddressDataBytes, length: MAC_ADDRESS_LENGTH)
let addressBytes = macAddressDataBytes.map({ String(format:"%02x", $0) })
return join(separator, addressBytes)
}
MACAddressForBSD("en0")
source to share
Update Martin R. There are a couple of lines that won't compile with Swift 2.1.
Edit:
let matchingDict = matchingDictUM.takeUnretainedValue() as NSMutableDictionary
To:
let matchingDict = matchingDictUM as NSMutableDictionary
Edit:
let macAddressAsString = ":".join(macAddress.map( { String(format:"%02x", $0) } ))
To:
let macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joinWithSeparator(":")
source to share