Manage ifaddrs to return MAC addresses as well in Swift

北城以北 提交于 2019-11-27 23:19:55
Martin R

(Remark/clarification: This is an answer to the question "Manage ifaddrs to return MAC addresses as well in Swift" and "Is it possible to modify the code from How to get Ip address in swift to return the MAC addresses as well". This is not a solution to "retrieve all the IPs connected to my router" which is also mentioned in the question body.)

Here is an extension of the referenced code which returns the local (up and running) interfaces as an array of (interface name, ip address, MAC address) tuples. The MAC address is retrieved from the interfaces of type AF_LINK which are stored as sockaddr_dl structure in the interface list. This is a variable length structure, and Swift's strict type system makes some pointer juggling necessary.

Important: This code is meant to run on Mac computers. It does not work to get the MAC addresses on iOS devices. iOS intentionally returns "02:00:00:00:00:00" as hardware address for all interfaces for privacy reasons, see for example Trouble with MAC address in iOS 7.0.2.)

func getInterfaces() -> [(name : String, addr: String, mac : String)] {

    var addresses = [(name : String, addr: String, mac : String)]()
    var nameToMac = [ String : String ]()

    // Get list of all interfaces on the local machine:
    var ifaddr : UnsafeMutablePointer<ifaddrs> = nil
    if getifaddrs(&ifaddr) == 0 {

        // For each interface ...
        var ptr = ifaddr
        while ptr != nil {
            defer { ptr = ptr.memory.ifa_next }

            let flags = Int32(ptr.memory.ifa_flags)
            let addr = ptr.memory.ifa_addr

            if let name = String.fromCString(ptr.memory.ifa_name)  {

                // Check for running IPv4, IPv6 interfaces. Skip the loopback interface.
                if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) {

                    if addr.memory.sa_family == UInt8(AF_LINK) {
                        // Get MAC address from sockaddr_dl structure and store in nameToMac dictionary:
                        let dl = UnsafePointer<sockaddr_dl>(ptr.memory.ifa_addr)
                        let lladdr = UnsafeBufferPointer(start: UnsafePointer<Int8>(dl) + 8 + Int(dl.memory.sdl_nlen),
                                                         count: Int(dl.memory.sdl_alen))
                        if lladdr.count == 6 {
                            nameToMac[name] = lladdr.map { String(format:"%02hhx", $0)}.joinWithSeparator(":")
                        }
                    }

                    if addr.memory.sa_family == UInt8(AF_INET) || addr.memory.sa_family == UInt8(AF_INET6) {
                        // Convert interface address to a human readable string:
                        var hostname = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
                        if (getnameinfo(addr, socklen_t(addr.memory.sa_len), &hostname, socklen_t(hostname.count),
                            nil, socklen_t(0), NI_NUMERICHOST) == 0) {
                            if let address = String.fromCString(hostname) {
                                addresses.append( (name: name, addr: address, mac : "") )
                            }
                        }
                    }
                }
            }
        }
        freeifaddrs(ifaddr)
    }

    // Now add the mac address to the tuples:
    for (i, addr) in addresses.enumerate() {
        if let mac = nameToMac[addr.name] {
            addresses[i] = (name: addr.name, addr: addr.addr, mac : mac)
        }
    }

    return addresses
}

You have to add

#include <ifaddrs.h>
#include <net/if_dl.h>

to the bridging header file to make this compile.

Example usage:

for addr in getInterfaces() {
   print(addr)
}
// ("en0", "fe80::1234:7fff:fe2e:8765%en0", "a9:55:6f:2e:57:78")
// ("en0", "192.168.2.108", "a9:55:6f:2e:57:78")
// ...

Update for Swift 3 (Xcode 8):

func getInterfaces() -> [(name : String, addr: String, mac : String)] {

    var addresses = [(name : String, addr: String, mac : String)]()
    var nameToMac = [ String: String ]()

    // Get list of all interfaces on the local machine:
    var ifaddr : UnsafeMutablePointer<ifaddrs>?
    guard getifaddrs(&ifaddr) == 0 else { return [] }
    guard let firstAddr = ifaddr else { return [] }

    // For each interface ...
    for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
        let flags = Int32(ptr.pointee.ifa_flags)
        if let addr = ptr.pointee.ifa_addr {
            let name = String(cString: ptr.pointee.ifa_name)

            // Check for running IPv4, IPv6 interfaces. Skip the loopback interface.
            if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) {
                switch Int32(addr.pointee.sa_family) {
                case AF_LINK:
                    // Get MAC address from sockaddr_dl structure and store in nameToMac dictionary:
                    addr.withMemoryRebound(to: sockaddr_dl.self, capacity: 1) { dl in
                        dl.withMemoryRebound(to: Int8.self, capacity: 8 + Int(dl.pointee.sdl_nlen + dl.pointee.sdl_alen)) {
                            let lladdr = UnsafeBufferPointer(start: $0 + 8 + Int(dl.pointee.sdl_nlen),
                                                             count: Int(dl.pointee.sdl_alen))
                            if lladdr.count == 6 {
                                nameToMac[name] = lladdr.map { String(format:"%02hhx", $0)}.joined(separator: ":")
                            }
                        }
                    }
                case AF_INET, AF_INET6:
                    // Convert interface address to a human readable string:
                    var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
                    if (getnameinfo(addr, socklen_t(addr.pointee.sa_len),
                                    &hostname, socklen_t(hostname.count),
                                    nil, socklen_t(0), NI_NUMERICHOST) == 0) {
                        let address = String(cString: hostname)
                        addresses.append( (name: name, addr: address, mac : "") )
                    }
                default:
                    break
                }
            }
        }
    }

    freeifaddrs(ifaddr)

    // Now add the mac address to the tuples:
    for (i, addr) in addresses.enumerated() {
        if let mac = nameToMac[addr.name] {
            addresses[i] = (name: addr.name, addr: addr.addr, mac : mac)
        }
    }

    return addresses
}

If you will use this inside a framework, you have to add a .modelmap file with this configuration inside it

module ifaddrs [system]  [extern_c] {
    header
    "/Applications/Xcode_7.3.1.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/ifaddrs.h"
    export *
}

module net [system]  [extern_c] {

     module types {
         header "/usr/include/sys/types.h"
         export *
     }

     module if_dl {

         header
         "/usr/include/net/if_dl.h"
         export *
     }

 }

and then in your .swift file

import ifaddrs
import net.if_dl
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!