Get device IP

I have an iOS app and a MacOS app in which I want to display to the user it's device's local IP.

If there is more than one IP, I would dispaly one of them, not matter which one.


This is the code I'm using:


func getIFAddresses() -> String {
        //var addresses = [String]()
        var address = "N/A"
        deviceLocalIp = "N/A"
       
        // Get list of all interfaces on the local machine:
        var ifaddr : UnsafeMutablePointer?
        guard getifaddrs(&ifaddr) == 0 else { return address }
        guard let firstAddr = ifaddr else { return address }
       
        // For each interface ...
        for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
            let flags = Int32(ptr.pointee.ifa_flags)
            var 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) {
                   
                    let interfaceName =  String.init(cString: &ptr.pointee.ifa_name.pointee)
                    //DDLogInfo("interfaceName:\(interfaceName)")
                   
                    // Convert interface address to a human readable string:
                    var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
                    if (getnameinfo(&addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count),
                                    nil, socklen_t(0), NI_NUMERICHOST) == 0) {
                       
                        if interfaceName == "en0" {
                            deviceLocalIp = String(cString: hostname)
                            address = deviceLocalIp
                            break
                        }
                       
                        //if we don't have address from en0 - try get it from another interface
                        //(but prefer from en0)
                        if address == "N/A" && (interfaceName == "en0" || interfaceName == "en1" || interfaceName == "en2" || interfaceName == "pdp_ip" || interfaceName == "ap1") {
                            deviceLocalIp = String(cString: hostname)
                            address = deviceLocalIp
                        }
                       
                    }
                }
            }
        }
       
        freeifaddrs(ifaddr)
        return address
    }
}



For IPv4 it seems to work well.

For IPv6 (via Mac's Internet Sharing), I'm getting an IPv6 address, but it's not the address I'm expecting to connect -

at the Network I see that my device is connected and has the IP address X and the result I'm getting with this code is address Y.


P.S -

For debugging, I printed all the IPs, not just the first, and still didn't get the correct one..

Accepted Answer

Your code has a bug that causes it to fail with large (and hence IPv6) addresses. Specifically, in line 14 you assign

ptr.pointee.ifa_addr.pointee
to
addr
, but
ptr.pointee.ifa_addr.pointee
is of type
sockaddr
, which isn’t big enough to hold an IPv6 address. What you should be doing is assigning
addr
to
ptr.pointee.ifa_addr
, that is, an
UnsafeMutablePointer<sockaddr>
, and that passing that directly to
getnameinfo
.

Pasted in below is a function that prints the address for each interface.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
func printAddresses() {
    var addrList : UnsafeMutablePointer<ifaddrs>?
    guard
        getifaddrs(&addrList) == 0,
        let firstAddr = addrList
    else { return }
    defer { freeifaddrs(addrList) }
    for cursor in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
        let interfaceName = String(cString: cursor.pointee.ifa_name)
        let addrStr: String
        var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
        if
            let addr = cursor.pointee.ifa_addr,
            getnameinfo(addr, socklen_t(addr.pointee.sa_len), &hostname, socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST) == 0,
            hostname[0] != 0
        {
            addrStr = String(cString: hostname)
        } else {
            addrStr = "?"
        }
        print(interfaceName, addrStr)
    }
    return
}
  • Any chance for an ObjC variant? I believe the underlying getifaddrs freeifaddrs getnameinfo APIs should be the same, but some of the swift trickery here worries me... if I back-translated this to C or Obj-C I might introduce issues...

    I would believe Mr. Eskimo (whose OpenTransport lines of code I still keep somewhere...) had this code running long before swift existed :) So... it would be much appreciated if you pasted something here.

Add a Comment

Answers

You wrote:

If there is more than one IP, I would dispaly one of them, not matter which one.

and:

