As NSHost does not the trick I came up with two variants that were proposed to work the way I want. But only Variant 1 actually does the trick, and with even less code than Variant 2.
Both variants are thought to be run in a Swift playgound.
//: # How to retrieve a host name and associated aliases from an IP address
//: Rename the file extension to `playground` and run it directly in Xcode 7 with Swift 2.
import Cocoa
//: ## Using the C `struct`s in Swift
//: We can safely use a Swift `String` for storing the IP address in charachter format as Swift supports toll-fee bridging to C strings.
let ip = "17.172.224.47"
//: In order to use the `hostent` C structure as a reference value (pointer) we have to declare it as an `UnsafeMutablePointer` of the corresponding type.
//: We use `let` as the value itself will never change, but only the reference to the value. As such, the value (not the reference, or pointer for that matter) will be a constant.
let he: UnsafeMutablePointer<hostent>
//: We can declare another constant of type `hostent` which will be type-compatible with the memory location of `he`'s pointer to the C structure.
let he2: hostent
//: We create an empty fully initialized `in_addr` C structure by using the convenience initializer.
var addr: in_addr = in_addr()
/*: Now, the following declaration of a constant is a bit awkward. It uses Swift's C-bridging types to denote a `char **` which represents a pointer to a list of `char` pointers, which in turn represents an array of C strings.
Swift translates the C `char` to a `CChar` and the `*` pointer to a `UnsafeMutablePointer`, so the notation
`char **`
is mapped by reading right-to-left to
UnsafeMutablePointer<UnsafeMutablePointer<CChar>>
*/
let host_list: UnsafeMutablePointer<UnsafeMutablePointer<CChar>>
//: To store the host names in a plain Swift array of `String`s we use the following variable.
var host_aliases: [String] = []
//: ## Using the C Standard Library
//: The C Standard Library function `inet_pton` converts an IP address from a string representation to a network number. `AF_INET` tells the function to expect an IP4 address.
//: Caveat: Some functions require the `AF_…` constants to be wrapped around an `sa_family_t()` type cast.
inet_pton(AF_INET, ip, &addr)
//: Now we can all the legacy `gethostbyaddr` C function which returns a pointer to a `hostent` structure.
he = gethostbyaddr(&addr, UInt32(sizeof(in_addr)), AF_INET)
//: For convenience we use the `memory` member of the `he` structure to assign the value of the structure to the `he2` constant. This leaves us with shorter member access names and also shows the type conversion between Swift's C-bridging pointer types and `struct` types.
he2 = he.memory
//: The first resolved host name is present as an optional C string in `hostent`'s member `h_name`.
//: Just print it out, and then append it to the `host_aliases` array of `String`s.
print("First canonical host name: \(String.fromCString(he2.h_name)!)")
host_aliases.append(String.fromCString(he2.h_name)!)
//: Again for convenience, we assign the pointer to the list of C strings containing the alias names of the host to a constant name.
host_list = he2.h_aliases
//: ## Converting the list of C strings to a Swift `String` array
//: We use a zero-initialized Int for looping over the list of C strings.
var i = 0
/*: This is the beauty of Swift's toll-free C-bridging at a max: We can simply refer to the list of C strings using the standard Swift array accessor.
Combined with the conditional optional unwrapping directly in the `while` loop we get a very concise expression for
iterating over the NULL-terminated list, converting the C strings into `String`s and and packing the `String`s into the Swift array of `Strings`.
Notice the `++i` implicit incrementation: We skip the first alias name as it represents the RARP address pointer (`47.224.172.17.in-addr.arpa`) which is of no use to us.
*/
while let h = String.fromCString(host_list[++i]) {
host_aliases.append(h)
}
//: ## The result
//: Having now a Swift array of `Strings` filled up with the host names, working with it in either the Playground or in code is nice and clean.
print("Found \(host_aliases.count) hosts for IP address \(ip):")
for host in host_aliases {
print(host)
}
//: # 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.takeRetainedValue()
//: For the callback to work we have to create a client context.
var ctx = CFHostClientContext(
version: 0,
info: unsafeBitCast(self, UnsafeMutablePointer.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)!.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(), 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()
So, Variant 2 does clearly not work as intended as CFHost only resolves one host name without aliases. A lot of code that does not live up to the expected result.
"Enjoy and share", as eskimo would put it. 🙂
Cheers,
Mati