-
-
Save MatiMax/1a2890337e76eb8d7700 to your computer and use it in GitHub Desktop.
//: # How to retrieve a host name and associated aliases from an IP address using Core Fondation's `CFHost` | |
import Cocoa | |
import XCPlayground | |
//: 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(sizeof(sockaddr_in)), | |
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 `CFData` object. | |
let data = withUnsafePointer(&sin) { ptr in | |
CFDataCreate(kCFAllocatorDefault, UnsafePointer(ptr), sizeof(sockaddr_in)) | |
} | |
//: Create the `CFHostRef` with the `CFData` object and store the retained value for later use. | |
let hostref = CFHostCreateWithAddress(kCFAllocatorDefault, data) | |
self.host = hostref.takeUnretainedValue() | |
//: For the callback to work we have to create a client context. | |
var ctx = CFHostClientContext( | |
version: 0, | |
info: unsafeBitCast(self, UnsafeMutablePointer<Void>.self), | |
retain: nil, | |
release: nil, | |
copyDescription: unsafeBitCast(0, 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, DNSResolve.self) | |
print("Resolving …") | |
obj.namesResolved(withError: error.memory) | |
}, &ctx) | |
//: Now schedule the runloop for the host. | |
CFHostScheduleWithRunLoop(host!, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); | |
//: 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: CFArrayRef = CFHostGetNames(host!, &resolved)!.takeUnretainedValue() | |
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(), kCFRunLoopDefaultMode) | |
} | |
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. | |
XCPSetExecutionShouldContinueIndefinitely() |
In line 57, let cfNames: CFArrayRef = CFHostGetNames(host!, &resolved)!.takeRetainedValue()
, I think takeUnretainedValue()
is more suitable, or else, program crashes because of releasing already released array, which does not happen every time, but it does happen at some time.
A swift 3 upgrade would be nice
Hi all,
thanks for the comments and the feedback. Lately I was focusing more on the Linux version of Swift but I certainly will take the time to port the Gist to Swift 3. Please stay tuned.
Cheers, Mati
Hello all,
I finally had time to update the playground to Swift 3. As there was a lot of changes going on, especially with C-pointer type casting and all, I had to fight a little to get it working. So, please see the comments in the playground which point out the finesses.
Happy Swift-ing,
Mati
@acalism: Good point. In the playground it tends to work nicely for me but I definitively see the logic behind using an unretained value. I adapted the sources accordingly.
You should put the IP address into initializer so we could instantiate the class with an IP, also it would be nice to have a closure as a completion handler for your resolve method, in my case I need to wait until I get the names before doing anything else
good works
thanks