Last active
October 9, 2021 09:44
-
-
Save MatiMax/083938182202c73f84c8156b4cf39013 to your computer and use it in GitHub Desktop.
This Playground shows how to use Core Foundation's CFHost in *Swift 3* to try to produce an array of Strings containing the host name and all its aliases for a given IP address. Unfortunately, CFHostGetNames() does not supply the aliases of a host, so we're left alone with the gethostbyaddr() C function.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: # How to retrieve a host name and associated aliases from an IP address using Core Fondation's `CFHost` in Swift 3 | |
import Foundation | |
import PlaygroundSupport | |
//: In order to get the callback working we use a simple class to implement the showcase. | |
class DNSResolve { | |
//: The IP address may be a Swift `String` thanks to the toll-free bridging to C strings. | |
let ip: String = "17.172.224.47" | |
//: We use an optional `CFHost` variable because CFHost neither comes with an initializer nor is conforming to the Nullable protocol. | |
var host: CFHost? | |
//: We use this array of `String`s to store the resolved host names. | |
var names: [String] = [] | |
func resolve() { | |
//: Let's set up the `sockaddr_in` C structure using the initializer. | |
var sin = sockaddr_in( | |
sin_len: UInt8(MemoryLayout<sockaddr_in>.size), | |
sin_family: sa_family_t(AF_INET), | |
sin_port: in_port_t(0), | |
sin_addr: in_addr(s_addr: inet_addr(ip)), | |
sin_zero: (0,0,0,0,0,0,0,0) | |
) | |
//: Now convert the structure into a `Data` object. Using the `Data` object is much less pain than fiddling around with the `CFData` variant using the `CFDataCreate` function which requires nasty un-Swift-ly pointer-type casting. | |
let data = Data(bytes: &sin, count: MemoryLayout<sockaddr_in>.size) | |
//: Create the `CFHostRef` with the `Data` object and store the retained value for later use. | |
let hostref = CFHostCreateWithAddress(kCFAllocatorDefault, data as CFData) | |
self.host = hostref.takeUnretainedValue() | |
//: For the callback to work we have to create a client context. | |
var ctx = CFHostClientContext( | |
version: 0, | |
info: unsafeBitCast(self, to: UnsafeMutableRawPointer.self), | |
retain: nil, | |
release: nil, | |
copyDescription: unsafeBitCast(kCFAllocatorDefault, to: CFAllocatorCopyDescriptionCallBack.self) | |
) | |
//: We can now set up the client for the callback using the `CFHostClientCallBack` signature for the closure. | |
CFHostSetClient(host!, { (host, infoType, error, info) in | |
let obj = unsafeBitCast(info, to: DNSResolve.self) | |
print("Resolving …") | |
obj.namesResolved(withError: (error?.pointee)!) | |
}, &ctx) | |
//: Now schedule the runloop for the host. | |
CFHostScheduleWithRunLoop(host!, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue); | |
//: Create a `CFStreamError` object for use with the info resolution using `CFHostStartInfoResolution`. | |
var error = CFStreamError() | |
//: Start the info resolution. | |
let started: Bool = CFHostStartInfoResolution(host!, .names, &error) | |
print("Name resolution started: \(started)") | |
} | |
//: This function is attachted as `CFHostClientCallBack` in `CFHostSetClient` which should get called during the info resolution. | |
func namesResolved(withError error: CFStreamError) { | |
print("namesResolved: Resolving …") | |
//: Create a boolean pointer `DarwinBoolean` for use with the function `CFHostGetNames`. | |
var resolved: DarwinBoolean = DarwinBoolean(false) | |
//: Now get the results of the info resolution. | |
let cfNames: CFArray = CFHostGetNames(host!, &resolved)!.takeRetainedValue() | |
print("namesResolved: Names resolved: \(resolved) with error \(error.error)") | |
//: We can use cascading casts from `[AnyObject]` to a force-unwrapped `[String]`. Thank you, Swift. | |
self.names = cfNames as [AnyObject] as! [String] | |
//: **Oh dear—we see only one host name here and no aliases. Stuck again … :-(** | |
print("CFArray reports \(CFArrayGetCount(cfNames)) elements, [String] reports \(self.names.count) elements.") | |
self.listNames() | |
//: After the info resolution clean up either way. | |
CFHostSetClient(host!, nil, nil); | |
CFHostCancelInfoResolution(host!, .names) | |
CFHostUnscheduleFromRunLoop(host!, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode as! CFString) | |
} | |
func listNames() { | |
print(self.names) | |
} | |
} | |
//: OK, let's create an instance of our `DNSResolve` class and run the `resolve()` method. | |
let dnsRes = DNSResolve() | |
dnsRes.resolve() | |
//: In order to see the callback working we have to set Playground's execution to take on forever. | |
PlaygroundPage.current.needsIndefiniteExecution = true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@wilpatterson I think you got it absolutely right. It's the kind of problem which I was to explain in my latest comment with regards to the threading issue. Your working solution creates an instance of the
DNSResolve
class which survives the context of the call to thenamesResolved()
method. The Playground does the same thing by setting the execution to last forever using theneedsIndefiniteExecution
property.CFHostSetClient
uses a callback as explained in the comment of the Playground source code. When the object, on which the methodnamesResolved()
should be called, isn't around afterCFHostSetClient
actually wants to execute the callback (the closure) then the program definitvely will crash. Your program design has to ensure that the instance of the object created inCFHostClientContext
asinfo
property will be around even after the context of this call ends. Your working code snippet actually ensures this.I'm very glad that Apple established the Server APIs Work Group and that the fine folks of Perfect, Kitura, Zewo, Vapor and many others put together their efforts to build more Swift-ly APIs for networking. I've also been in talks with an Apple engineer for a complete re-write, or better re-imagination, of the NSHost and DNS handling APIs. Those APIs received the least amount of love in the past years, and also the handling of the Core Foundation APIs for networking are very clumsy to handle in Swift. We need beautifully designed APIs, and the Server APIs Work Group is very well under way to take care of those requirements. Cheers to them! 😉