28

How do i get my local IpAddress?

I tried to use this Obj C example: how to get ip address of iphone programmatically

When i get to the function getifaddrs() i can't get any further. i can't use the function.

Is there an alternative way to do this, or am I approaching this the wrong way?

Community
  • 1
  • 1
Mads Gadeberg
  • 1,429
  • 3
  • 20
  • 30
  • Swift cannot communicate with C directly, you have to use Objective-C as a bridge. – akashivskyy Sep 02 '14 at 15:02
  • 1
    @akashivskyy: That is not true in general. *Many* C functions are exposed to Swift. `getifaddrs()` however seems to be invisible to Swift, perhaps the used data structures are incompatible. – Martin R Sep 02 '14 at 15:12
  • @akashivskyy this isn't completely true, there are many cases where swift can communicate directly with C, eg. pretty much all of the CoreFoundation API's are accessible. That said, in this specific case it's probably going to be easier to write an Objective-C glue layer because of the required pointer arithmetic and structure unpacking. – David Berry Sep 02 '14 at 15:19

5 Answers5

55

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
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • When i run it i get that all instance variables in tmpAddr have the value 0. so they don't point to anything. I assume this isn't correct? the if statement ofc fails. – Mads Gadeberg Sep 02 '14 at 16:42
  • @MadsGadeberg: Strange, I have tested the code on a device and in the Simulator and it worked for me. - Which if statement fails? – Martin R Sep 02 '14 at 16:52
  • The one where we check if sa_family is AF_INET. Does it matter if I'm on wifi? – Mads Gadeberg Sep 02 '14 at 17:10
  • @MadsGadeberg: Try AF_INET6 instead. – Martin R Sep 02 '14 at 17:11
  • @MadsGadeberg: I have modified the function so that it prints some debug output. – Martin R Sep 02 '14 at 17:17
  • Same result. The issue is that the pointer tmpAddr.ifa_addr have the value 0x0 so it doesn't point to an instance. therefore the if statement of course fails. Im not very good at this apple world yet but can't it be the case that we are asigning the tmpAddr wrong? (Don't know what pt.memory is) – Mads Gadeberg Sep 02 '14 at 17:20
  • Getting this from the edited method: name: lo0, family: 18 name: lo0, family: 30 name: lo0, family: 2 name: lo0, family: 30 name: gif0, family: 18 name: stf0, family: 18 name: en0, family: 18 name: en1, family: 18 name: en1, family: 30 name: en1, family: 2 name: en2, family: 18 name: fw0, family: 18 name: p2p0, family: 18 name: bridge0, family: 18 – Mads Gadeberg Sep 02 '14 at 17:27
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/60470/discussion-between-martin-r-and-mads-gadeberg). – Martin R Sep 02 '14 at 17:32
  • So, this was for getting the ip address on a mac or am I not understanding it all? Anyone have any ideas for getting the mobile device's IP address? – fischgeek Apr 23 '15 at 02:37
  • @MartinR are you still around for help with implementing this? :) – bandoy123 Nov 26 '15 at 11:30
  • @MartinR, Why do you need an array? Why isn't the first found address is not enough? – Luda Apr 26 '16 at 15:38
  • @Luda: You may want all addresses, or just one, depending on what you want to do with it. – Here is a similar Q&A which retrieves just one (the "en0") interface address: http://stackoverflow.com/a/30754194/1187415. – Martin R Apr 26 '16 at 16:00
  • @MartinR, What I didn't understand is how can a phone have multiple addresses? – Luda May 01 '16 at 07:38
  • @Luda: Actually this question was about interfaces on a Mac. That wasn't clear in the question but became apparent in the chat discussion. I have now re-tagged the question. – Martin R May 01 '16 at 08:17
  • @MartinR -> I am getting 4 results. Which one is the WiFi and Cellular Network? – cengo Nov 03 '16 at 21:33
  • @cengo: The "en0" interface is WiFi, compare http://stackoverflow.com/a/30754194/1187415. – Martin R Nov 03 '16 at 21:36
  • @MartinR thanks for the quick response, I need cellular network ip address too. Do you know how? – cengo Nov 03 '16 at 21:38
  • @MartinR : Will this capture Mobile Data IP address? – Jayprakash Dubey Mar 08 '17 at 06:01
  • @fischgeek see my answer, it works on mac, it works on iOS, it works even on Playground https://stackoverflow.com/a/44543696/3441734 – user3441734 Jun 14 '17 at 15:27
  • Hi @MartinR I am trying to get this to detect which interface is up and print the IP only for that interface. I've adapted the code to use getIFAddresses[1] which kinda works but is bad practice. Any advice? – DanTdd May 09 '18 at 23:55
