Created
August 12, 2014 05:49
-
-
Save yasirmturk/30207ffc8a44a735d992 to your computer and use it in GitHub Desktop.
Quick MX resolver class can be modified to resolve other DNS records like NS, PTR, TXT
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
// | |
// YTMXResolver.h | |
// | |
// Copyright 2011 Yasir M Turk. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
#include <dns_sd.h> | |
#ifndef DNSCONSTANTS | |
// Keys for the dictionaries in the results array: | |
extern NSString * kSRVResolverPriority; // NSNumber, host byte order | |
extern NSString * kSRVResolverWeight; // NSNumber, host byte order | |
extern NSString * kSRVResolverPort; // NSNumber, host byte order | |
extern NSString * kDNSResolverTarget; // NSString | |
extern NSString * kDNSResolverType; // NSString | |
extern NSString * kSRVResolverErrorDomain; | |
#define DNSCONSTANTS 1 | |
#endif | |
@protocol YTMXResolverDelegate; | |
@interface YTMXResolver : NSObject | |
@property (nonatomic, copy, readonly) NSString * srvName; | |
@property (nonatomic, assign, readwrite) id<YTMXResolverDelegate> delegate; | |
@property (nonatomic, assign, readonly, getter=isFinished) BOOL finished; // observable | |
@property (nonatomic, retain, readonly) NSError * error; // observable | |
@property (nonatomic, retain, readonly) NSMutableArray * results; // of NSDictionary, observable | |
- (id)initWithServerName:(NSString *)serverName; | |
- (void)start; | |
- (void)stop; | |
@end | |
@protocol YTMXResolverDelegate <NSObject> | |
@optional | |
- (void)srvResolver:(YTMXResolver *)resolver didReceiveResult:(NSDictionary *)result; | |
- (void)srvResolver:(YTMXResolver *)resolver didStopWithError:(NSError *)error; | |
@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
// | |
// YTMXResolver.m | |
// | |
// Copyright 2011 Yasir M Turk. All rights reserved. | |
// | |
#import "YTMXResolver.h" | |
#include <dns_util.h> | |
#ifndef DNSCONSTANTS | |
NSString * kSRVResolverPriority = @"priority"; | |
NSString * kSRVResolverWeight = @"weight"; | |
NSString * kSRVResolverPort = @"port"; | |
NSString * kDNSResolverTarget = @"target"; | |
NSString * kDNSResolverType = @"type"; | |
NSString * kSRVResolverErrorDomain = @"kSRVResolverErrorDomain"; | |
#define DNSCONSTANTS 1 | |
#endif | |
@interface YTMXResolver () { | |
DNSServiceRef sdRef; | |
CFSocketRef sdRefSocket; | |
} | |
// Forward declarations | |
- (void)_start; | |
@end | |
@implementation YTMXResolver | |
- (id)initWithServerName:(NSString *)serverName{ | |
assert(serverName != nil); | |
self = [super init]; | |
if (self != nil) { | |
_srvName = [serverName copy]; | |
_results = [[NSMutableArray alloc] init]; | |
} | |
return self; | |
} | |
- (void)_processRecord:(const void *)rdata length:(NSUInteger)rdlen ofType:(uint16_t)rrType{ | |
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_MX); | |
//u16 = htons(rrType); | |
[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; | |
NSString * type; | |
switch (rr->dnstype) { | |
case kDNSServiceType_MX: | |
type = @"Mail Exchanger (MX)"; | |
target = [NSString stringWithCString:rr->data.MX->name encoding:NSASCIIStringEncoding]; | |
break; | |
default: | |
type = nil; | |
target = nil; | |
break; | |
} | |
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, kDNSResolverTarget, | |
type, kDNSResolverType, | |
nil | |
]; | |
assert(result != nil); | |
resultIndexSet = [NSIndexSet indexSetWithIndex:self.results.count]; | |
assert(resultIndexSet != nil); | |
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:resultIndexSet forKey:@"results"]; | |
[self.results 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. | |
{ | |
YTMXResolver * obj; | |
obj = (__bridge YTMXResolver *) context; | |
assert([obj isKindOfClass:[YTMXResolver class]]); | |
#pragma unused(sdRef) | |
assert(sdRef == obj->sdRef); | |
assert(flags & kDNSServiceFlagsAdd); | |
#pragma unused(interfaceIndex) | |
// errorCode looked at below | |
#pragma unused(fullname) | |
NSLog(@"kDNSServiceType:[%d]", rrtype); | |
#pragma unused(rrtype) | |
assert(rrtype == kDNSServiceType_MX); | |
#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 ofType:rrtype]; | |
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; | |
YTMXResolver * obj; | |
#pragma unused(type) | |
assert(type == kCFSocketReadCallBack); | |
#pragma unused(address) | |
#pragma unused(data) | |
obj = (__bridge YTMXResolver *) info; | |
assert([obj isKindOfClass:[YTMXResolver class]]); | |
#pragma unused(s) | |
assert(s == obj->sdRefSocket); | |
err = DNSServiceProcessResult(obj->sdRef); | |
if (err != kDNSServiceErr_NoError) { | |
[obj _stopWithDNSServiceError:err]; | |
} | |
} | |
- (void)_start{ | |
DNSServiceErrorType err; | |
const char * srvNameCStr; | |
int fd; | |
CFSocketContext context = { 0, (__bridge void *)(self), NULL, NULL, NULL }; | |
CFRunLoopSourceRef rls; | |
assert(sdRef == NULL); | |
// Create the DNSServiceRef to run our query. | |
err = kDNSServiceErr_NoError; | |
srvNameCStr = [self.srvName cStringUsingEncoding:NSASCIIStringEncoding]; | |
if (srvNameCStr == nil) { | |
err = kDNSServiceErr_BadParam; | |
} | |
if (err == kDNSServiceErr_NoError) { | |
err = DNSServiceQueryRecord(&sdRef, | |
kDNSServiceFlagsReturnIntermediates, | |
kDNSServiceInterfaceIndexAny, // interfaceIndex | |
srvNameCStr, | |
kDNSServiceType_MX, | |
kDNSServiceClass_IN, | |
QueryRecordCallback, | |
(__bridge void *)(self)); | |
} | |
// Create a CFSocket to handle incoming messages associated with the | |
// DNSServiceRef. | |
if (err == kDNSServiceErr_NoError) { | |
assert(sdRef != NULL); | |
fd = DNSServiceRefSockFD(sdRef); | |
assert(fd >= 0); | |
assert(sdRefSocket == NULL); | |
sdRefSocket = CFSocketCreateWithNative(NULL, | |
fd, | |
kCFSocketReadCallBack, | |
SDRefSocketCallback, | |
&context); | |
assert(sdRefSocket != NULL); | |
CFSocketSetSocketFlags(sdRefSocket, | |
CFSocketGetSocketFlags(sdRefSocket) & ~kCFSocketCloseOnInvalidate); | |
rls = CFSocketCreateRunLoopSource(NULL, sdRefSocket, 0); | |
assert(rls != NULL); | |
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); | |
CFRelease(rls); | |
} | |
if (err != kDNSServiceErr_NoError) { | |
[self _stopWithDNSServiceError:err]; | |
} | |
} | |
- (void)start{ | |
if (sdRef == NULL) { | |
_error = nil; // starting up again, so forget any previous error | |
_finished = NO; | |
[self _start]; | |
} | |
} | |
- (void)stop{ | |
if (sdRefSocket != NULL) { | |
CFSocketInvalidate(sdRefSocket); | |
CFRelease(sdRefSocket); | |
sdRefSocket = NULL; | |
} | |
if (sdRef != NULL) { | |
DNSServiceRefDeallocate(sdRef); | |
sdRef = NULL; | |
} | |
_finished = YES; | |
} | |
- (void)_stopWithError:(NSError *)theError{ | |
// error may be nil | |
_error = theError; | |
[self stop]; | |
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(srvResolver:didStopWithError:)] ) { | |
[self.delegate srvResolver:self didStopWithError:theError]; | |
} | |
} | |
- (void)_stopWithDNSServiceError:(DNSServiceErrorType)errorCode{ | |
NSError * theError; | |
theError = nil; | |
if (errorCode != kDNSServiceErr_NoError) { | |
theError = [NSError errorWithDomain:kSRVResolverErrorDomain code:errorCode userInfo:nil]; | |
} | |
[self _stopWithError:theError]; | |
} | |
- (void)dealloc{ | |
[self stop]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment