IP addresses from NSNetService - why does my code fail in release builds?

I have some code that I have cobbled togther without really understanding the details.

It works great in debug builds, but fails if you build for release.


I found this out when I released an update 😟


can anyone explain what is going on?


extension NetService {
        var debugOnlyIPAddresses : [String] {
            var ipAddresses:[String] = []
  
            guard let addresses = addresses else {
                return []
            }
  
            for address in addresses {
                let data = address as NSData
  
                let inetAddress: sockaddr_in = data.castToCPointer()
                if inetAddress.sin_family == __uint8_t(AF_INET) {
  
                    if let ip = String(cString: inet_ntoa(inetAddress.sin_addr), encoding: .ascii) {
                        ipAddresses.append(ip)
                    }
                } else if inetAddress.sin_family == __uint8_t(AF_INET6) {
                    let inetAddress6: sockaddr_in6 = data.castToCPointer()
                    let ipStringBuffer = UnsafeMutablePointer.allocate(capacity: Int(INET6_ADDRSTRLEN))
                    var addr = inetAddress6.sin6_addr
  
                    if let ipString = inet_ntop(Int32(inetAddress6.sin6_family), &addr, ipStringBuffer, __uint32_t(INET6_ADDRSTRLEN)) {
                        if let ip = String(cString: ipString, encoding: .ascii) {
                            // IPv6
                            ipAddresses.append(ip)
                        }
                    }
  
                    ipStringBuffer.deallocate()
                }
            }
            print("returning \(ipAddresses)")
            return ipAddresses
        }
}


in release builds, inetAddress.sin_family is always 0

Replies

It looks like you may be using a custom extension for Data on castToCPointer. This would be the first place I would look at. Also, I would run some tests to make sure the IP address you are expecting is still attached to the service. If for some reason a service is no longer available this could give you problems when working with the IP. Use dns-sd on the terminal to verify this.


Also, I wanted to ask you about why you are using the IP address with NSNetService? Are you connecting with an IP address or with a service endpoint? There can good reasons to use an IP in this context, for example if you are browsing HTTP services and want to use the IP to resolve a hostname, however if you are just wanting to connect to a service I would not use the IP, but rather the service endpoint instead.


Matt Eaton

DTS Engineering, CoreOS

meaton3 at apple.com

I have cobbled togther without really understanding the details.

Yeah, it shows )-:

The main problem here is that you’re using obsolete DNS APIs, like

inet_ntoa
. Those APIs are particularly tricky to call from Swift but, even from C, they’re a pain because you end up having to write different code for IPv4 and IPv6.

The correct API to use here is

getnameinfo
. If you pass in
NI_NUMERICHOST | NI_NUMERICSERV
you’re guaranteed to get numeric results rather than having it hit the ‘wire’.

However, even that API isn’t particularly nice to call from Swift. Here’s some code that shows how it’s done:

func nameForAddress(_ address: Data) -> String {
    var host = [CChar](repeating: 0, count: Int(NI_MAXHOST))
    let err = address.withUnsafeBytes { buf -> Int32 in
        let sa = buf.baseAddress!.assumingMemoryBound(to: sockaddr.self)
        let saLen = socklen_t(buf.count)
        return getnameinfo(sa, saLen, &host, socklen_t(host.count), nil, 0, NI_NUMERICHOST | NI_NUMERICSERV)
    }
    guard err == 0 else { return "?" }
    return String(cString: host)
}

Share and Enjoy

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

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