Manage Ifaddrs to Return MAC Addresses as Well in Swift

Manage ifaddrs to return MAC addresses as well in Swift

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

How to get Ip address in swift

As it turned out in the discussion, OP needs the interface address on a Mac and not on an iOS device as I thought initially. The code referenced in the question checks for the
interface name "en0", which is the WiFi interface on the iPhone. On a Mac it makes more
sense to check for any "up-and-running" interface instead.
Therefore I have rewritten the answer. It is now a Swift translation of the code in
Detect any connected network.


getifaddrs() is defined in <ifaddrs.h>, which is not included by default.
Therefore you have to create a bridging header and add

#include <ifaddrs.h>

The following function returns
an array with the names of all local "up-and-running" network interfaces.

func getIFAddresses() -> [String] {
var addresses = [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.memory

// 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) {

// Convert interface address to a human readable string:
var hostname = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
if (getnameinfo(ptr.memory.ifa_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)
}

return addresses
}

Update for Swift 3: In addition to adopting the code to the
many changes in Swift 3,
iterating over all interfaces can now use the new generalized
sequence() function:

func getIFAddresses() -> [String] {
var addresses = [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)
let addr = ptr.pointee.ifa_addr.pointee

// 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) {

// Convert interface address to a human readable string:
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
if (getnameinfo(ptr.pointee.ifa_addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count),
nil, socklen_t(0), NI_NUMERICHOST) == 0) {
let address = String(cString: hostname)
addresses.append(address)
}
}
}
}

freeifaddrs(ifaddr)
return addresses
}

MAC address with getifaddrs

Here is the code for getting IP and MAC addresses

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <net/if.h>

int main(void)
{
char buf[8192] = {0};
struct ifconf ifc = {0};
struct ifreq *ifr = NULL;
int sck = 0;
int nInterfaces = 0;
int i = 0;
char ip[INET6_ADDRSTRLEN] = {0};
char macp[19];
struct ifreq *item;
struct sockaddr *addr;

/* Get a socket handle. */
sck = socket(PF_INET, SOCK_DGRAM, 0);
if(sck < 0)
{
perror("socket");
return 1;
}

/* Query available interfaces. */
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if(ioctl(sck, SIOCGIFCONF, &ifc) < 0)
{
perror("ioctl(SIOCGIFCONF)");
return 1;
}

/* Iterate through the list of interfaces. */
ifr = ifc.ifc_req;
nInterfaces = ifc.ifc_len / sizeof(struct ifreq);

for(i = 0; i < nInterfaces; i++)
{
item = &ifr[i];

addr = &(item->ifr_addr);

/* Get the IP address*/
if(ioctl(sck, SIOCGIFADDR, item) < 0)
{
perror("ioctl(OSIOCGIFADDR)");
}

if (inet_ntop(AF_INET, &(((struct sockaddr_in *)addr)->sin_addr), ip, sizeof ip) == NULL) //vracia adresu interf
{
perror("inet_ntop");
continue;
}

/* Get the MAC address */
if(ioctl(sck, SIOCGIFHWADDR, item) < 0) {
perror("ioctl(SIOCGIFHWADDR)");
return 1;
}

/* display result */

sprintf(macp, " %02x:%02x:%02x:%02x:%02x:%02x",
(unsigned char)item->ifr_hwaddr.sa_data[0],
(unsigned char)item->ifr_hwaddr.sa_data[1],
(unsigned char)item->ifr_hwaddr.sa_data[2],
(unsigned char)item->ifr_hwaddr.sa_data[3],
(unsigned char)item->ifr_hwaddr.sa_data[4],
(unsigned char)item->ifr_hwaddr.sa_data[5]);

printf("%s %s ", ip, macp);

}

return 0;
}

Trouble with MAC address in iOS 7.0.2

This is a privacy change, so there's no way to get at the underlying MAC address. https://developer.apple.com/news/?id=8222013a

If your apps use the MAC address to identify an iOS device, the system will return the same static value for all devices running iOS 7. Please update your apps to use the identifierForVendor property of UIDevice. If you need an identifier for advertising purposes, use the advertisingIdentifier property of ASIdentifierManager.

Binary operator cannot be applied to Clong in Swift

#define MAXADDRS    32

is imported to Swift as

public var MAXADDRS: Int32 { get }

On the other hand, CLong is an alias for Int ("The C 'long' type.")
Therefore you need to convert all values to a common type. Since
array subscripting requires an Int index, converting MAXADDRS
to Int might be the easiest solution:

var i = 0 // Int
for (i=0; i < Int(MAXADDRS); ++i) {

}

or more simply:

for i in 0 ..< Int(MAXADDRS) {

}

How can I programmatically get the MAC address of an iphone


NOTE As of iOS7, you can no longer retrieve device MAC addresses. A fixed value will be returned rather than the actual MAC


Somthing I stumbled across a while ago. Originally from here I modified it a bit and cleaned things up.

IPAddress.h

IPAddress.c

And to use it

InitAddresses();
GetIPAddresses();
GetHWAddresses();

int i;
NSString *deviceIP = nil;
for (i=0; i<MAXADDRS; ++i)
{
static unsigned long localHost = 0x7F000001; // 127.0.0.1
unsigned long theAddr;

theAddr = ip_addrs[i];

if (theAddr == 0) break;
if (theAddr == localHost) continue;

NSLog(@"Name: %s MAC: %s IP: %s\n", if_names[i], hw_addrs[i], ip_names[i]);

//decided what adapter you want details for
if (strncmp(if_names[i], "en", 2) == 0)
{
NSLog(@"Adapter en has a IP of %s", ip_names[i]);
}
}

Adapter names vary depending on the simulator/device as well as wifi or cell on the device.

Count IP addresses

If you need to convert an IP to a range, you'll need a function that converts an IPv4 value to an integer, then do math on those:

require 'ipaddr'

def ip(string)
IPAddr.new(string).to_i
end

def parse_ip(string)
string.split(/\s+\-\s+/).collect { |v| ip(v) }
end

def ip_range(string)
ips = parse_ip(string)

ips.last - ips.first + 1
end

ip_range("10.2.3.10 - 10.2.3.15")
# => 6

That should do it.



Related Topics



Leave a reply



Submit