Skip to content

Instantly share code, notes, and snippets.

@MatiMax
Last active October 9, 2021 09:44
Show Gist options
  • Save MatiMax/083938182202c73f84c8156b4cf39013 to your computer and use it in GitHub Desktop.
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.
//: # 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
@michaelloistl
Copy link

@MatiMax thank you for putting that together. It works fine in Playground, however crashes in app on line 39.
Any idea why that would happen?

screen shot 2016-11-14 at 07 53 52

@MatiMax
Copy link
Author

MatiMax commented Dec 14, 2016

Hi, I only was playing with this code in Playground.

I would have to dive deeper into the code and build an app that incorporates the code to investigate on this which may take some time. My frist thought would be that the CFHostClientContext object has not been allocated correctly thus might be unset, nil or 0. You might want to try and debug your project with a watchdog on the ctx variable and have a look at its content. The info member should be non-nil.

You might also be aware of the fact, that using the unsafe bitcast to selfwhen creating the CFHostClientContext might cause issues in a multithreaded software design. Are you running this piece of code on the same thread where you create the reference to self? Line 39 would cause a crash if the method namesResolved is called on an empty or unknown obj. The code might also crash if the main thread terminates before CFHostScheduleWithRunLoop is actually called. I had this issue just before I inserted line 78 to make the Playground run infinitely.

@willpnw
Copy link

willpnw commented Dec 15, 2016

Hi,

First off all, HUGE thanks to migrating this to swift 3, you're a life saver!

However, I'm running into the exact same issue as michaelloistl. Did either of you ever solve this?
Any help would be much appreciated, Thanks!

@willpnw
Copy link

willpnw commented Dec 15, 2016

It appears to be a scoping issue. I had added the class to my project and placed in an event handler:

// THROWS ERROR
func handleSomeEvent() {
let dnsRes = DNSResolve()
dnsRes.resolve()
}

By moving the following the Instance to a property and calling the resolve function from an event handler I was able to not get this error

// WORKS
class MySpecialClass {
let dnsRes = DNSResolve()
func handleSomeEvent() {
dnsRes.resolve()
}
}

Thanks!

@MatiMax
Copy link
Author

MatiMax commented Dec 20, 2016

@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 the namesResolved()method. The Playground does the same thing by setting the execution to last forever using the needsIndefiniteExecution property.

CFHostSetClient uses a callback as explained in the comment of the Playground source code. When the object, on which the method namesResolved() should be called, isn't around after CFHostSetClient 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 in CFHostClientContext as info 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! 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment