12

I want to get the IP address (like 192.168.0.1 or 87.12.56.50) from DNS query in Swift. I tried 100 times with 100 different methods ... Nothing helped me, so I'll have to ask for help. This is my code so far:

let host = CFHostCreateWithName(nil,"subdomain.of.stackoverflow.com").takeUnretainedValue();
CFHostStartInfoResolution(host, .Addresses, nil);
var success: Boolean = 0;
let addresses = CFHostGetAddressing(host, &success).takeUnretainedValue() as NSArray;
if(addresses.count > 0){
   let theAddress = addresses[0] as NSData;
   println(theAddress);
}

OK ... These are the links for the code I tried to implement without success: https://gist.github.com/mikeash/bca3a341db74221625f5
How to perform DNS query on iOS
Create an Array in Swift from an NSData Object
Does CFHostGetAddressing() support ipv6 DNS entries?
Do a simple DNS lookup in Swift

jtbandes
  • 115,675
  • 35
  • 233
  • 266
Hristo Atanasov
  • 1,236
  • 2
  • 18
  • 25

3 Answers3

37

Your code retrieves the address as a "socket address" structure. getnameinfo() can be used to convert the address into a numerical IP string (code recycled from https://stackoverflow.com/a/25627545/1187415, now updated to Swift 2):

let host = CFHostCreateWithName(nil,"www.google.com").takeRetainedValue()
CFHostStartInfoResolution(host, .Addresses, nil)
var success: DarwinBoolean = false
if let addresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as NSArray?,
    let theAddress = addresses.firstObject as? NSData {
    var hostname = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
    if getnameinfo(UnsafePointer(theAddress.bytes), socklen_t(theAddress.length),
        &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 {
            if let numAddress = String.fromCString(hostname) {
                print(numAddress)
            }
    }
}

Output (example): 173.194.112.147

Note also the usage of takeRetainedValue() in the first line, because CFHostCreateWithName() has "Create" in its name the therefore returns a (+1) retained object.


Update for Swift 3/Xcode 8:

let host = CFHostCreateWithName(nil,"www.google.com" as CFString).takeRetainedValue()
CFHostStartInfoResolution(host, .addresses, nil)
var success: DarwinBoolean = false
if let addresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as NSArray?,
    let theAddress = addresses.firstObject as? NSData {
    var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
    if getnameinfo(theAddress.bytes.assumingMemoryBound(to: sockaddr.self), socklen_t(theAddress.length),
                   &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 {
        let numAddress = String(cString: hostname)
        print(numAddress)
    }
}

Or, to get all IP addresses for the host:

let host = CFHostCreateWithName(nil,"www.google.com" as CFString).takeRetainedValue()
CFHostStartInfoResolution(host, .addresses, nil)
var success: DarwinBoolean = false
if let addresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as NSArray? {
    for case let theAddress as NSData in addresses {
        var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
        if getnameinfo(theAddress.bytes.assumingMemoryBound(to: sockaddr.self), socklen_t(theAddress.length),
                       &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 {
            let numAddress = String(cString: hostname)
            print(numAddress)
        }
    }
}
Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    You are the man! I'm very new to Swift and believe me ... you've added only 5 line to my code, but I don't understant a word of it. So you saved me time. I wouldn not do this by myself! Thanx! – Hristo Atanasov Sep 17 '14 at 13:12
  • @ХристоАтанасов: So you should now read the documentation on getnameinfo() and all that stuff until you understand it :) – Martin R Sep 17 '14 at 13:13
  • 3
    I've run into a problem. If the hostname string is, for example, `hello`, the program crashes saying `fatal error: unexpectedly found nil while unwrapping an Optional value`. – Matt Oct 01 '14 at 23:54
  • Probably you have to make some validations if the host name is valid domain and use it if it is .. – Hristo Atanasov Oct 15 '14 at 14:00
  • @Matt: you have to add exclamation mark at "as!" in next statement let theAddress = addresses[0] as NSData; – Oleg Novosad Jul 01 '15 at 07:22
  • I am sorry whose IP is it? google's ip or your device ip? – Saty Nov 23 '15 at 06:40
  • @Saty: This returns the IP address of the DNS name resolution, in this example the (first) IP address of "www.google.com". – Martin R Nov 23 '15 at 06:44
  • @Matt: That *is* updated for Swift 2. I just double-checked that it still compiles with Xcode 7.2. – Martin R Jan 05 '16 at 21:45
  • @MartinR Thanks for the contribution! When I use it I get the IPv6 address, anyway I can get the IPv4, what do I need to change? – Kostas Mar 01 '17 at 20:54
  • @Kostas: The above code only extracts the IP address from the first element of the `addresses` array. I have added another variant which prints all IP addresses. – Martin R Mar 01 '17 at 21:00
  • I just updated my answer [here](https://stackoverflow.com/questions/24794997/convert-nsdata-to-sockaddr-struct-in-swift) for Swift 5. It uses `inet_ntop()` instead of `getnameinfo()`. However I'm not very informed about network programming — which one should be preferred and when? Perhaps one of these questions should be closed as duplicate of the other. – jtbandes Apr 23 '20 at 01:46
  • Upon further reading I think `getnameinfo` is better, so I'll close the other question. – jtbandes Apr 23 '20 at 01:49
  • The above returns IPv4 address for me, how can I adjust this so it returns the IPv6 address? – Bob The Coder Apr 20 '23 at 15:09
  • @BobTheCoder: That code returns both IPv4 and IPv6 addresses, if available. Perhaps your router/DNS server does not support IPv6? – Martin R Apr 20 '23 at 15:20
  • @MartinR Maybe I am misunderstanding sorry, is there anyway I can get the IPv6 equivalent of the IPv4 address that is returned from this function? – Bob The Coder Apr 20 '23 at 16:07
  • @BobTheCoder: I am not sure if I understand you. Do you mean the [IPv4-mapped IPv6 address](https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses)? I am afraid that I can not help you with that. – Martin R Apr 21 '23 at 04:57
  • @MartinR On mac in my terminal if I run `dig A +short apple.com` and `dig AAAA +short apple.com` I get the corresponding IPv4 and IPv6 addresses, I'm trying to figure out how to do the same in swift but am unsure exactly how. – Bob The Coder Apr 21 '23 at 14:56
  • @BobTheCoder: In fact I get two addresses (2620:149:af0::10 and 17.253.144.10) for apple.com when I run the code in my home WiFi network, but in my companies WiFi network I get only the IPv4 address. Right now I do not know why that is. – Martin R Apr 21 '23 at 16:40
1

Refer to the following Swift code to get DNS resolution for a website. One method uses CFHostStartInfoResolution whereas other one uses gethostbyname.

Both these APIs support all/multiple IP address resolution.

private func urlToIP_cfHostResolution(_ url: String) -> [String] {

    var ipList: [String] = []

    let host = CFHostCreateWithName(nil,url as CFString).takeRetainedValue()

    CFHostStartInfoResolution(host, .addresses, nil)

    var success: DarwinBoolean = false

    if let addresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as NSArray? {

        for case let theAddress as NSData in addresses {

            var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))

            if getnameinfo(theAddress.bytes.assumingMemoryBound(to: sockaddr.self), socklen_t(theAddress.length),
                       &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 {

                ipList.append(String(cString: hostname))
            }    
        }
    }

    return ipList
}

This method returns ["151.101.129.69", "151.101.1.69", "151.101.193.69", "151.101.65.69"] for www.stackoverflow.com

private func urlToIP_gethostbyname(_ url: URL) -> [String] {

    var ipList: [String] = []

    guard let hostname = url.host else {

        return ipList
    }

    guard let host = hostname.withCString({gethostbyname($0)}) else {

        return ipList
    }

    guard host.pointee.h_length > 0 else {

        return ipList
    }

    var index = 0

    while host.pointee.h_addr_list[index] != nil {

        var addr: in_addr = in_addr()

        memcpy(&addr.s_addr, host.pointee.h_addr_list[index], Int(host.pointee.h_length))

        guard let remoteIPAsC = inet_ntoa(addr) else {

            return ipList
        }

        ipList.append(String.init(cString: remoteIPAsC))

        index += 1
    }

    return ipList
}

This method also returns ["151.101.129.69", "151.101.1.69", "151.101.193.69", "151.101.65.69"] for www.stackoverflow.com

Hope this helps.

sudhanshu-shishodia
  • 1,100
  • 1
  • 11
  • 25
0

A number of the answers are using the deprecated bytes to retrieve the sockaddr to supply to getnameinfo. We’d use address.withUnsafeBytes now. For example, in Swift 5:

/// Returns the string representation of the supplied address.
///
/// - parameter address: Contains a `(struct sockaddr)` with the address to render.
///
/// - returns: A string representation of that address.

func stringRepresentation(forAddress address: Data) -> String? {
    address.withUnsafeBytes { pointer in
        var hostStr = [Int8](repeating: 0, count: Int(NI_MAXHOST))

        let result = getnameinfo(
            pointer.baseAddress?.assumingMemoryBound(to: sockaddr.self),
            socklen_t(address.count),
            &hostStr,
            socklen_t(hostStr.count),
            nil,
            0,
            NI_NUMERICHOST
        )
        guard result == 0 else { return nil }
        return String(cString: hostStr)
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044