Swift 获取地址信息

Swift getaddrinfo

POSIX getaddrinfo 分配稍后必须使用 freeaddrinfo 释放的内存。 参见 http://manpages.ubuntu.com/manpages/xenial/en/man3/getaddrinfo.3.html

为了简化 API,我创建了这个函数:

import Foundation

enum SystemError: Swift.Error {
    case getaddrinfo(Int32, Int32?)
}

public func getaddrinfo(node: String?, service: String?, hints: addrinfo?) throws -> [addrinfo] {
    var err: Int32
    var res: UnsafeMutablePointer<addrinfo>?
    if var hints = hints {
        err = getaddrinfo(node, service, &hints, &res)
    } else {
        err = getaddrinfo(node, service, nil, &res)
    }
    if err == EAI_SYSTEM {
        throw SystemError.getaddrinfo(err, errno)
    }
    if err != 0 {
        throw SystemError.getaddrinfo(err, nil)
    }
    defer {
        freeaddrinfo(res)
    }
    var result = [addrinfo]()
    var ai = res?.pointee
    while ai != nil {
        result.append(ai!)
        ai = ai!.ai_next?.pointee
    }
    return result
}

不过我觉得函数不正确。

getaddrinfo 交互的正确方法是什么?

getaddrinfo分配的内存(例如malloc)不会分配给任何其他人 同一 运行 进程中的动态内存分配函数 直到 freeaddrinfo 释放(例如 free)。 因此 Swift 运行时不会践踏该内存(如果我们 假设它没有编程错误,例如错误的指针计算)。

另外struct addrinfo是值类型,所以

result.append(ai!)

会将指向的结构的副本附加到数组。

但是还是有问题 struct addrinfo的部分成员 是指针

public var ai_canonname: UnsafeMutablePointer<Int8>! /* canonical name for hostname */
public var ai_addr: UnsafeMutablePointer<sockaddr>! /* binary address */

可能指向 getaddrinfo 分配的内存,因此 在 freeaddrinfo 之后无效,并在您的之后取消引用它们 函数 returns 导致未定义的行为。

因此您必须将 freeaddrinfo 推迟到 不再需要地址列表,或复制信息。 这有点麻烦,因为 ai_addr 可能指向一个 具有不同长度的 IPv4 或 IPv6 套接字地址结构。

以下代码演示了如何复制地址列表 到 sockaddr_storage 结构的数组(足够大 持有任何IP地址)。此代码尚未经过彻底测试, 请谨慎使用。

public func getaddrinfo(node: String, service: String, hints: addrinfo?) throws -> [sockaddr_storage] {
    var err: Int32
    var res: UnsafeMutablePointer<addrinfo>?
    if var hints = hints {
        err = getaddrinfo(node, service, &hints, &res)
    } else {
        err = getaddrinfo(node, service, nil, &res)
    }
    if err == EAI_SYSTEM {
        throw SystemError.getaddrinfo(err, errno)
    }
    if err != 0 {
        throw SystemError.getaddrinfo(err, nil)
    }
    defer {
        freeaddrinfo(res)
    }
    guard let firstAddr = res else {
        return []
    }

    var result = [sockaddr_storage]()
    for addr in sequence(first: firstAddr, next: { [=12=].pointee.ai_next }) {
        var sockAddr = sockaddr_storage()
        memcpy(&sockAddr, addr.pointee.ai_addr, Int(addr.pointee.ai_addrlen))
        result.append(sockAddr)
    }
    return result
}

备注:

  • 如果您只对单个地址族感兴趣,那么 您可以将 sockaddr_storage 替换为 sockaddr_insockaddr_in6.

  • 我已将 nodeservice 参数设置为非可选参数。 原因是Swift目前传多了有Bug 比 C 函数的一个可选字符串,请参阅 Why does Swift return an unexpected pointer when converting an optional String into an UnsafePointer?.

  • 这里使用
  • sequence()来遍历链表,而不是 while 循环。