Skip to content

Instantly share code, notes, and snippets.

@yasirmturk
Created August 12, 2014 05:49
Show Gist options
  • Save yasirmturk/30207ffc8a44a735d992 to your computer and use it in GitHub Desktop.
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
//
// 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
//
// 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