2

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?

Yvo
  • 18,681
  • 11
  • 71
  • 90
  • As I said in response to your previous question https://stackoverflow.com/questions/44426911/xcode-address-sanitizer-issue-with-sockaddr: A `struct sockaddr` *cannot* hold an IPv6 address. So if you get correct results then by pure chance. – How do you use that extension method with `getifaddrs()`? Did you have a look at https://stackoverflow.com/a/25627545/1187415, which (now) works correctly? – Martin R Jun 10 '17 at 21:10
  • Yes I did have a look. The line `let addr = ptr.pointee.ifa_addr.pointee` results in a `sockaddr`. If you check the definition, you'll see the following: `public var ifa_addr: UnsafeMutablePointer!`. Basically all I did is move the part that comes after that to an extension on `sockaddr`. – Yvo Jun 10 '17 at 21:24
  • Also the definition of `getnameinfo` starts with `public func getnameinfo(_: UnsafePointer!,`. And `getnameinfo` can resolve both IPv4 and IPv6 addresses. – Yvo Jun 10 '17 at 21:27
  • 1
    `getnameinfo()` takes a *pointer* to a `sockaddr`, not the `sockaddr` itself. `ptr.pointee.ifa_addr` is the pointer obtained from `getifaddrs()` and can point to an IPv4 or to an IPv6 address. – You are right: `let addr = ptr.pointee.ifa_addr.pointee` copies only the "common" part, that is for convenience and only used to access `addr.sa_family`. But the *pointer* is passed to `getnameinfo()` (Actually I had that wrong originally, if you look at the last edit of the answer) – Martin R Jun 10 '17 at 21:40
  • Ok, but what is the difference between doing it that way and doing it via an extension? I'm passing `&me` to `getnameinfo` which is a pointer to a `sockaddr` right? – Yvo Jun 10 '17 at 22:00
  • 1
    As soon as you copy `let addr = ptr.pointee.ifa_addr.pointee`, a part of the IPv6 address is lost, only the "common" part (`sockaddr`) is copied. – Martin R Jun 10 '17 at 22:02

1 Answers1

2

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!

Yvo
  • 18,681
  • 11
  • 71
  • 90