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