PortMapper/MYDNSService.m
author Jens Alfke <jens@mooseyard.com>
Mon Jul 20 14:50:49 2009 -0700 (2009-07-20)
changeset 60 dd637bdd214e
parent 31 1d6924779df7
permissions -rw-r--r--
DNS NULL record support in MYBonjourRegistration. Minor fix to IPAddress init. Force 4-char indent in source files.
     1 //
     2 //  MYDNSService.m
     3 //  MYNetwork
     4 //
     5 //  Created by Jens Alfke on 4/23/09.
     6 //  Copyright 2009 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYDNSService.h"
    10 #import "CollectionUtils.h"
    11 #import "Logging.h"
    12 #import "Test.h"
    13 #import "ExceptionUtils.h"
    14 
    15 #import <dns_sd.h>
    16 
    17 
    18 static void serviceCallback(CFSocketRef s, 
    19                             CFSocketCallBackType type,
    20                             CFDataRef address,
    21                             const void *data,
    22                             void *clientCallBackInfo);
    23         
    24 
    25 @implementation MYDNSService
    26 
    27 
    28 - (void) dealloc
    29 {
    30     Log(@"DEALLOC %@ %p", self.class,self);
    31     if( _serviceRef )
    32         [self cancel];
    33     [super dealloc];
    34 }
    35 
    36 - (void) finalize
    37 {
    38     if( _serviceRef )
    39         [self cancel];
    40     [super finalize];
    41 }
    42 
    43 
    44 - (DNSServiceErrorType) error {
    45     return _error;
    46 }
    47 
    48 - (void) setError: (DNSServiceErrorType)error {
    49     if (error)
    50         Warn(@"%@ error := %i", self,error);
    51     _error = error;
    52 }
    53 
    54 
    55 @synthesize continuous=_continuous, serviceRef=_serviceRef, usePrivateConnection=_usePrivateConnection;
    56 
    57 
    58 - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
    59     AssertAbstractMethod();
    60 }
    61 
    62 
    63 - (void) gotResponse: (DNSServiceErrorType)errorCode {
    64     _gotResponse = YES;
    65     if (!_continuous)
    66         [self cancel];
    67     if (errorCode && errorCode != _error) {
    68         Log(@"%@ got error %i", self,errorCode);
    69         self.error = errorCode;
    70     }
    71 }
    72 
    73 
    74 - (BOOL) start
    75 {
    76     if (_serviceRef)
    77         return YES;     // already started
    78 
    79     if (_error)
    80         self.error = 0;
    81     _gotResponse = NO;
    82 
    83     if (!_usePrivateConnection) {
    84         _connection = [[MYDNSConnection sharedConnection] retain];
    85         if (!_connection) {
    86             self.error = kDNSServiceErr_Unknown;
    87             return NO;
    88         }
    89         _serviceRef = _connection.connectionRef;
    90     }
    91     
    92     // Ask the subclass to create a DNSServiceRef:
    93     _error = [self createServiceRef: &_serviceRef];
    94     if (_error) {
    95         _serviceRef = NULL;
    96         setObj(&_connection,nil);
    97         if (!_error)
    98             self.error = kDNSServiceErr_Unknown;
    99         LogTo(DNS,@"Failed to open %@ -- err=%i",self,_error);
   100         return NO;
   101     }
   102     
   103     if (!_connection)
   104         _connection = [[MYDNSConnection alloc] initWithServiceRef: _serviceRef];
   105     
   106     LogTo(DNS,@"Started %@",self);
   107     return YES; // Succeeded
   108 }
   109 
   110 
   111 - (BOOL) waitForReply {
   112     if( ! _serviceRef )
   113         if( ! [self start] )
   114             return NO;
   115     // Run the runloop until there's either an error or a result:
   116     _gotResponse = NO;
   117     LogTo(DNS,@"Waiting for reply to %@...", self);
   118     while( !_gotResponse )
   119         if( ! [_connection processResult] )
   120             break;
   121     LogTo(DNS,@"    ...got reply");
   122     return (self.error==0);
   123 }
   124 
   125 
   126 - (void) cancel
   127 {
   128     if( _serviceRef ) {
   129         LogTo(DNS,@"Stopped %@",self);
   130         DNSServiceRefDeallocate(_serviceRef);
   131         _serviceRef = NULL;
   132         
   133         setObj(&_connection,nil);
   134     }
   135 }
   136 
   137 
   138 - (void) stop
   139 {
   140     [self cancel];
   141     if (_error)
   142         self.error = 0;
   143 }
   144 
   145 
   146 - (BOOL) isRunning {
   147     return _serviceRef != NULL;
   148 }
   149 
   150 
   151 + (NSString*) fullNameOfService: (NSString*)serviceName
   152                          ofType: (NSString*)type
   153                        inDomain: (NSString*)domain
   154 {
   155     //FIX: Do I need to un-escape the serviceName?
   156     Assert(type);
   157     Assert(domain);
   158     char fullName[kDNSServiceMaxDomainName];
   159     if (DNSServiceConstructFullName(fullName, serviceName.UTF8String, type.UTF8String, domain.UTF8String) == 0)
   160         return [NSString stringWithUTF8String: fullName];
   161     else
   162         return nil;
   163 }
   164 
   165 
   166 @end
   167 
   168 
   169 #pragma mark -
   170 #pragma mark SHARED CONNECTION:
   171 
   172 
   173 @interface MYDNSConnection ()
   174 - (BOOL) open;
   175 @end
   176 
   177 
   178 @implementation MYDNSConnection
   179 
   180 
   181 MYDNSConnection *sSharedConnection;
   182 
   183 
   184 - (id) init
   185 {
   186     DNSServiceRef connectionRef = NULL;
   187     DNSServiceErrorType err = DNSServiceCreateConnection(&connectionRef);
   188     if (err || !connectionRef) {
   189         Warn(@"MYDNSConnection: DNSServiceCreateConnection failed, err=%i", err);
   190         [self release];
   191         return nil;
   192     }
   193     return [self initWithServiceRef: connectionRef];
   194 }
   195 
   196 
   197 - (id) initWithServiceRef: (DNSServiceRef)serviceRef
   198 {
   199     Assert(serviceRef);
   200     self = [super init];
   201     if (self != nil) {
   202         _connectionRef = serviceRef;
   203         LogTo(DNS,@"INIT %@", self);
   204         if (![self open]) {
   205             [self release];
   206             return nil;
   207         }
   208     }
   209     return self;
   210 }
   211 
   212 
   213 + (MYDNSConnection*) sharedConnection {
   214     @synchronized(self) {
   215         if (!sSharedConnection)
   216             sSharedConnection = [[[self alloc] init] autorelease];
   217     }
   218     return sSharedConnection;
   219 }
   220 
   221 
   222 - (void) dealloc
   223 {
   224     LogTo(DNS,@"DEALLOC %@", self);
   225     [self close];
   226     [super dealloc];
   227 }
   228 
   229 - (void) finalize {
   230     [self close];
   231     [super finalize];
   232 }
   233 
   234 
   235 @synthesize connectionRef=_connectionRef;
   236 
   237 - (NSString*) description {
   238     return $sprintf(@"%@[conn=%p]", self.class,_connectionRef);
   239 }
   240 
   241 - (BOOL) open {
   242     if (_runLoopSource)
   243         return YES;        // Already opened
   244     
   245     // Wrap a CFSocket around the service's socket:
   246     CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL };
   247     _socket = CFSocketCreateWithNative(NULL, 
   248                                                        DNSServiceRefSockFD(_connectionRef), 
   249                                                        kCFSocketReadCallBack, 
   250                                                        &serviceCallback, &ctxt);
   251     if( _socket ) {
   252         CFSocketSetSocketFlags(_socket, 
   253                                CFSocketGetSocketFlags(_socket) & ~kCFSocketCloseOnInvalidate);
   254         // Attach the socket to the runloop so the serviceCallback will be invoked:
   255         _runLoopSource = CFSocketCreateRunLoopSource(NULL, _socket, 0);
   256         if( _runLoopSource ) {
   257             CFRunLoopAddSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes);
   258             // Success!
   259             LogTo(DNS,@"Successfully opened %@", self);
   260             return YES;
   261         }
   262     }
   263     
   264     // Failure:
   265     Warn(@"Failed to connect %@ to runloop", self);
   266     [self close];
   267     return NO;
   268 }
   269 
   270 
   271 - (void) close {
   272     @synchronized(self) {
   273         if( _runLoopSource ) {
   274             CFRunLoopSourceInvalidate(_runLoopSource);
   275             CFRelease(_runLoopSource);
   276             _runLoopSource = NULL;
   277         }
   278         if( _socket ) {
   279             CFSocketInvalidate(_socket);
   280             CFRelease(_socket);
   281             _socket = NULL;
   282         }
   283         if( _connectionRef ) {
   284             LogTo(DNS,@"Closed %@",self);
   285             DNSServiceRefDeallocate(_connectionRef);
   286             _connectionRef = NULL;
   287         }
   288         
   289         if (self==sSharedConnection)
   290             sSharedConnection = nil;
   291     }
   292 }
   293 
   294 
   295 - (BOOL) processResult {
   296     NSAutoreleasePool *pool = [NSAutoreleasePool new];
   297     LogTo(DNS,@"---serviceCallback----");
   298     DNSServiceErrorType err = DNSServiceProcessResult(_connectionRef);
   299     if (err) {
   300         Warn(@"%@: DNSServiceProcessResult failed, err=%i !!!", self,err);
   301         //FIX: Are errors here fatal, meaning I should close the connection?
   302         // I've run into infinite loops constantly getting kDNSServiceErr_ServiceNotRunning
   303         // or kDNSServiceErr_BadReference ...
   304     }
   305     [pool drain];
   306     return !err;
   307 }
   308 
   309 
   310 /** CFSocket callback, informing us that _socket has data available, which means
   311     that the DNS service has an incoming result to be processed. This will end up invoking
   312     the service's specific callback. */
   313 static void serviceCallback(CFSocketRef s, 
   314                             CFSocketCallBackType type,
   315                             CFDataRef address, const void *data, void *clientCallBackInfo)
   316 {
   317     MYDNSConnection *connection = clientCallBackInfo;
   318     [connection processResult];
   319 }
   320 
   321 
   322 @end
   323 
   324 
   325 /*
   326  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   327  
   328  Redistribution and use in source and binary forms, with or without modification, are permitted
   329  provided that the following conditions are met:
   330  
   331  * Redistributions of source code must retain the above copyright notice, this list of conditions
   332  and the following disclaimer.
   333  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   334  and the following disclaimer in the documentation and/or other materials provided with the
   335  distribution.
   336  
   337  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   338  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   339  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   340  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   341  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   342   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   343  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   344  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   345  */