jimbob
jimbob

Reputation: 461

How to get MAC address from OS X with Swift

Is it possible to get the MAC address using Swift?

The MAC address being the primary address for the Wi-Fi or Airport.

I'm trying to make a OS X application.

Upvotes: 12

Views: 10781

Answers (7)

Marek H
Marek H

Reputation: 5566

Apple updated their sample code for getting mac address for the purpose of validating it against mac app store receipt. Using previous code you will get incorrect address if you have an active AirPlay connection. NOTE: If the motherboard was damaged the correct port might be en11. https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device#//apple_ref/doc/uid/TP40010573-CH1-SW14

import IOKit
import Foundation


// Returns an object with a +1 retain count; the caller needs to release.
func io_service(named name: String, wantBuiltIn: Bool) -> io_service_t? {
    let default_port = kIOMasterPortDefault
    var iterator = io_iterator_t()
    defer {
        if iterator != IO_OBJECT_NULL {
            IOObjectRelease(iterator)
        }
    }


    guard let matchingDict = IOBSDNameMatching(default_port, 0, name),
        IOServiceGetMatchingServices(default_port,
                                     matchingDict as CFDictionary,
                                     &iterator) == KERN_SUCCESS,
        iterator != IO_OBJECT_NULL
    else {
        return nil
    }


    var candidate = IOIteratorNext(iterator)
    while candidate != IO_OBJECT_NULL {
        if let cftype = IORegistryEntryCreateCFProperty(candidate,
                                                        "IOBuiltin" as CFString,
                                                        kCFAllocatorDefault,
                                                        0) {
            let isBuiltIn = cftype.takeRetainedValue() as! CFBoolean
            if wantBuiltIn == CFBooleanGetValue(isBuiltIn) {
                return candidate
            }
        }


        IOObjectRelease(candidate)
        candidate = IOIteratorNext(iterator)
    }


    return nil
}


func copy_mac_address() -> CFData? {
    // Prefer built-in network interfaces.
    // For example, an external Ethernet adaptor can displace
    // the built-in Wi-Fi as en0.
    guard let service = io_service(named: "en0", wantBuiltIn: true)
            ?? io_service(named: "en1", wantBuiltIn: true)
            ?? io_service(named: "en0", wantBuiltIn: false)
        else { return nil }
    defer { IOObjectRelease(service) }


    if let cftype = IORegistryEntrySearchCFProperty(
        service,
        kIOServicePlane,
        "IOMACAddress" as CFString,
        kCFAllocatorDefault,
        IOOptionBits(kIORegistryIterateRecursively | kIORegistryIterateParents)) {
            return (cftype as! CFData)
    }


    return nil
}

If you need a string:

// Returns the best mac address for app store validation or 00:00:00:00:00:00
func copy_mac_address() -> String {
    var macAddress : [UInt8] = [0, 0, 0, 0, 0, 0]
    var cfData = copy_mac_address() ?? CFDataCreate(.none, macAddress, 8)!
    (cfData as Data).copyBytes(to: &macAddress, count: macAddress.count)
    return macAddress.map( { String(format:"%02x", $0) } ).joined(separator: ":")
}

Upvotes: 0

You can also use 'SystemConfiguration' framework

import SystemConfiguration

func collectMACAddresses() -> [String] {
    guard let interfaces = SCNetworkInterfaceCopyAll() as? [SCNetworkInterface] else {
        return []
    }
    
    return interfaces
        .map(SCNetworkInterfaceGetHardwareAddressString)
        .compactMap { $0 as String? }
}

Upvotes: 3

vadian
vadian

Reputation: 285069

Different approach via 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 {
        print("Error: could not find index for bsd name \(bsd)")
        return nil
    }
    let bsdData = Data(bsd.utf8)
    var managementInfoBase = [CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, bsdIndex]

    if sysctl(&managementInfoBase, 6, nil, &length, nil, 0) < 0 {
        print("Error: could not determine length of info data structure");
        return nil;
    }

    buffer = [CChar](unsafeUninitializedCapacity: length, initializingWith: {buffer, initializedCount in
        for x in 0..<length { buffer[x] = 0 }
        initializedCount = length
    })

    if sysctl(&managementInfoBase, 6, &buffer, &length, nil, 0) < 0 {
        print("Error: could not read info data structure");
        return nil;
    }

    let infoData = Data(bytes: buffer, count: length)
    let indexAfterMsghdr = MemoryLayout<if_msghdr>.stride + 1
    let rangeOfToken = infoData[indexAfterMsghdr...].range(of: bsdData)!
    let lower = rangeOfToken.upperBound
    let upper = lower + MAC_ADDRESS_LENGTH
    let macAddressData = infoData[lower..<upper]
    let addressBytes = macAddressData.map{ String(format:"%02x", $0) }
    return addressBytes.joined(separator: separator)
}

MACAddressForBSD(bsd: "en0")

Upvotes: 5

Marek H
Marek H

Reputation: 5566

Update for Swift 4.2

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! 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, kIOServicePlane, &controllerService) == KERN_SUCCESS {

            let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0)
            if dataUM != nil {
                let data = (dataUM!.takeRetainedValue() as! CFData) as Data
                macAddress = [0, 0, 0, 0, 0, 0]
                data.copyBytes(to: &macAddress!, count: macAddress!.count)
            }
            IOObjectRelease(controllerService)
        }

        IOObjectRelease(intfService)
        intfService = IOIteratorNext(intfIterator)
    }

    return macAddress
}


func getMacAddress() -> String? {
    var macAddressAsString : String?
    if let intfIterator = FindEthernetInterfaces() {
        if let macAddress = GetMACAddress(intfIterator) {
            macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joined(separator: ":")
            print(macAddressAsString!)
        }

        IOObjectRelease(intfIterator)
    }
    return macAddressAsString
}

Upvotes: 6

Martin R
Martin R

Reputation: 539685

Apple's sample code from https://developer.apple.com/library/mac/samplecode/GetPrimaryMACAddress/Introduction/Intro.html to retrieve the Ethernet MAC address can be translated to Swift. I have preserved only the most important comments, more explanations can be found in the original 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 how to work with Unmanaged objects, those have the suffix UM in my code.

Instead of returning an error code, the functions return an optional value which is nil if the function failed.


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)
}

Upvotes: 16

Alex M
Alex M

Reputation: 537

Update to Martin R's entry. There are a couple of lines that won't compile with Swift 2.1.

Change:

let matchingDict = matchingDictUM.takeUnretainedValue() as NSMutableDictionary

To:

let matchingDict = matchingDictUM as NSMutableDictionary

Change:

let macAddressAsString = ":".join(macAddress.map( { String(format:"%02x", $0) } ))

To:

let macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joinWithSeparator(":")

Upvotes: 4

Eric Aya
Eric Aya

Reputation: 70097

DISCLAIMER: this is not production-ready. It would probably be rejected by the App Store. It's also subject to errors if the output of ifconfig changes in the future. I've made this because I lacked the skills to translate the C code given in the links. It does not replace a full Swift solution. That being said, it works...

Get ifconfig's output and parse it to get the MAC address associated with an 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)
        }
    }
}

Upvotes: 0

Related Questions