GuiSoySauce GuiSoySauce - 5 months ago 143
Swift Question

Manage ifaddrs to return MAC addresses as well in Swift

I've got the following code that successfully retrieves all the IPs connected to my router. But I need to a get the MAC Addresses for each IP.

So instead of addresses being returned as an array with

[ips]
, be returned as a dictionary
[ips:0000000, mac: 000000]


Is it possible to be achieved with changes to the following code (from How to get Ip address in swift)?

func getIFAddresses() -> [String] {


print("GET IF ADDRESSSE")

var addresses = [String]()

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

print("getifaddrs\(getifaddrs)")

// For each interface ...
for (var ptr = ifaddr; ptr != nil; ptr = ptr.memory.ifa_next) {
let flags = Int32(ptr.memory.ifa_flags)
var addr = ptr.memory.ifa_addr.memory

print("flags\(flags)")
print("addr\(addr)")

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



if addr.sa_family == UInt8(AF_INET) || addr.sa_family == UInt8(AF_INET6) {

print("addr.sa_family\(addr.sa_family)")

// Convert interface address to a human readable string:
var hostname = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)

print("hostname\(hostname)")

if (getnameinfo(
&addr, socklen_t(addr.sa_len),
&hostname,
socklen_t(hostname.count),
nil,
socklen_t(0), NI_NUMERICHOST) == 0) {


if let address = String.fromCString(hostname) {
addresses.append(address)
}
}
}
}
}
freeifaddrs(ifaddr)
print("freeifaddrs\(freeifaddrs)")
}
print("ADDRESSES \(addresses)")
return addresses


}

Answer

(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.

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:
                    let dl = UnsafePointer<sockaddr_dl>(addr)
                    let lladdr = UnsafeBufferPointer(start: UnsafePointer<Int8>(dl) + 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
}