2

Answering to Is there an alternative way to getifaddrs() for getting the ip-address?

Yes, there is an alternative way. You can also get the ip address by utilizing ioctl(). The cleanest way would be doing it in C and then wrapping it up in Swift. So consider this:

Create a C Source File (Xcode should create it together with a .h) and remember adding the header into your project's bridging header, or into umbrella-header if you have a cocoa touch framework.

Add the following into your .c source and method's declaration into .h:

#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>

int _interfaceAddressForName(char* interfaceName, struct sockaddr* interfaceAddress) {

    struct ifreq ifr;
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    ifr.ifr_addr.sa_family = AF_INET;

    strncpy(ifr.ifr_name, interfaceName, IFNAMSIZ-1);

    int ioctl_res;
    if ( (ioctl_res = ioctl(fd, SIOCGIFADDR, &ifr)) < 0){
        return ioctl_res;
    }

    close(fd);
    memcpy(interfaceAddress, &ifr.ifr_addr, sizeof(struct sockaddr));
    return 0;
}

Your Swift wrapper might look something like:

public enum Error:ErrorType {
    case IOCTLFailed(Int32)
    case StringIsNotAnASCIIString
}

public func interfaceAddress(forInterfaceWithName interfaceName: String) throws -> sockaddr_in {

    guard let cString = interfaceName.cStringUsingEncoding(NSASCIIStringEncoding) else {
        throw Error.StringIsNotAnASCIIString
    }

    let addressPtr = UnsafeMutablePointer<sockaddr>.alloc(1)
    let ioctl_res = _interfaceAddressForName(strdup(cString), addressPtr)
    let address = addressPtr.move()
    addressPtr.dealloc(1)

    if ioctl_res < 0 {
        throw Error.IOCTLFailed(errno)
    } else {
        return unsafeBitCast(address, sockaddr_in.self)
    }
}

Then you can use it in your code like:

let interfaceName = "en0"
do {
    let wlanInterfaceAddress = try interfaceAddress(forInterfaceWithName: interfaceName)
    print(String.fromCString(inet_ntoa(wlanInterfaceAddress.sin_addr))!)
} catch {
    if case Error.IOCTLFailed(let errno) = error where errno == ENXIO {
        print("interface(\(interfaceName)) is not available")
    } else {
        print(error)
    }
}

en0 typically is the interface you need, which stands for WLAN

If you also need to know available interface names you can utilize if_indextoname():

public func interfaceNames() -> [String] {

    let MAX_INTERFACES = 128;

    var interfaceNames = [String]()
    let interfaceNamePtr = UnsafeMutablePointer<Int8>.alloc(Int(IF_NAMESIZE))
    for interfaceIndex in 1...MAX_INTERFACES {
        if (if_indextoname(UInt32(interfaceIndex), interfaceNamePtr) != nil){
            if let interfaceName = String.fromCString(interfaceNamePtr) {
                interfaceNames.append(interfaceName)
            }
        } else {
            break
        }
    }

    interfaceNamePtr.dealloc(Int(IF_NAMESIZE))
    return interfaceNames
}
ambientlight
  • 7,212
  • 3
  • 49
  • 61
2
   func getIfConfigOutput() -> [String:String] {
    let cmd = "for i in $(ifconfig -lu); do if ifconfig $i | grep -q \"status: active\" ; then echo $i; fi; done"
    let interfaceString = shell(cmd)
    let interfaceArray = interfaceString.components(separatedBy: "\n")
    var finalDictionary:[String:String] = [String:String]()
    for (i,_) in interfaceArray.enumerated() {
        if (interfaceArray[i].hasPrefix("en")){
            let sp = shell("ifconfig \(interfaceArray[i]) | grep \"inet \" | grep -v 127.0.0.1 | cut -d\\  -f2")
          finalDictionary[interfaceArray[i]] = sp.replacingOccurrences(of: "\n", with: "")
        }
    }
  print(finalDictionary)
    return finalDictionary
}
func shell(_ args: String) -> String {
    var outstr = ""
    let task = Process()
    task.launchPath = "/bin/sh"
    task.arguments = ["-c", args]
    let pipe = Pipe()
    task.standardOutput = pipe
    task.launch()
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    if let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue) {
        outstr = output as String
    }
    task.waitUntilExit()
    return outstr
}

