Skip to content

Commit

Permalink
CFHostSample: Version 3.0, 2017-03-14
Browse files Browse the repository at this point in the history
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
Lax committed Nov 20, 2017
1 parent c0557ce commit b30523d
Show file tree
Hide file tree
Showing 11 changed files with 1,925 additions and 0 deletions.
422 changes: 422 additions & 0 deletions CFHostSample/CFHostSample.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

110 changes: 110 additions & 0 deletions CFHostSample/CFHostSampleObjC/QHostAddressQuery.h
@@ -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
138 changes: 138 additions & 0 deletions CFHostSample/CFHostSampleObjC/QHostAddressQuery.m
@@ -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
117 changes: 117 additions & 0 deletions CFHostSample/CFHostSampleObjC/QHostNameQuery.h
@@ -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

0 comments on commit b30523d

Please sign in to comment.