Skip to content

Instantly share code, notes, and snippets.

@MatiMax
Last active November 14, 2016 05:48
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save MatiMax/1a2890337e76eb8d7700 to your computer and use it in GitHub Desktop.
Save MatiMax/1a2890337e76eb8d7700 to your computer and use it in GitHub Desktop.
This Swift Playground shows how to use Core Foundation's CFHost in Swift 2 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`
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()
@agiguere
Copy link

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

@acalism
Copy link

acalism commented Jun 2, 2016

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.

@agiguere
Copy link

agiguere commented Sep 6, 2016

A swift 3 upgrade would be nice

@MatiMax
Copy link
Author

MatiMax commented Sep 13, 2016

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

@MatiMax
Copy link
Author

MatiMax commented Sep 28, 2016

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

@MatiMax
Copy link
Author

MatiMax commented Sep 28, 2016

@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.

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