Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CFHostSample: Version 3.0, 2017-03-14
Rewritten in Swift. The main program is a simple command line tool that accepts parameters, creates query objects, runs them, and prints the results. The various query classes are each a simple wrapper around CFHost; each has extensive comments explaining how to use the class and how the class works internally. Signed-off-by: Liu Lantao <liulantao@gmail.com>
- Loading branch information
Showing
11 changed files
with
1,925 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/* | ||
Copyright (C) 2017 Apple Inc. All Rights Reserved. | ||
See LICENSE.txt for this sample’s licensing information | ||
Abstract: | ||
Resolves a DNS name to a list of IP addresses. | ||
*/ | ||
|
||
@import Foundation; | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
@protocol QHostAddressQueryDelegate; | ||
|
||
/// This class uses CFHost to query a DNS name for its addresses. To do this: | ||
/// | ||
/// 1. Create the `QHostAddressQuery` object with the name in question. | ||
/// | ||
/// 2. Set a delegate. | ||
/// | ||
/// 3. Call `start()`. | ||
/// | ||
/// 4. Wait for `-hostAddressQuery:didCompleteWithAddresses:` or `-hostAddressQuery:didCompleteWithError:` | ||
/// to be called. | ||
/// | ||
/// CFHost, and hence this class, is run loop based. The class remembers the run loop on which you | ||
/// call `start()` and delivers the delegate callbacks on that run loop. | ||
|
||
@interface QHostAddressQuery : NSObject | ||
|
||
/// Creates an instance to query the specified DNS name for its addresses. | ||
/// | ||
/// - Parameter name: The DNS name to query. | ||
|
||
- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER; | ||
|
||
- (instancetype)init NS_UNAVAILABLE; | ||
|
||
/// The DNS name to query. | ||
|
||
@property (nonatomic, copy, readonly) NSString * name; | ||
|
||
/// You must set this to learn about the results of your query. | ||
|
||
@property (nonatomic, weak, readwrite) id<QHostAddressQueryDelegate> delegate; | ||
|
||
/// Starts the query process. | ||
/// | ||
/// The query remembers the thread that called this method and calls any delegate | ||
/// callbacks on that thread. | ||
/// | ||
/// - Important: For the query to make progress, this thread must run its run loop in | ||
/// the default run loop mode. | ||
/// | ||
/// - Warning: It is an error to start a query that's running. | ||
|
||
- (void)start; | ||
|
||
/// Cancels a running query. | ||
/// | ||
/// If you successfully cancel a query, no delegate callback for that query will be | ||
/// called. | ||
/// | ||
/// If the query is running, you must call this from the thread that called `start()`. | ||
/// | ||
/// - Note: It is acceptable to call this on a query that's not running; it does nothing | ||
// in that case. | ||
|
||
- (void)cancel; | ||
|
||
@end | ||
|
||
/// The delegate protocol for the HostAddressQuery class. | ||
|
||
@protocol QHostAddressQueryDelegate <NSObject> | ||
|
||
@required | ||
|
||
/// Called when the query completes successfully. | ||
/// | ||
/// This is called on the same thread that called `start()`. | ||
/// | ||
/// - Parameters: | ||
/// - addresses: The addresses for the DNS name. This has some important properties: | ||
/// - It will not be empty. | ||
/// - Each element is an `NSData` value that contains some flavour of `sockaddr` | ||
/// - It can contain any combination of IPv4 and IPv6 addresses | ||
/// - The addresses are sorted, with the most preferred first | ||
/// - query: The query that completed. | ||
|
||
- (void)hostAddressQuery:(QHostAddressQuery *)query didCompleteWithAddresses:(NSArray<NSData *> *)addresses; | ||
|
||
/// Called when the query completes with an error. | ||
/// | ||
/// This is called on the same thread that called `start()`. | ||
/// | ||
/// - Parameters: | ||
/// - error: An error describing the failure. | ||
/// - query: The query that completed. | ||
/// | ||
/// - Important: In most cases the error will be in domain `kCFErrorDomainCFNetwork` | ||
/// with a code of `kCFHostErrorUnknown` (aka `CFNetworkErrors.cfHostErrorUnknown`), | ||
/// and the user info dictionary will contain an element with the `kCFGetAddrInfoFailureKey` | ||
/// key whose value is an NSNumber containing an `EAI_XXX` value (from `<netdb.h>`). | ||
|
||
- (void)hostAddressQuery:(QHostAddressQuery *)query didCompleteWithError:(NSError *)error; | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
/* | ||
Copyright (C) 2017 Apple Inc. All Rights Reserved. | ||
See LICENSE.txt for this sample’s licensing information | ||
Abstract: | ||
Resolves a DNS name to a list of IP addresses. | ||
*/ | ||
|
||
#import "QHostAddressQuery.h" | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
@interface QHostAddressQuery () | ||
|
||
/// The underlying CFHost object that does the resolution. | ||
|
||
@property (nonatomic, strong, readwrite, nullable) CFHostRef host __attribute__ (( NSObject )); | ||
|
||
/// The run loop on which the CFHost object is scheduled; this is set in `start()` | ||
/// and cleared when the query stops (either via `cancel()` or by completing). | ||
|
||
@property (nonatomic, strong, readwrite, nullable) NSRunLoop * targetRunLoop; | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END | ||
|
||
@implementation QHostAddressQuery | ||
|
||
- (instancetype)initWithName:(NSString *)name { | ||
NSParameterAssert(name != nil); | ||
self = [super init]; | ||
if (self != nil) { | ||
self->_name = [name copy]; | ||
self->_host = CFHostCreateWithName(nil, (__bridge CFStringRef) self->_name); | ||
} | ||
return self; | ||
} | ||
|
||
- (instancetype)init { | ||
abort(); | ||
} | ||
|
||
- (void)start { | ||
NSParameterAssert(self.targetRunLoop == nil); | ||
self.targetRunLoop = NSRunLoop.currentRunLoop; | ||
|
||
CFHostClientContext context = { | ||
.info = (void *) CFBridgingRetain(self) | ||
}; | ||
BOOL success = CFHostSetClient(self.host, HostClientCallBack, &context) != false; | ||
assert(success); | ||
CFHostScheduleWithRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); | ||
|
||
CFStreamError streamError; | ||
success = CFHostStartInfoResolution(self.host, kCFHostAddresses, &streamError) != false; | ||
if ( ! success ) { | ||
[self stopWithStreamError:&streamError notify:NO]; | ||
} | ||
} | ||
|
||
/// Our CFHost callback function for our host; this just extracts the object from the `info` | ||
/// pointer and calls methods on it. | ||
|
||
static void HostClientCallBack(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError * __nullable error, void * __nullable info) { | ||
#pragma unused(theHost) | ||
#pragma unused(typeInfo) | ||
|
||
QHostAddressQuery * obj = (__bridge QHostAddressQuery *) info; | ||
assert([obj isKindOfClass:[QHostAddressQuery class]]); | ||
|
||
if ( (error == NULL) || ( (error->domain == 0) && (error->error == 0) ) ) { | ||
[obj stopWithStreamError:NULL notify:YES]; | ||
} else { | ||
[obj stopWithStreamError:error notify:YES]; | ||
} | ||
} | ||
|
||
/// Stops the query with the supplied error, notifying the delegate if `notify` is true. | ||
|
||
- (void)stopWithStreamError:(nullable const CFStreamError *)streamError notify:(BOOL)notify { | ||
NSError * error = nil; | ||
|
||
if (streamError != NULL) { | ||
// Convert a CFStreamError to a NSError. This is less than ideal. I only handle a | ||
// limited number of error domains, and I can't use a switch statement because | ||
// some of the `kCFStreamErrorDomainXxx` values are not a constant. Wouldn't it be | ||
// nice if there was a public API to do this mapping <rdar://problem/5845848> | ||
// or a CFHost API that used CFError <rdar://problem/6016542>. | ||
if (streamError->domain == kCFStreamErrorDomainPOSIX) { | ||
error = [NSError errorWithDomain:NSPOSIXErrorDomain code:streamError->error userInfo:nil]; | ||
} else if (streamError->domain == kCFStreamErrorDomainMacOSStatus) { | ||
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:streamError->error userInfo:nil]; | ||
} else if (streamError->domain == kCFStreamErrorDomainNetServices) { | ||
error = [NSError errorWithDomain:(__bridge NSString *) kCFErrorDomainCFNetwork code:streamError->error userInfo:nil]; | ||
} else if (streamError->domain == kCFStreamErrorDomainNetDB) { | ||
error = [NSError errorWithDomain:(__bridge NSString *) kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:@{ | ||
(__bridge NSString *) kCFGetAddrInfoFailureKey: @(streamError->error) | ||
}]; | ||
} else { | ||
// If it's something we don't understand, we just assume it comes from | ||
// CFNetwork. | ||
error = [NSError errorWithDomain:(__bridge NSString *) kCFErrorDomainCFNetwork code:streamError->error userInfo:nil]; | ||
} | ||
} | ||
|
||
[self stopWithError:error notify:notify]; | ||
} | ||
|
||
/// Stops the query with the supplied error, notifying the delegate if `notify` is true. | ||
|
||
- (void)stopWithError:(nullable NSError *)error notify:(BOOL)notify { | ||
NSParameterAssert(NSRunLoop.currentRunLoop == self.targetRunLoop); | ||
self.targetRunLoop = nil; | ||
|
||
CFHostSetClient(self.host, nil, nil); | ||
CFHostUnscheduleFromRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); | ||
CFHostCancelInfoResolution(self.host, kCFHostAddresses); | ||
CFRelease( (__bridge CFTypeRef) self ); | ||
|
||
id<QHostAddressQueryDelegate> strongDelegate = self.delegate; | ||
if (notify && (strongDelegate != nil) ) { | ||
if (error != nil) { | ||
[strongDelegate hostAddressQuery:self didCompleteWithError:error]; | ||
} else { | ||
NSArray<NSData *> * addresses = (__bridge NSArray<NSData *> *) CFHostGetAddressing(self.host, NULL); | ||
[strongDelegate hostAddressQuery:self didCompleteWithAddresses:addresses]; | ||
} | ||
} | ||
} | ||
|
||
- (void)cancel { | ||
if (self.targetRunLoop != nil) { | ||
[self stopWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil] notify:NO]; | ||
} | ||
} | ||
|
||
@end |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/* | ||
Copyright (C) 2017 Apple Inc. All Rights Reserved. | ||
See LICENSE.txt for this sample’s licensing information | ||
Abstract: | ||
Resolves an IP address to a list of DNS names. | ||
*/ | ||
|
||
@import Foundation; | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
@protocol QHostNameQueryDelegate; | ||
|
||
/// This class uses CFHost to query an IP address for its DNS names. To do this: | ||
/// | ||
/// 1. Create the `QHostNameQuery` object with the name in question. | ||
/// | ||
/// 2. Set a delegate. | ||
/// | ||
/// 3. Call `start()`. | ||
/// | ||
/// 4. Wait for `-hostNameQuery:didCompleteWithNames:` or `-hostNameQuery:didCompleteWithError:` | ||
/// to be called. | ||
/// | ||
/// CFHost, and hence this class, is run loop based. The class remembers the run loop on which you | ||
/// call `start()` and delivers the delegate callbacks on that run loop. | ||
/// | ||
/// - Important: Reverse DNS queries are notoriously unreliable. Specifically, you must not | ||
/// assume that: | ||
/// | ||
/// - Every IP address has a valid reverse DNS name | ||
/// - The reverse DNS name is unique | ||
/// - There's any correlation between the forward and reverse DNS mappings | ||
/// | ||
/// Unless you have domain specific knowledge (for example, you're working in an enterprise | ||
/// environment where you know how the DNS is set up), reverse DNS queries are generally not | ||
/// useful for anything other than logging. | ||
|
||
@interface QHostNameQuery : NSObject | ||
|
||
/// Creates an instance to query the specified IP address for its DNS name. | ||
/// | ||
/// - Parameter address: The IP address to query, as a `NSData` value containing some flavour of `sockaddr`. | ||
|
||
- (instancetype)initWithAddress:(NSData *)address NS_DESIGNATED_INITIALIZER; | ||
|
||
- (instancetype)init NS_UNAVAILABLE; | ||
|
||
/// The IP address to query, as a `NSData` value containing some flavour of `sockaddr`. | ||
|
||
@property (nonatomic, copy, readonly) NSData * address; | ||
|
||
/// You must set this to learn about the results of your query. | ||
|
||
@property (nonatomic, weak, readwrite) id<QHostNameQueryDelegate> delegate; | ||
|
||
/// Starts the query process. | ||
/// | ||
/// The query remembers the thread that called this method and calls any delegate | ||
/// callbacks on that thread. | ||
/// | ||
/// - Important: For the query to make progress, this thread must run its run loop in | ||
/// the default run loop mode. | ||
/// | ||
/// - Warning: It is an error to start a query that's running. | ||
|
||
- (void)start; | ||
|
||
/// Cancels a running query. | ||
/// | ||
/// If you successfully cancel a query, no delegate callback for that query will be | ||
/// called. | ||
/// | ||
/// If the query is running, you must call this from the thread that called `start()`. | ||
/// | ||
/// - Note: It is acceptable to call this on a query that's not running; it does nothing | ||
// in that case. | ||
|
||
- (void)cancel; | ||
|
||
@end | ||
|
||
/// The delegate protocol for the HostNameQuery class. | ||
|
||
@protocol QHostNameQueryDelegate <NSObject> | ||
|
||
@required | ||
|
||
/// Called when the query completes successfully. | ||
/// | ||
/// This is called on the same thread that called `start()`. | ||
/// | ||
/// - Parameters: | ||
/// - names: The DNS names for the IP address. | ||
/// - query: The query that completed. | ||
|
||
- (void)hostNameQuery:(QHostNameQuery *)query didCompleteWithNames:(NSArray<NSString *> *)names; | ||
|
||
/// Called when the query completes with an error. | ||
/// | ||
/// This is called on the same thread that called `start()`. | ||
/// | ||
/// - Parameters: | ||
/// - error: An error describing the failure. | ||
/// - query: The query that completed. | ||
/// | ||
/// - Important: In most cases the error will be in domain `kCFErrorDomainCFNetwork` | ||
/// with a code of `kCFHostErrorUnknown` (aka `CFNetworkErrors.cfHostErrorUnknown`), | ||
/// and the user info dictionary will contain an element with the `kCFGetAddrInfoFailureKey` | ||
/// key whose value is an NSNumber containing an `EAI_XXX` value (from `<netdb.h>`). | ||
|
||
- (void)hostNameQuery:(QHostNameQuery *)query didCompleteWithError:(NSError *)error; | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
Oops, something went wrong.