Search code examples
swiftsockaddrgetnameinfo

Swift getnameinfo unreliable results for IPv6


I have the following extension on sockaddr:

extension sockaddr {
  /// Indicates if this is an IPv4 address.
  var isIPv4: Bool {
    return sa_family == UInt8(AF_INET)
  }

  /// Indicates if this is an IPv6 address.
  var isIPv6: Bool {
    return sa_family == UInt8(AF_INET6)
  }

  /// Returns the address in string notation.
  var address: String? {
    var result: String = ""
    var me = self
    var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))

    if getnameinfo(&me, socklen_t(me.sa_len), &hostname, socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST) == 0 {
       result = String(cString: hostname)
    }

    return result
  }
}

In an other part of my code I'm calling getifaddrs to get the interface addresses of the current device. The code above works fine for IPv4, but is somewhat unreliable for IPv6.

I get results like: 192.168.1.10 and fe80::e0fa:1204:100:0

When I change the line var result: String = "" to var result: String? = nil. The IPv6 addresses suddenly become fe80::, the rest is cut off.

Even weirder, when I just switch the var result and the var me = self lines like this:

extension sockaddr {
  /// Indicates if this is an IPv4 address.
  var isIPv4: Bool {
    return sa_family == UInt8(AF_INET)
  }

  /// Indicates if this is an IPv6 address.
  var isIPv6: Bool {
    return sa_family == UInt8(AF_INET6)
  }

  /// Returns the address in string notation.
  var address: String? {
    var me = self
    var result: String = ""
    var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))

    if getnameinfo(&me, socklen_t(me.sa_len), &hostname, socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST) == 0 {
       result = String(cString: hostname)
    }

    return result
  }
}

Then the function will only work for IPv4 addresses. The getnameinfo will return 4 (FAIL).

This is during debugging, with no optimizations that I know of. It doesn't matter if I run it on a simulator or real device.

Could someone please explain why this is happening?


Solution

  • The problem is that getnameinfo expects a pointer that can either be a sockaddr_in or a sockaddr_in6. The definition of the function is a bit confusing because it expects a sockaddr pointer.

    Because I'm using an extension to extract the IP address a copy is being made of the memory contents. This isn't a problem for IPv4 because the size of a sockaddr_in is the same size as a sockaddr. However for IPv6, the sockaddr_in6 is larger than the sockaddr struct and some relevant information is cut off.

    The order of my commands probably determined what was stored in memory at the location directly after the sockaddr address. Sometimes it would look like a proper IPv6 address, but in reality incorrect.

    I've resolved this issue by moving my extension to the network interface ifaddrs:

    extension ifaddrs {
      /// Returns the IP address.
      var ipAddress: String? {
        var buffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
        let address = ifa_addr.pointee
        let result = getnameinfo(ifa_addr, socklen_t(address.sa_len), &buffer, socklen_t(buffer.count), nil, socklen_t(0), NI_NUMERICHOST)
        return result == 0 ? String(cString: buffer) : nil
      }
    }
    

    Thank you @MartinR finding the cause of the problem!