SRVResolver.m
/* |
File: SRVResolver.m |
Contains: Uses <dns_sd.h> APIs to resolve SRV records. |
Written by: DTS |
Copyright: Copyright (c) 2010-2012 Apple Inc. All Rights Reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. |
("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of |
these terms. If you do not agree with these terms, please do |
not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following |
terms, and subject to these terms, Apple grants you a personal, |
non-exclusive license, under Apple's copyrights in this |
original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or |
without modifications, in source and/or binary forms; provided |
that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the |
following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks |
or logos of Apple Inc. may be used to endorse or promote |
products derived from the Apple Software without specific prior |
written permission from Apple. Except as expressly stated in |
this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or |
by other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. |
APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING |
WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, |
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING |
THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, |
INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY |
OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY |
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR |
OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF |
SUCH DAMAGE. |
*/ |
#import "SRVResolver.h" |
#include <dns_util.h> |
@interface SRVResolver () |
// Redeclare some external properties as read/write |
@property (nonatomic, assign, readwrite, getter=isFinished) BOOL finished; |
@property (nonatomic, copy, readwrite) NSError * error; |
// Private properties |
@property (nonatomic, strong, readonly ) NSMutableArray * resultsMutable; // backing for the public results property |
// Forward declarations |
- (void)startInternal; |
@end |
@implementation SRVResolver |
{ |
DNSServiceRef _sdRef; |
CFSocketRef _sdRefSocket; |
} |
@synthesize srvName = _srvName; |
@synthesize delegate = _delegate; |
@synthesize finished = _finished; |
@synthesize error = _error; |
@synthesize resultsMutable = _resultsMutable; |
- (id)initWithSRVName:(NSString *)srvName |
{ |
assert(srvName != nil); |
self = [super init]; |
if (self != nil) { |
self->_srvName = [srvName copy]; |
assert(self->_srvName != nil); |
self->_resultsMutable = [[NSMutableArray alloc] init]; |
assert(self->_resultsMutable != nil); |
} |
return self; |
} |
- (void)dealloc |
{ |
[self stop]; |
} |
- (void)start |
{ |
if (self->_sdRef == NULL) { |
self.error = nil; // starting up again, so forget any previous error |
self.finished = NO; |
[self startInternal]; |
} |
} |
- (void)stop |
{ |
if (self->_sdRefSocket != NULL) { |
CFSocketInvalidate(self->_sdRefSocket); |
CFRelease(self->_sdRefSocket); |
self->_sdRefSocket = NULL; |
} |
if (self->_sdRef != NULL) { |
DNSServiceRefDeallocate(self->_sdRef); |
self->_sdRef = NULL; |
} |
self.finished = YES; |
} |
- (void)stopWithError:(NSError *)error |
{ |
// error may be nil |
self.error = error; |
[self stop]; |
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(srvResolver:didStopWithError:)] ) { |
[self.delegate srvResolver:self didStopWithError:error]; |
} |
} |
- (void)stopWithDNSServiceError:(DNSServiceErrorType)errorCode |
{ |
NSError * error; |
error = nil; |
if (errorCode != kDNSServiceErr_NoError) { |
error = [NSError errorWithDomain:kSRVResolverErrorDomain code:errorCode userInfo:nil]; |
} |
[self stopWithError:error]; |
} |
- (NSArray *)results |
{ |
// Return a snapshot of our internal mutable array. |
return [self.resultsMutable copy]; |
} |
- (void)processRecord:(const void *)rdata length:(NSUInteger)rdlen |
{ |
NSMutableData * rrData; |
dns_resource_record_t * rr; |
uint8_t u8; |
uint16_t u16; |
uint32_t u32; |
assert(rdata != NULL); |
assert(rdlen < 65536); // rdlen comes from a uint16_t, so can't exceed this. |
// This also constrains [rrData length] to well less than a uint32_t. |
// Rather than write a whole bunch of icky parsing code, I just synthesise |
// a resource record and use <dns_util.h>. |
rrData = [NSMutableData data]; |
assert(rrData != nil); |
u8 = 0; |
[rrData appendBytes:&u8 length:sizeof(u8)]; |
u16 = htons(kDNSServiceType_SRV); |
[rrData appendBytes:&u16 length:sizeof(u16)]; |
u16 = htons(kDNSServiceClass_IN); |
[rrData appendBytes:&u16 length:sizeof(u16)]; |
u32 = htonl(666); |
[rrData appendBytes:&u32 length:sizeof(u32)]; |
u16 = htons(rdlen); |
[rrData appendBytes:&u16 length:sizeof(u16)]; |
[rrData appendBytes:rdata length:rdlen]; |
// Parse the record. |
rr = dns_parse_resource_record([rrData bytes], (uint32_t) [rrData length]); |
assert(rr != NULL); |
// If the parse is successful, add the results to the array. |
if (rr != NULL) { |
NSString * target; |
target = [NSString stringWithCString:rr->data.SRV->target encoding:NSASCIIStringEncoding]; |
if (target != nil) { |
NSDictionary * result; |
NSIndexSet * resultIndexSet; |
result = [NSDictionary dictionaryWithObjectsAndKeys: |
[NSNumber numberWithUnsignedInt:rr->data.SRV->priority], kSRVResolverPriority, |
[NSNumber numberWithUnsignedInt:rr->data.SRV->weight], kSRVResolverWeight, |
[NSNumber numberWithUnsignedInt:rr->data.SRV->port], kSRVResolverPort, |
target, kSRVResolverTarget, |
nil |
]; |
assert(result != nil); |
resultIndexSet = [NSIndexSet indexSetWithIndex:self.results.count]; |
assert(resultIndexSet != nil); |
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:resultIndexSet forKey:@"results"]; |
[self.resultsMutable addObject:result]; |
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:resultIndexSet forKey:@"results"]; |
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(srvResolver:didReceiveResult:)] ) { |
[self.delegate srvResolver:self didReceiveResult:result]; |
} |
} |
dns_free_resource_record(rr); |
} |
} |
static void QueryRecordCallback( |
DNSServiceRef sdRef, |
DNSServiceFlags flags, |
uint32_t interfaceIndex, |
DNSServiceErrorType errorCode, |
const char * fullname, |
uint16_t rrtype, |
uint16_t rrclass, |
uint16_t rdlen, |
const void * rdata, |
uint32_t ttl, |
void * context |
) |
// Call (via our CFSocket callback) when we get a response to our query. |
// It does some preliminary work, but the bulk of the interesting stuff |
// is done in the -processRecord:length: method. |
{ |
SRVResolver * obj; |
obj = (__bridge SRVResolver *) context; |
assert([obj isKindOfClass:[SRVResolver class]]); |
#pragma unused(sdRef) |
assert(sdRef == obj->_sdRef); |
assert(flags & kDNSServiceFlagsAdd); |
#pragma unused(interfaceIndex) |
// errorCode looked at below |
#pragma unused(fullname) |
#pragma unused(rrtype) |
assert(rrtype == kDNSServiceType_SRV); |
#pragma unused(rrclass) |
assert(rrclass == kDNSServiceClass_IN); |
// rdlen and rdata used below |
#pragma unused(ttl) |
// context used above |
if (errorCode == kDNSServiceErr_NoError) { |
[obj processRecord:rdata length:rdlen]; |
// We're assuming SRV records over unicast DNS here, so the first result packet we get |
// will contain all the information we're going to get. In a more dynamic situation |
// (for example, multicast DNS or long-lived queries in Back to My Mac) we'd would want |
// to leave the query running. |
if ( ! (flags & kDNSServiceFlagsMoreComing) ) { |
[obj stopWithError:nil]; |
} |
} else { |
[obj stopWithDNSServiceError:errorCode]; |
} |
} |
static void SDRefSocketCallback( |
CFSocketRef s, |
CFSocketCallBackType type, |
CFDataRef address, |
const void * data, |
void * info |
) |
// A CFSocket callback. This runs when we get messages from mDNSResponder |
// regarding our DNSServiceRef. We just turn around and call DNSServiceProcessResult, |
// which does all of the heavy lifting (and would typically call QueryRecordCallback). |
{ |
DNSServiceErrorType err; |
SRVResolver * obj; |
#pragma unused(type) |
assert(type == kCFSocketReadCallBack); |
#pragma unused(address) |
#pragma unused(data) |
obj = (__bridge SRVResolver *) info; |
assert([obj isKindOfClass:[SRVResolver class]]); |
#pragma unused(s) |
assert(s == obj->_sdRefSocket); |
err = DNSServiceProcessResult(obj->_sdRef); |
if (err != kDNSServiceErr_NoError) { |
[obj stopWithDNSServiceError:err]; |
} |
} |
- (void)startInternal |
{ |
DNSServiceErrorType err; |
const char * srvNameCStr; |
int fd; |
CFSocketContext context = { 0, (__bridge void *) self, NULL, NULL, NULL }; |
CFRunLoopSourceRef rls; |
assert(self->_sdRef == NULL); |
// Create the DNSServiceRef to run our query. |
err = kDNSServiceErr_NoError; |
srvNameCStr = [self.srvName UTF8String]; |
if (srvNameCStr == nil) { |
err = kDNSServiceErr_BadParam; |
} |
if (err == kDNSServiceErr_NoError) { |
err = DNSServiceQueryRecord( |
&self->_sdRef, |
kDNSServiceFlagsReturnIntermediates, |
0, // interfaceIndex |
srvNameCStr, |
kDNSServiceType_SRV, |
kDNSServiceClass_IN, |
QueryRecordCallback, |
(__bridge void *)(self) |
); |
} |
// Create a CFSocket to handle incoming messages associated with the |
// DNSServiceRef. |
if (err == kDNSServiceErr_NoError) { |
assert(self->_sdRef != NULL); |
fd = DNSServiceRefSockFD(self->_sdRef); |
assert(fd >= 0); |
assert(self->_sdRefSocket == NULL); |
self->_sdRefSocket = CFSocketCreateWithNative( |
NULL, |
fd, |
kCFSocketReadCallBack, |
SDRefSocketCallback, |
&context |
); |
assert(self->_sdRefSocket != NULL); |
CFSocketSetSocketFlags( |
self->_sdRefSocket, |
CFSocketGetSocketFlags(self->_sdRefSocket) & ~ (CFOptionFlags) kCFSocketCloseOnInvalidate |
); |
rls = CFSocketCreateRunLoopSource(NULL, self->_sdRefSocket, 0); |
assert(rls != NULL); |
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); |
CFRelease(rls); |
} |
if (err != kDNSServiceErr_NoError) { |
[self stopWithDNSServiceError:err]; |
} |
} |
@end |
NSString * kSRVResolverPriority = @"priority"; |
NSString * kSRVResolverWeight = @"weight"; |
NSString * kSRVResolverPort = @"port"; |
NSString * kSRVResolverTarget = @"target"; |
NSString * kSRVResolverErrorDomain = @"kSRVResolverErrorDomain"; |
Copyright © 2012 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2012-08-29