【问题标题】:Swift getnameinfo unreliable results for IPv6IPv6的Swift getnameinfo结果不可靠
【发布时间】:2017-11-12 16:03:05
【问题描述】:

我在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
  }
}

在我的代码的另一部分,我调用getifaddrs 来获取当前设备的接口地址。上面的代码在 IPv4 上运行良好,但在 IPv6 上有些不可靠。

我得到如下结果:192.168.1.10fe80::e0fa:1204:100:0

当我将var result: String = "" 更改为var result: String? = nil 时。 IPv6地址突然变成fe80::,剩下的就被切断了。

更奇怪的是,当我像这样切换 var resultvar me = self 行时:

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
  }
}

那么该功能将仅适用于 IPv4 地址。 getnameinfo 将返回 4 (FAIL)。

这是在调试过程中,据我所知没有任何优化。在模拟器或真机上运行都没关系。

有人能解释一下为什么会这样吗?

【问题讨论】:

  • 正如我在回答您之前的问题stackoverflow.com/questions/44426911/… 时所说的:struct sockaddr 不能 持有 IPv6 地址。因此,如果您得到正确的结果,那纯属偶然。 – 你如何使用getifaddrs() 的扩展方法?你看过stackoverflow.com/a/25627545/1187415,哪个(现在)可以正常工作了吗?
  • 是的,我确实看过。行let addr = ptr.pointee.ifa_addr.pointee 产生sockaddr。如果您检查定义,您将看到以下内容:public var ifa_addr: UnsafeMutablePointer<sockaddr>!。基本上我所做的只是将之后的部分移动到sockaddr上的扩展名。
  • 同样getnameinfo的定义以public func getnameinfo(_: UnsafePointer<sockaddr>!,开头。而getnameinfo 可以同时解析 IPv4 和 IPv6 地址。
  • getnameinfo()指针 指向 sockaddr,而不是 sockaddr 本身。 ptr.pointee.ifa_addr 是从getifaddrs() 获得的指针,可以指向 IPv4 或 IPv6 地址。 – 你是对的:let addr = ptr.pointee.ifa_addr.pointee 只复制“common”部分,这是为了方便,只用于访问addr.sa_family。但是 pointer 被传递给getnameinfo() (实际上我最初是错的,如果您查看答案的最后编辑)
  • 好的,但是那样做和通过扩展做有什么区别呢?我将&me 传递给getnameinfo,这是一个指向sockaddr 的指针,对吗?

标签: swift sockaddr getnameinfo


【解决方案1】:

问题是getnameinfo 需要一个指针,它可以是sockaddr_insockaddr_in6。该函数的定义有点混乱,因为它需要一个sockaddr 指针。

因为我正在使用扩展程序来提取 IP 地址,所以正在制作内存内容的副本。这对 IPv4 来说不是问题,因为sockaddr_in 的大小与sockaddr 的大小相同。但是对于 IPv6,sockaddr_in6sockaddr 结构大,一些相关信息被截断。

我的命令顺序可能决定了内存中存储在sockaddr 地址之后的位置。有时它看起来像是一个正确的 IPv6 地址,但实际上是不正确的。

我已经通过将我的扩展移动到网络接口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
  }
}

感谢@MartinR 找到问题的原因!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-14
    • 1970-01-01
    • 2011-02-07
    • 1970-01-01
    • 1970-01-01
    • 2016-07-24
    • 2015-05-11
    • 1970-01-01
    相关资源
    最近更新 更多