For IPv6 (via Mac's Internet Sharing), I'm getting an IPv6 address, but it's not the address I'm expecting …

Ah, um, isn’t that self contradictory? If you don’t care which IP address you get back you can’t then complain that you got the wrong one.

As I’ve said on many times, both here on forums and elsewhere, there’s no such thing as “the device’s IP address”. There is a set of IP addresses, which can be an arbitrary mix of IPv4 and IPv6. The only way to get one specific IP address is to apply constraints to narrow down that set. And for me to help you with that I’d need to know what constraints you want to apply.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

>I want to display to the user it's device's local IP.


Why? What's the goal? Why does the user need it? What will they do with that info once presented? Will if be part of a list of other info? Is there a spec you're trying to satisfy? Define 'local IP' context, specifically...as seen by what protocol, etc, vs. 'not matter which one'...doesn't matter to you or doesn't matter to the user, or? What type of app are you creating?


If you can shed light, someone may be able to make specific suggestions/alternatives, etc.


Good luck.

Post not yet marked as solved Up vote reply of KMT Down vote reply of KMT

Yes, it will be part of a list of other info. This is for a VPN app, where we show the user the IP he has at our server, and his "local" device IP - what we want for local IP is to show the same IP as can be seen via System Preferences -> Network -> and the IP of one of the connected devices.

I'll try to explain it better -

For the above code, if I'll print the IPs I'm getting, while running on the interfaces,

If I'm connected on IPv4 network, one of those IPs would be the same as the IP that can be seen at System Preferences -> Network -> and the IP of one of the connected devices.

But if I'm on IPv6 network (via Internet sharing), all the printed IPs are different than the IP of the connected device.

Your code has a bug that causes it to fail with large (and hence IPv6) addresses. Specifically, in line 14 you assign

ptr.pointee.ifa_addr.pointee
to
addr
, but
ptr.pointee.ifa_addr.pointee
is of type
sockaddr
, which isn’t big enough to hold an IPv6 address. What you should be doing is assigning
addr
to
ptr.pointee.ifa_addr
, that is, an
UnsafeMutablePointer<sockaddr>
, and that passing that directly to
getnameinfo
.

Pasted in below is a function that prints the address for each interface.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
func printAddresses() {
    var addrList : UnsafeMutablePointer<ifaddrs>?
    guard
        getifaddrs(&addrList) == 0,
        let firstAddr = addrList
    else { return }
    defer { freeifaddrs(addrList) }
    for cursor in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
        let interfaceName = String(cString: cursor.pointee.ifa_name)
        let addrStr: String
        var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
        if
            let addr = cursor.pointee.ifa_addr,
            getnameinfo(addr, socklen_t(addr.pointee.sa_len), &hostname, socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST) == 0,
            hostname[0] != 0
        {
            addrStr = String(cString: hostname)
        } else {
            addrStr = "?"
        }
        print(interfaceName, addrStr)
    }
    return
}
  • Any chance for an ObjC variant? I believe the underlying getifaddrs freeifaddrs getnameinfo APIs should be the same, but some of the swift trickery here worries me... if I back-translated this to C or Obj-C I might introduce issues...

    I would believe Mr. Eskimo (whose OpenTransport lines of code I still keep somewhere...) had this code running long before swift existed :) So... it would be much appreciated if you pasted something here.

Add a Comment

Wow, thanks! Great help as always!

  • very good sample,. works fine also under Xcode 13.x

    I DO KNOW is not the correct place, but only to notify.. in case others arrive here...

    Under UBUNTU (I am developing a SPM) I got:

     error: value of type 'sockaddr' has no member 'sa_len'

                                     socklen_t(addr.pointee.sa_len),

    I DID read all the stuff around, (for example Quinn wrappers on Swit.org) but no solution til now. thx in advance,.

Add a Comment

Any chance for an ObjC variant?

This is easier in Objective-C because the underlying API is C and Objective-C inherits all if its unsafeness (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

static void printAddresses(void) {
    struct ifaddrs * addrList;
    BOOL success = getifaddrs(&addrList) >= 0;
    if ( ! success ) { return; }
    struct ifaddrs * cursor = addrList;
    while (cursor != NULL) {
        char hostname[NI_MAXHOST];
        success = NO;
        if (cursor->ifa_addr != nil) {
            success = getnameinfo(cursor->ifa_addr, cursor->ifa_addr->sa_len, hostname, sizeof(hostname), NULL, 0, NI_NUMERICHOST) == 0;
        }
        if (success && (hostname[0] != 0)) {
            fprintf(stderr, "%s\n", hostname);
        } else {
            fprintf(stderr, "?\n");
        }
        cursor = cursor->ifa_next;
    }
    freeifaddrs(addrList);
}
  • Thanks. Should have suspected this...

    Living too many decades in the realm of C (errr and older...) I no longer see unsafeness there. Only freedom, responsibility and performance...

    But I fail to see how this is less safe Than the swift code - after all you kept all the conditionals in place. The only missing? point is freeing the addrList if exception occurs somewhere? but where should such exception come from? and if it comes, would freeing that memory save you from the situation?

    just wondering.

Add a Comment

But I fail to see how this is less safe than the Swift code

That depends on how you look at it. While the final code I posted isn’t any less safe — this API is defined in C, and hence fundamentally unsafe, and thus you have to use Swift’s unsafe features to use it — the process you use to get there is safer in Swift than it is in C. For example, in Swift the type system forces me to deal with the possibility that ifa_addr is nil, where in C I could blindly dereference it. In this case that mistake would be relatively benign [1] but in other cases it can easily create a security vulnerability.

Additionally, my experience is that most C programmers don’t know how unsafe their language is. For example, a lot of BSD Sockets code fails to follow C’s strict aliasing rules.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Assuming you’re on a platform where dereferencing a NULL pointer crashes! Personally, I cut my teeth programming C on traditional Mac OS, where that wasn’t the case, which probably explains a lot (-:

Under UBUNTU (I am developing a SPM) I got … 'sockaddr' has no member 'sa_len'

It seems like you already got an answer on Swift Forums.

My only comments are:

  • If you’re working with BSD Sockets, I strongly recommend that you get yourself a Unix-y text book on the subject. My go-to book for this is UNIX Network Programming www.unpbook.com.

  • It’s called BSD Sockets for a reason (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"