将NSData转换为Swift中的sockaddr结构体

11

我正在尝试使用 Swift 进行简单的 DNS 查询。到目前为止,这是我拥有的代码:

let hostRef = CFHostCreateWithName(kCFAllocatorDefault, "google.com").takeRetainedValue()
var resolved = CFHostStartInfoResolution(hostRef, CFHostInfoType.Addresses, nil)
let addresses = CFHostGetAddressing(hostRef, &resolved).takeRetainedValue() as NSArray

此时,“addresses”NSArray中的每个元素都是一个封装了sockaddr结构体的CFDataRef对象。

由于CFDataRef可以与NSData进行无桥接转换,因此我可以像这样循环遍历它们:

for address: AnyObject in addresses {
  println(address)  // address is of type NSData.
}

到目前为止(我认为)一切都很顺利。当我在单元测试中运行它时,它输出看起来有效的数据。但这就是我的困境所在。无论如何,我都想不出如何将NSData对象中的字节转换为sockaddr结构体。

我该如何将类型为COpaquePointer?的address.bytes转换为C结构体?任何帮助都将不胜感激。我一直在试图想出解决方法,但现在已经很崩溃了。

1个回答

16

如果想使用更简单的方法,可以使用getnameinfo,请参见Martin在此处的答案:如何从Swift中的DNS查询中获取真实IP地址?

更新至Swift 5 / IPv6:

CFHostGetAddressing返回的对象可以作为Data桥接到Swift,并通过使用withUnsafeBytesassumingMemoryBound(to:)将其转换为in_addr/in6_addr

下面是一个完整的示例,它使用inet_ntop将IPv4 / IPv6地址转换为字符串:

import CFNetwork
import Foundation

protocol NetworkAddress {
    static var family: Int32 { get }
    static var maxStringLength: Int32 { get }
}
extension in_addr: NetworkAddress {
    static let family = AF_INET
    static let maxStringLength = INET_ADDRSTRLEN
}
extension in6_addr: NetworkAddress {
    static let family = AF_INET6
    static let maxStringLength = INET6_ADDRSTRLEN
}

extension String {
    init<A: NetworkAddress>(address: A) {
        // allocate a temporary buffer large enough to hold the string
        var buf = ContiguousArray<Int8>(repeating: 0, count: Int(A.maxStringLength))
        self = withUnsafePointer(to: address) { rawAddr in
            buf.withUnsafeMutableBufferPointer {
                String(cString: inet_ntop(A.family, rawAddr, $0.baseAddress, UInt32($0.count)))
            }
        }
    }
}

func addressToString(data: Data) -> String? {
    return data.withUnsafeBytes {
        let family = $0.baseAddress!.assumingMemoryBound(to: sockaddr_storage.self).pointee.ss_family
        // family determines which address type to cast to (IPv4 vs IPv6)
        if family == numericCast(AF_INET) {
            return String(address: $0.baseAddress!.assumingMemoryBound(to: sockaddr_in.self).pointee.sin_addr)
        } else if family == numericCast(AF_INET6) {
            return String(address: $0.baseAddress!.assumingMemoryBound(to: sockaddr_in6.self).pointee.sin6_addr)
        }
        return nil
    }
}

let host = CFHostCreateWithName(kCFAllocatorDefault, "google.com" as CFString).takeRetainedValue()
var resolved = DarwinBoolean(CFHostStartInfoResolution(host, .addresses, nil))
let addresses = CFHostGetAddressing(host, &resolved)?.takeUnretainedValue() as! [Data]?

print(addresses?.compactMap(addressToString))

你可以使用NSData方法getBytes(_, length:),并使用前缀&运算符将sockaddr结构传递给inout参数:

var data: NSData ...
var address: sockaddr ...

data.getBytes(&address, length: MemoryLayout<sockaddr>.size)

已更新至Swift 3:

let host = CFHostCreateWithName(kCFAllocatorDefault, "google.com" as CFString).takeRetainedValue()
var resolved = DarwinBoolean(CFHostStartInfoResolution(host, .addresses, nil))
let addresses = CFHostGetAddressing(host, &resolved)?.takeUnretainedValue() as! [NSData]?

if let data = addresses?.first {
    var storage = sockaddr_storage()
    data.getBytes(&storage, length: MemoryLayout<sockaddr_storage>.size)

    if Int32(storage.ss_family) == AF_INET {
        let addr4 = withUnsafePointer(to: &storage) {
            $0.withMemoryRebound(to: sockaddr_in.self, capacity: 1) {
                $0.pointee
            }
        }

        // prints 74.125.239.132
        print(String(cString: inet_ntoa(addr4.sin_addr), encoding: .ascii))
    }
}

更新于2015年6月3日: 现在由于C结构体可以轻松地进行零初始化,这变得更加简单了:

let host = CFHostCreateWithName(kCFAllocatorDefault, "google.com").takeRetainedValue()
var resolved = CFHostStartInfoResolution(host, .Addresses, nil)
let addresses = CFHostGetAddressing(host, &resolved)?.takeUnretainedValue() as! [NSData]?

if let data = addresses?.first {
    var storage = sockaddr_storage()
    data.getBytes(&storage, length: sizeof(sockaddr_storage))

    if Int32(storage.ss_family) == AF_INET {
        let addr4 = withUnsafePointer(&storage) { UnsafePointer<sockaddr_in>($0).memory }

        // prints 74.125.239.132
        println(String(CString: inet_ntoa(addr4.sin_addr), encoding: NSASCIIStringEncoding))
    }
}

抱歉,这需要先初始化 sockaddr。为了避免这种情况,你可以像这样做:
func makeWithUnsafePointer<T>(body: UnsafePointer<T> -> ()) -> T {
    let ptr = UnsafePointer<T>.alloc(sizeof(T))
    body(ptr)
    return ptr.move()
}

let addr: sockaddr = makeWithUnsafePointer {
    data.getBytes($0 as UnsafePointer<sockaddr>, length: sizeof(sockaddr))
}

或者是这个:
func makeWithUninitialized<T>(body: inout T -> ()) -> T {
    let ptr = UnsafePointer<T>.alloc(sizeof(T))
    body(&ptr.memory)
    return ptr.move()
}

let addr = makeWithUninitialized { (inout addr: sockaddr) in
    data.getBytes(&addr, length: sizeof(sockaddr))
}

更多讨论请参阅Swift:将未初始化的C结构传递给导入的C函数


这甚至可以简化为:data.getBytes(&addr, length:sizeof(sockaddr))。 - kgreenek
2
说实话,你可能不想使用sockaddr - 而是使用你得到的实际sockaddr结构。比如sockaddr_in或sockaddr_un。否则你很可能会遇到缓冲区溢出的问题。也许你会发现这个扩展很有用:https://github.com/AlwaysRightInstitute/SwiftSockets/blob/master/ARISockets/SocketAddress.swift - hnh
@MartinR 看起来你是对的。如果你能想出一个解决方案,请随意编辑--否则我稍后会查看并更新我的答案。也许body(ptr)就足够了。 - jtbandes
@jtbandes 謝謝 :) - itsji10dra
4
我得到了以下的错误信息: “&”与非inout类型“sockaddr_storage”一起使用。 - david72
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接