This will help you. Code Returns a Dictionary with Interfaces and IP Addresses Associated with it.

1

In Swift 5 you can use ioctl directly with Swift rather than resorting to 'C' to get the IP address details for a given interface.

Like this:

private enum AddressRequestType {
    case ipAddress
    case netmask
}

// From BDS ioccom.h
// Macro to create ioctl request
private static func _IOC (_ io: UInt32, _ group: UInt32, _ num: UInt32, _ len: UInt32) -> UInt32 {
    let rv = io | (( len & UInt32(IOCPARM_MASK)) << 16) | ((group << 8) | num)
    return rv
}

// Macro to create read/write IOrequest
private static func _IOWR (_ group: Character , _ num : UInt32, _ size: UInt32) -> UInt32 {
    return _IOC(IOC_INOUT, UInt32 (group.asciiValue!), num, size)
}

private static func _interfaceAddressForName (_ name: String, _ requestType: AddressRequestType) throws -> String {
    
    var ifr = ifreq ()
    ifr.ifr_ifru.ifru_addr.sa_family = sa_family_t(AF_INET)
    
    // Copy the name into a zero padded 16 CChar buffer
    
    let ifNameSize = Int (IFNAMSIZ)
    var b = [CChar] (repeating: 0, count: ifNameSize)
    strncpy (&b, name, ifNameSize)
    
    // Convert the buffer to a 16 CChar tuple - that's what ifreq needs
    ifr.ifr_name = (b [0], b [1], b [2], b [3], b [4], b [5], b [6], b [7], b [8], b [9], b [10], b [11], b [12], b [13], b [14], b [15])
    
    let ioRequest: UInt32 = {
        switch requestType {
        case .ipAddress: return _IOWR("i", 33, UInt32(MemoryLayout<ifreq>.size))    // Magic number SIOCGIFADDR - see sockio.h
        case .netmask: return _IOWR("i", 37, UInt32(MemoryLayout<ifreq>.size))      // Magic number SIOCGIFNETMASK
        }
    } ()
    
    if ioctl(socket(AF_INET, SOCK_DGRAM, 0), UInt(ioRequest), &ifr) < 0 {
        throw POSIXError (POSIXErrorCode (rawValue: errno) ?? POSIXErrorCode.EINVAL)
    }
    
    let sin = unsafeBitCast(ifr.ifr_ifru.ifru_addr, to: sockaddr_in.self)
    let rv = String (cString: inet_ntoa (sin.sin_addr))
    
    return rv
}

public static func getInterfaceIPAddress (interfaceName: String) throws -> String {
    return try _interfaceAddressForName(interfaceName, .ipAddress)
}

public static func getInterfaceNetMask (interfaceName: String) throws -> String {
    return try _interfaceAddressForName(interfaceName, .netmask)
}
Colin Wilson
  • 251
  • 4
  • 6
1

Code works for Swift 5+ and iOS 13+

To get IPAddress of device on turning Wifi or Wired or Mobile data ON, use below method :

func getIPAddress() -> String? {
    var address : String?

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

    // For each interface ...
    for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
        let interface = ifptr.pointee

        // Check for IPv4 or IPv6 interface:
        let addrFamily = interface.ifa_addr.pointee.sa_family
        if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {

            // Check interface name:
            // wifi = ["en0"]
            // wired = ["en2", "en3", "en4"]
            // cellular = ["pdp_ip0","pdp_ip1","pdp_ip2","pdp_ip3"]
            
            let name = String(cString: interface.ifa_name)
            if  name == "en0" || name == "en2" || name == "en3" || name == "en4" || name == "pdp_ip0" || name == "pdp_ip1" || name == "pdp_ip2" || name == "pdp_ip3" {

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

    return address
}

Here, code/checks works for Wifi, Wired and Mobile data is:

if name == "en0" || name == "en2" || name == "en3" || name == "en4" || name == "pdp_ip0" || name == "pdp_ip1" || name == "pdp_ip2" || name == "pdp_ip3"

Hope will be helping! :)

JaspreetKour
  • 777
  • 9
  • 11