Frameworks/AIUtilities Framework/Source/AIHostReachabilityMonitor.m
author Zachary West <zacw@adium.im>
Sat Oct 24 11:32:51 2009 -0400 (2009-10-24)
changeset 2768 6ba5f8f8102a
parent 2170 be5dc822a044
child 2769 28fc8d5dd66c
permissions -rw-r--r--
Add an observer for both AF_INET (IPv4) and AF_INET6 (IPv6) DNS records for connection hosts. Fixes #12632.

When we're determining reachability, we consult all entries for a host, instead of the one that changed. We only add one observer for each record type.
David@0
     1
//
David@0
     2
//  AIHostReachabilityMonitor.m
David@0
     3
//  AIUtilities.framework
David@0
     4
//
David@0
     5
//  Created by Mac-arena the Bored Zo on 2005-02-11.
David@0
     6
//
David@0
     7
David@0
     8
#import "AIHostReachabilityMonitor.h"
David@0
     9
#import "AISleepNotification.h"
David@0
    10
#import <SystemConfiguration/SystemConfiguration.h>
catfish@2093
    11
#import <arpa/inet.h>
catfish@2093
    12
#import <netdb.h>
catfish@2093
    13
#import <netinet/in.h>
David@0
    14
David@0
    15
#define CONNECTIVITY_DEBUG FALSE
David@0
    16
David@0
    17
static AIHostReachabilityMonitor *singleton = nil;
David@0
    18
David@498
    19
@interface AIHostReachabilityMonitor ()
David@0
    20
- (void)scheduleReachabilityMonitoringForHost:(NSString *)nodename observer:(id)observer;
David@0
    21
- (void)gotReachabilityRef:(SCNetworkReachabilityRef)reachabilityRef forHost:(NSString *)host observer:(id)observer;
David@0
    22
David@0
    23
- (void)addUnconfiguredHost:(NSString *)host observer:(id)observer;
David@0
    24
- (void)removeUnconfiguredHost:(NSString *)host observer:(id)observer;
David@0
    25
- (void)queryUnconfiguredHosts;
David@0
    26
David@0
    27
- (void)beginMonitorngIPChanges;
David@0
    28
- (void)stopMonitoringIPChanges;
David@0
    29
@end
David@0
    30
David@0
    31
@implementation AIHostReachabilityMonitor
David@0
    32
David@0
    33
#pragma mark Shared instance management
David@0
    34
David@0
    35
/*!
David@0
    36
 *	@brief	Returns a shared instance, usable for most purposes.
David@0
    37
 *	@return A shared AIHostReachabilityMonitor instance.
David@0
    38
 */
David@0
    39
+ (id)defaultMonitor
David@0
    40
{
David@0
    41
	if (!singleton) {
David@0
    42
		singleton = [[AIHostReachabilityMonitor alloc] init];
David@0
    43
	}
David@0
    44
David@0
    45
	return singleton;
David@0
    46
}
David@0
    47
David@0
    48
#pragma mark -
David@0
    49
#pragma mark Birth and death
David@0
    50
David@0
    51
/*
David@0
    52
 * @brief Initialize
David@0
    53
 */
David@0
    54
- (id)init
David@0
    55
{
David@0
    56
	if ((self = [super init])) {
David@0
    57
		hostAndObserverListLock = [[NSLock alloc] init];
catfish@2170
    58
		[hostAndObserverListLock setName:@"HostAndObserverListLock"];
David@0
    59
David@0
    60
		[hostAndObserverListLock lock];
David@0
    61
		hosts          = [[NSMutableArray alloc] init];
David@0
    62
		observers      = [[NSMutableArray alloc] init];
David@0
    63
		reachabilities = [[NSMutableArray alloc] init];
David@0
    64
		
David@0
    65
		unconfiguredHostsAndObservers = [[NSMutableSet alloc] init];
David@0
    66
		ipChangesRunLoopSourceRef = nil;
David@0
    67
David@0
    68
		[hostAndObserverListLock unlock];
David@0
    69
		
David@0
    70
		//Monitor system sleep so we can accurately report connectivity changes when the system wakes
David@0
    71
		[[NSNotificationCenter defaultCenter] addObserver:self
David@0
    72
												 selector:@selector(systemDidWake:)
David@0
    73
													 name:AISystemDidWake_Notification
David@0
    74
												   object:nil];
David@0
    75
	}
David@0
    76
	return self;
David@0
    77
}
David@0
    78
David@0
    79
/*
David@0
    80
 * @brief Deallocate
David@0
    81
 */
David@0
    82
- (void)dealloc
David@0
    83
{
David@0
    84
	[hostAndObserverListLock lock];
David@0
    85
	[hosts          release]; hosts          = nil;
David@0
    86
	[observers      release]; observers      = nil;
David@0
    87
	[reachabilities release]; reachabilities = nil;
David@0
    88
	
David@0
    89
	[unconfiguredHostsAndObservers release]; unconfiguredHostsAndObservers = nil;
David@0
    90
	[hostAndObserverListLock unlock];
David@0
    91
David@0
    92
	[hostAndObserverListLock release];
David@0
    93
David@0
    94
	[[NSNotificationCenter defaultCenter] removeObserver:self];
David@0
    95
David@0
    96
	[super dealloc];
David@0
    97
}
David@0
    98
David@0
    99
#pragma mark -
David@0
   100
#pragma mark Observer management
David@0
   101
David@0
   102
/*!
David@0
   103
 *	@brief Begins observing a host's reachability for an object.
David@0
   104
 */
David@0
   105
- (void)addObserver:(id <AIHostReachabilityObserver>)newObserver forHost:(NSString *)host
David@0
   106
{
David@0
   107
	NSParameterAssert(host != nil);
David@0
   108
	NSParameterAssert(newObserver != nil);
David@0
   109
David@0
   110
	NSString	*hostCopy = [host copy];
David@0
   111
	[self scheduleReachabilityMonitoringForHost:hostCopy
David@0
   112
									   observer:newObserver];
David@0
   113
	[hostCopy release];
David@0
   114
}
David@0
   115
David@0
   116
/*!
David@0
   117
 *	@brief Stops an object's observation of a host's reachability.
David@0
   118
 *
David@0
   119
 *	When host is non-nil, stops observing that host's reachability for the given observer.
David@0
   120
 *	When host is nil, stops observing all hosts' reachability for the given observer.
David@0
   121
 */
David@0
   122
David@0
   123
- (void)removeObserver:(id <AIHostReachabilityObserver>)newObserver forHost:(NSString *)host
David@0
   124
{
David@0
   125
	//nil cannot observe, so it must not be in the list.
David@0
   126
	if (!newObserver) return;
David@0
   127
David@0
   128
	[hostAndObserverListLock lock];
David@0
   129
David@0
   130
	unsigned numObservers = [observers count];
David@0
   131
	for (unsigned i = 0; i < numObservers; ) {
David@0
   132
		BOOL removed = NO;
David@0
   133
		if (newObserver == [observers objectAtIndex:i]) {
David@0
   134
			if ((!host) || ([host isEqualToString:[hosts objectAtIndex:i]])) {
David@0
   135
				[hosts          removeObjectAtIndex:i];
David@0
   136
				[observers      removeObjectAtIndex:i];
David@0
   137
				SCNetworkReachabilityScheduleWithRunLoop((SCNetworkReachabilityRef)[reachabilities objectAtIndex:i],
David@0
   138
														 CFRunLoopGetCurrent(),
David@0
   139
														 kCFRunLoopDefaultMode);
David@0
   140
				[reachabilities removeObjectAtIndex:i];
David@0
   141
David@0
   142
				[self removeUnconfiguredHost:host
David@0
   143
									observer:newObserver];
David@0
   144
				
David@0
   145
				removed = YES;
David@0
   146
				--numObservers;
David@0
   147
			}
David@0
   148
		}
David@0
   149
		i += !removed;
David@0
   150
	}
David@0
   151
David@0
   152
	[hostAndObserverListLock unlock];
David@0
   153
}
David@0
   154
David@0
   155
/*
David@0
   156
 * @brief Is an observer currently observing a host?
David@0
   157
 *
David@0
   158
 * @result YES if so, NO if not
David@0
   159
 */
David@0
   160
- (BOOL)observer:(id)observer isObservingHost:(NSString *)host
David@0
   161
{
David@0
   162
	BOOL isObserving = NO;
David@0
   163
	
David@0
   164
	[hostAndObserverListLock lock];
David@0
   165
David@0
   166
	if (host && observer) {
David@0
   167
		unsigned numObservers = [observers count];
David@0
   168
		for (unsigned i = 0; i < numObservers; i++) {
David@0
   169
			if ((observer == [observers objectAtIndex:i]) &&
David@0
   170
				([host isEqualToString:[hosts objectAtIndex:i]])) {
David@0
   171
				isObserving = YES;
David@0
   172
				break;
David@0
   173
			}
David@0
   174
		}
David@0
   175
		
David@0
   176
		if (!isObserving && [unconfiguredHostsAndObservers count]) {
David@0
   177
			NSDictionary *unconfiguredHostObserverDict = [NSDictionary dictionaryWithObjectsAndKeys:
David@0
   178
				observer, @"observer",
David@0
   179
				host, @"host",
David@0
   180
				nil];
David@0
   181
			if ([unconfiguredHostsAndObservers containsObject:unconfiguredHostObserverDict]) {
David@0
   182
				isObserving = YES;
David@0
   183
			}
David@0
   184
		}
David@0
   185
	}
David@0
   186
	
David@0
   187
	[hostAndObserverListLock unlock];
David@0
   188
David@0
   189
	return isObserving;
David@0
   190
}
David@0
   191
David@0
   192
#pragma mark -
David@0
   193
#pragma mark Reachability monitoring
David@0
   194
David@0
   195
/*
David@0
   196
 * @brief A host's reachability changed
David@0
   197
 *
David@0
   198
 * @param reachability The SCNetworkReachabilityRef for the host which changed
David@0
   199
 * @param isReachable YES if the host is now reachable; NO if it is not reachable
David@0
   200
 */
zacw@2768
   201
- (void)reachabilityChanged:(SCNetworkReachabilityRef)reachability
David@0
   202
{
David@0
   203
	[hostAndObserverListLock lock];
David@0
   204
David@0
   205
	NSUInteger i = [reachabilities indexOfObjectIdenticalTo:(id)reachability];
David@0
   206
	if (i != NSNotFound) {
David@0
   207
		NSString *host = [hosts objectAtIndex:i];
David@0
   208
		id <AIHostReachabilityObserver> observer = [observers objectAtIndex:i];
David@0
   209
		
zacw@2768
   210
		BOOL anyHostsReachable = NO;
zacw@2768
   211
		
zacw@2768
   212
		// If we have multiple host <-> IP links (AAAA record and an A record), we need to check agreement.
zacw@2768
   213
		for (NSUInteger index = 0; index < hosts.count; index++) {
zacw@2768
   214
			if (![host isEqualToString:[hosts objectAtIndex:index]])
zacw@2768
   215
				continue;
zacw@2768
   216
			
zacw@2768
   217
			SCNetworkReachabilityRef otherReachability = (SCNetworkReachabilityRef)[reachabilities objectAtIndex:index];
zacw@2768
   218
			SCNetworkConnectionFlags flags;
zacw@2768
   219
zacw@2768
   220
			if (SCNetworkReachabilityGetFlags(otherReachability, &flags)
zacw@2768
   221
				&& (flags & kSCNetworkFlagsReachable)
zacw@2768
   222
				&& !(flags & kSCNetworkFlagsConnectionRequired)) {
zacw@2768
   223
				anyHostsReachable = YES;
zacw@2768
   224
				break;
zacw@2768
   225
			}
zacw@2768
   226
		}
zacw@2768
   227
		
zacw@2768
   228
		// Return reachability based on any reachability response.
zacw@2768
   229
		if (anyHostsReachable) {
David@0
   230
			[observer hostReachabilityMonitor:self hostIsReachable:host];
David@0
   231
		} else {
David@0
   232
			[observer hostReachabilityMonitor:self hostIsNotReachable:host];
David@0
   233
		}
David@0
   234
	}
David@0
   235
David@0
   236
	[hostAndObserverListLock unlock];
David@0
   237
}
David@0
   238
David@0
   239
/*
David@0
   240
 * @brief Callback for changes in a host's reachability (SCNetworkReachability)
David@0
   241
 *
David@0
   242
 * @param info The AIHostReachabilityMonitor which requested host reachability monitoring
David@0
   243
 */
David@0
   244
static void hostReachabilityChangedCallback(SCNetworkReachabilityRef target, SCNetworkConnectionFlags flags, void *info)
David@0
   245
{
David@0
   246
#if CONNECTIVITY_DEBUG
David@0
   247
	NSLog(@"*** hostReachabilityChangedCallback got flags: %c%c%c%c%c%c%c \n",  
David@0
   248
 	      (flags & kSCNetworkFlagsTransientConnection)  ? 't' : '-',  
David@0
   249
 	      (flags & kSCNetworkFlagsReachable)            ? 'r' : '-',  
David@0
   250
 	      (flags & kSCNetworkFlagsConnectionRequired)   ? 'c' : '-',  
David@0
   251
 	      (flags & kSCNetworkFlagsConnectionAutomatic)  ? 'C' : '-',  
David@0
   252
 	      (flags & kSCNetworkFlagsInterventionRequired) ? 'i' : '-',  
David@0
   253
 	      (flags & kSCNetworkFlagsIsLocalAddress)       ? 'l' : '-',  
David@0
   254
 	      (flags & kSCNetworkFlagsIsDirect)             ? 'd' : '-');
David@0
   255
#endif
zacw@2768
   256
	
David@0
   257
	AIHostReachabilityMonitor *self = info;
zacw@2768
   258
	[self reachabilityChanged:target];
David@0
   259
}
David@0
   260
David@0
   261
/*
David@0
   262
 * @brief Callbacak for resolution of a host's name to an IP (CFHost)
David@0
   263
 *
David@0
   264
 * @param info An NSDictionary with the keys @"self", @"observer", and @"host"
David@0
   265
 */
David@0
   266
static void hostResolvedCallback(CFHostRef theHost, CFHostInfoType typeInfo,  const CFStreamError *error, void *info)
David@0
   267
{
David@0
   268
	NSDictionary				*infoDict = info;
David@0
   269
	AIHostReachabilityMonitor	*self = [infoDict objectForKey:@"self"];
David@0
   270
	id							observer = [infoDict objectForKey:@"observer"];
David@0
   271
	NSString					*host = [infoDict objectForKey:@"host"];
David@0
   272
David@0
   273
	if (typeInfo == kCFHostAddresses) {
zacw@2768
   274
		//CFHostGetAddressing returns a CFArrayRef of CFDataRefs which wrap struct sockaddr
David@0
   275
		CFArrayRef addresses = CFHostGetAddressing(theHost, NULL);
zacw@2768
   276
		
zacw@2768
   277
		if (!CFArrayGetCount(addresses)) {
zacw@2768
   278
			/* We were not able to resolve the host name to an IP address.  This is most likely because we have no
zacw@2768
   279
			 * Internet connection or because the user is attempting to connect to MSN.
zacw@2768
   280
			 *
zacw@2768
   281
			 * Add to unconfiguredHostsAndObservers so we can try configuring again later.
zacw@2768
   282
			 */
zacw@2768
   283
			[self addUnconfiguredHost:host
zacw@2768
   284
							 observer:observer];
zacw@2768
   285
		}
zacw@2768
   286
		
zacw@2768
   287
		// Only add 1 observer for IPv6 and one for IPv4.
zacw@2768
   288
		BOOL addedIPv4 = NO, addedIPv6 = NO;
zacw@2768
   289
		
zacw@2768
   290
		for (NSData *saData in (NSArray *)addresses) {
zacw@2768
   291
			struct sockaddr							*remoteAddr = (struct sockaddr *)saData.bytes;
zacw@2768
   292
			
zacw@2768
   293
			if ((remoteAddr->sa_family == AF_INET && addedIPv4) || (remoteAddr->sa_family == AF_INET6 && addedIPv6)) {
zacw@2768
   294
				continue;
zacw@2768
   295
			}
zacw@2768
   296
			
zacw@2768
   297
			if (remoteAddr->sa_family == AF_INET)
zacw@2768
   298
				addedIPv4 = YES;
zacw@2768
   299
			if (remoteAddr->sa_family == AF_INET6)
zacw@2768
   300
				addedIPv6 = YES;
zacw@2768
   301
						
David@0
   302
			SCNetworkReachabilityRef		reachabilityRef;
David@0
   303
			SCNetworkReachabilityContext	reachabilityContext = {
David@0
   304
				.version         = 0,
David@0
   305
				.info            = self,
David@0
   306
				.retain          = CFRetain,
David@0
   307
				.release         = CFRelease,
David@0
   308
				.copyDescription = CFCopyDescription,
David@0
   309
			};
David@0
   310
			SCNetworkConnectionFlags				flags;
David@0
   311
			struct sockaddr_in						localAddr;
David@0
   312
			
David@0
   313
			/* Create a reachability reference pair with localhost and the remote host */
David@0
   314
			
David@0
   315
			//Our local address is 127.0.0.1
David@0
   316
			bzero(&localAddr, sizeof(localAddr));
David@0
   317
			localAddr.sin_len = sizeof(localAddr);
David@0
   318
			localAddr.sin_family = AF_INET;
David@0
   319
			inet_aton("127.0.0.1", &localAddr.sin_addr);
David@0
   320
			
David@0
   321
			//Create the pair
David@0
   322
			reachabilityRef = SCNetworkReachabilityCreateWithAddressPair(NULL, 
David@0
   323
																		 (struct sockaddr *)&localAddr, 
David@0
   324
																		 remoteAddr);
David@0
   325
			
David@0
   326
			//Configure our callback
David@0
   327
			SCNetworkReachabilitySetCallback(reachabilityRef, 
David@0
   328
											 hostReachabilityChangedCallback, 
David@0
   329
											 &reachabilityContext);
David@0
   330
			
David@0
   331
			//Add it to the run loop so we will receive the notifications
David@0
   332
			SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef,
David@0
   333
													 CFRunLoopGetCurrent(),
David@0
   334
													 kCFRunLoopDefaultMode);
David@0
   335
			
David@0
   336
			//Note that we succesfully configured for reachability notifications
David@0
   337
			[self gotReachabilityRef:(SCNetworkReachabilityRef)[(NSObject *)reachabilityRef autorelease]
David@0
   338
							 forHost:host
David@0
   339
							observer:observer];
David@0
   340
David@0
   341
			if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
David@0
   342
				//We already have valid flags for the reachabilityRef
zacw@2768
   343
		#if CONNECTIVITY_DEBUG
David@0
   344
				NSLog(@"Immediate reachability info for %@", reachabilityRef);
zacw@2768
   345
		#endif
David@0
   346
				hostReachabilityChangedCallback(reachabilityRef,
David@0
   347
												flags,
David@0
   348
												self);
David@0
   349
David@0
   350
			} else {
David@0
   351
				/* Perform an immediate reachability check, since we've just scheduled checks for future changes
David@0
   352
				* and won't be notified immediately.  We update the hostContext to include our reachabilityRef before
David@0
   353
				* scheduling the info resolution (it's still in our run loop from when we requested the IP address).
David@0
   354
				*/
David@0
   355
				CFHostClientContext	hostContext = {
David@0
   356
					.version		 = 0,
David@0
   357
					.info			 = [NSDictionary dictionaryWithObjectsAndKeys:
David@0
   358
						self, @"self",
David@0
   359
						host, @"host",
David@0
   360
						observer, @"observer",
David@0
   361
						reachabilityRef, @"reachabilityRef",
David@0
   362
						nil],
David@0
   363
					.retain			 = CFRetain,
David@0
   364
					.release		 = CFRelease,
David@0
   365
					.copyDescription = CFCopyDescription,
David@0
   366
				};
David@0
   367
				CFHostSetClient(theHost,
David@0
   368
								hostResolvedCallback,
David@0
   369
								&hostContext);
David@0
   370
				CFHostStartInfoResolution(theHost,
David@0
   371
										  kCFHostReachability,
David@0
   372
										  NULL);
David@0
   373
			}
David@0
   374
David@0
   375
		}
David@0
   376
		
David@0
   377
	} else if (typeInfo == kCFHostReachability) {
David@0
   378
		/* Asynchronous host reachability notification from CFHost(), triggered by CFHostStartInfoResolution() above. */
David@0
   379
		SCNetworkConnectionFlags	flags;
David@0
   380
		CFDataRef					flagsData = CFHostGetReachability(theHost,
David@0
   381
																	  NULL);
David@0
   382
		CFDataGetBytes(flagsData,
David@0
   383
					   CFRangeMake(0, CFDataGetLength(flagsData)),
David@0
   384
					   (UInt8 *)&flags);
David@0
   385
David@0
   386
		//Call the reachability changed callback directly
David@0
   387
		hostReachabilityChangedCallback((SCNetworkReachabilityRef)[infoDict objectForKey:@"reachabilityRef"],
David@0
   388
										flags,
David@0
   389
										self);
David@0
   390
David@0
   391
		//No further need for this CFHost to be in our run loop
David@0
   392
		CFHostUnscheduleFromRunLoop(theHost,
David@0
   393
									CFRunLoopGetCurrent(),
David@0
   394
									kCFRunLoopDefaultMode);
David@0
   395
	}
David@0
   396
}
David@0
   397
David@0
   398
/*
David@0
   399
 * @brief We obtained an SCNetworkReachabilityRef for a host/observer pair
David@0
   400
 *
David@0
   401
 * We can now effectively monitor connectivity between us and the host.
David@0
   402
 *
David@0
   403
 * Add these three objects to our hosts, observers, and reachabilities arrays respectively so we can determine the
David@0
   404
 * host and observer given the reachabilityRef in hostReachabilityChangedCallback() above.
David@0
   405
 *
David@0
   406
 * Remove the host/observer pair from the unconfigured dictionary.
David@0
   407
 */
David@0
   408
- (void)gotReachabilityRef:(SCNetworkReachabilityRef)reachabilityRef forHost:(NSString *)host observer:(id)observer
David@0
   409
{
David@0
   410
	//Add to our arrays for tracking
David@0
   411
	[hostAndObserverListLock lock];
David@0
   412
	
David@0
   413
	[hosts          addObject:host];
David@0
   414
	[observers      addObject:observer];
David@0
   415
	[reachabilities addObject:(id)reachabilityRef];
David@0
   416
	
David@0
   417
	//Remove from our unconfigured array
David@0
   418
	[self removeUnconfiguredHost:host
David@0
   419
						observer:observer];
David@0
   420
	
David@0
   421
	[hostAndObserverListLock unlock];
David@0
   422
David@0
   423
#if CONNECTIVITY_DEBUG
David@0
   424
	NSLog(@"Obtained reachability ref %@ for %@ (%@).",reachabilityRef, host, observer);
David@0
   425
#endif
David@0
   426
}
David@0
   427
David@0
   428
/*
David@0
   429
 * @brief Schedule a reachability check for a host, with an observer
David@0
   430
 *
David@0
   431
 * This method begins the process of scheduling the reachability check.  It actually creates a CFHost to schedules
David@0
   432
 * an asynchronous IP lookup for nodename.  hostResolvedCallback() will be called when it succeeds or fails.
David@0
   433
 *
David@0
   434
 * @param nodename The name such as "www.adiumxtras.com"
David@0
   435
 * @param observer The observer which will be notified when the reachability changes
David@0
   436
 */
David@0
   437
- (void)scheduleReachabilityMonitoringForHost:(NSString *)nodename observer:(id)observer
David@0
   438
{
David@0
   439
	//Resolve the remote host domain name to an IP asynchronously
David@0
   440
	CFHostClientContext	hostContext = {
David@0
   441
		.version		 = 0,
David@0
   442
		.info			 = [NSDictionary dictionaryWithObjectsAndKeys:
David@0
   443
							self, @"self",
David@0
   444
							nodename, @"host",
David@0
   445
							observer, @"observer",
David@0
   446
							nil],
David@0
   447
		.retain			 = CFRetain,
David@0
   448
		.release		 = CFRelease,
David@0
   449
		.copyDescription = CFCopyDescription,
David@0
   450
	};
David@0
   451
	CFHostRef host = CFHostCreateWithName(kCFAllocatorDefault,
David@0
   452
										  (CFStringRef)nodename);
David@0
   453
	CFHostSetClient(host,
David@0
   454
					hostResolvedCallback,
David@0
   455
					&hostContext);
David@0
   456
	CFHostScheduleWithRunLoop(host,
David@0
   457
							  CFRunLoopGetCurrent(),
David@0
   458
							  kCFRunLoopDefaultMode);
David@0
   459
	CFHostStartInfoResolution(host,
David@0
   460
							  kCFHostAddresses,
David@0
   461
							  NULL);
David@0
   462
#if CONNECTIVITY_DEBUG
David@0
   463
	NSLog(@"Scheduled reachability check for %@",nodename);
David@0
   464
#endif
David@0
   465
	
David@0
   466
	CFRelease(host);
David@0
   467
}
David@0
   468
David@0
   469
#pragma mark -
David@0
   470
#pragma mark Unconfigured hosts
David@0
   471
/*
David@0
   472
 * @brief Add an unconfigured host and observer to unconfiguredHostsAndObservers
David@0
   473
 *
David@0
   474
 * We have to resolve a host to an IP address before we can properly observe reachability.  unconfiguredHostsAndObservers
David@0
   475
 * holds information on host/observer pairs which we haven't been able to resolve yet.  When the IP configuration changes,
David@0
   476
 * we will try again, hoping to get an IP address this time.
David@0
   477
 */
David@0
   478
- (void)addUnconfiguredHost:(NSString *)host observer:(id)observer
David@0
   479
{
David@0
   480
	NSDictionary *unconfiguredHostDict = [NSDictionary dictionaryWithObjectsAndKeys:
David@0
   481
		observer, @"observer",
David@0
   482
		host, @"host",
David@0
   483
		nil];
David@0
   484
	BOOL addedNewUnconfiguredHost = NO;
David@0
   485
	[hostAndObserverListLock lock];
David@0
   486
	if (![unconfiguredHostsAndObservers containsObject:unconfiguredHostDict]) {
David@0
   487
		addedNewUnconfiguredHost = YES;
David@0
   488
		[unconfiguredHostsAndObservers addObject:unconfiguredHostDict];
David@0
   489
	}
David@0
   490
	[hostAndObserverListLock unlock];
David@0
   491
	
David@0
   492
	if (addedNewUnconfiguredHost) {
David@0
   493
		/* If this is the first unconfigured host, begin monitoring IP changes so we can try to set it (and any others)
David@0
   494
		* up at the earliest possible time.
David@0
   495
		*/
David@0
   496
		if ([unconfiguredHostsAndObservers count] == 1) {
David@0
   497
			[self beginMonitorngIPChanges];
David@0
   498
		}
David@0
   499
David@0
   500
#if CONNECTIVITY_DEBUG
David@0
   501
		NSLog(@"Unable to resolve %@. Now monitoring IP changes for %@",host,unconfiguredHostsAndObservers);
David@0
   502
#endif
David@0
   503
David@0
   504
		/*
David@0
   505
		 * There are various ways we can get here when we already have an IP, such as when other network conditions
David@0
   506
		 * need to be negotiated at the router-level before we're actually connected.  In such a case, IPs aren't going to
David@0
   507
		 * change... so we'll check one more time, 10 seconds from the last time we get an unconfigured host call,
David@0
   508
		 * for connectivity.
David@0
   509
		 */
David@0
   510
		[NSObject cancelPreviousPerformRequestsWithTarget:self
David@0
   511
												 selector:@selector(queryUnconfiguredHosts)
David@0
   512
												   object:nil];
David@0
   513
		[self performSelector:@selector(queryUnconfiguredHosts)
David@0
   514
				   withObject:nil
David@0
   515
				   afterDelay:10.0];
David@0
   516
	}
David@0
   517
}
David@0
   518
David@0
   519
/*
David@0
   520
 * @brief Remove an unconfigured host and observer from unconfiguredHostsAndObservers
David@0
   521
 *
David@0
   522
 * Must be called with the hostAndObserverListLock already obtained.
David@0
   523
 */
David@0
   524
- (void)removeUnconfiguredHost:(NSString *)host observer:(id)observer
David@0
   525
{
David@0
   526
	[unconfiguredHostsAndObservers removeObject:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
   527
		observer, @"observer",
David@0
   528
		host, @"host",
David@0
   529
		nil]];
David@0
   530
David@0
   531
	if ([unconfiguredHostsAndObservers count] == 0) {
David@0
   532
		[self stopMonitoringIPChanges];
David@0
   533
	}
David@0
   534
}
David@0
   535
David@0
   536
/*
David@0
   537
 * @brief Attempt to set up reachability monitoring for all currently unconfigured hosts
David@0
   538
 *
David@0
   539
 * Called by localIPsChangedCallback() in response to a change in the local IP list.
David@0
   540
 *
David@0
   541
 * Also called 5 seconds after one or more unconfigured hosts are added to verify they are actually unreachable.
David@0
   542
 *
David@0
   543
 * If we are able to schedule reachability monitoring for a given host, its dictionary in unconfiguredHostsAndObservers
David@0
   544
 * will be removed.
David@0
   545
 */
David@0
   546
- (void)queryUnconfiguredHosts
David@0
   547
{
David@0
   548
	if ([unconfiguredHostsAndObservers count]) {
David@0
   549
		NSEnumerator	*enumerator;
David@0
   550
		NSDictionary	*unconfiguredDict;
David@0
   551
David@0
   552
		[hostAndObserverListLock lock];
David@0
   553
		enumerator = [unconfiguredHostsAndObservers objectEnumerator];
David@0
   554
		while ((unconfiguredDict = [enumerator nextObject])) {
David@0
   555
			[self scheduleReachabilityMonitoringForHost:[unconfiguredDict objectForKey:@"host"]
David@0
   556
											   observer:[unconfiguredDict objectForKey:@"observer"]];
David@0
   557
		}
David@0
   558
		[hostAndObserverListLock unlock];
David@0
   559
	}
David@0
   560
}
David@0
   561
David@0
   562
/*
David@0
   563
 * @brief The local IP list changed (SCDynamicStore)
David@0
   564
 *
David@0
   565
 * @param info The AIHostReachabilityMonitor which set up the callback
David@0
   566
 */
David@0
   567
static void localIPsChangedCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
David@0
   568
{
David@0
   569
	AIHostReachabilityMonitor	*self = info;
David@0
   570
	
David@0
   571
	/* Wait one second after receiving the callback, as it seems to be sent in some cases the middle of the change
David@0
   572
	 * rather than after it is complete.
David@0
   573
	 */
David@0
   574
	[self performSelector:@selector(queryUnconfiguredHosts)
David@0
   575
			   withObject:nil
David@0
   576
			   afterDelay:1.0];	
David@0
   577
}
David@0
   578
David@0
   579
static OSStatus CreateIPAddressListChangeCallbackSCF(SCDynamicStoreCallBack callback, void *contextPtr, 
David@0
   580
													 SCDynamicStoreRef *storeRef, CFRunLoopSourceRef *sourceRef);
David@0
   581
David@0
   582
/*
David@0
   583
 * @brief Monitor when our local IP list changes, which generally indicates a possible change in network connectivity
David@0
   584
 */
David@0
   585
- (void)beginMonitorngIPChanges
David@0
   586
{
David@0
   587
	if (!ipChangesRunLoopSourceRef) {		
David@0
   588
		SCDynamicStoreRef	storeRef = nil;
David@0
   589
		OSStatus			status;
David@0
   590
		
David@0
   591
		//Create the CFRunLoopSourceRef we will want to add to our run loop to have
David@0
   592
		//localIPsChangedCallback() called when the IP list changes
David@0
   593
		status = CreateIPAddressListChangeCallbackSCF(localIPsChangedCallback, 
David@0
   594
													  self,
David@0
   595
													  &storeRef,
David@0
   596
													  &ipChangesRunLoopSourceRef);
David@0
   597
		
David@0
   598
		//Add it to the run loop so we will receive the notifications
David@0
   599
		if((status == noErr) && ipChangesRunLoopSourceRef){
David@0
   600
			CFRunLoopAddSource(CFRunLoopGetCurrent(),
David@0
   601
							   ipChangesRunLoopSourceRef,
David@0
   602
							   kCFRunLoopDefaultMode);
David@0
   603
		}
David@0
   604
		
David@0
   605
		CFRelease(storeRef);
David@0
   606
	}
David@0
   607
}
David@0
   608
David@0
   609
/*
David@0
   610
 * @brief Stop monitoring changes to our local IP list
David@0
   611
 */
David@0
   612
- (void)stopMonitoringIPChanges
David@0
   613
{
David@0
   614
	if (ipChangesRunLoopSourceRef) {
David@0
   615
		CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
David@0
   616
							  ipChangesRunLoopSourceRef,
David@0
   617
							  kCFRunLoopDefaultMode);
David@0
   618
		CFRelease(ipChangesRunLoopSourceRef);
David@0
   619
		ipChangesRunLoopSourceRef = nil;
David@0
   620
	}
David@0
   621
}
David@0
   622
David@0
   623
#pragma mark -
David@0
   624
#pragma mark Sleep and Wake
David@0
   625
David@0
   626
/*!
David@0
   627
 * @brief System is waking from sleep
David@0
   628
 *
David@0
   629
 * When the system wakes, manually reconfigure reachability checking as not all network configurations will report a change.
David@0
   630
 */
David@0
   631
- (void)systemDidWake:(NSNotification *)notification
David@0
   632
{
David@0
   633
	[hostAndObserverListLock lock];
David@0
   634
David@0
   635
	NSArray	*oldHosts = [hosts copy];
David@0
   636
	NSArray	*oldObservers = [observers copy];
David@0
   637
	
David@0
   638
	NSEnumerator				*enumerator;
David@0
   639
	SCNetworkReachabilityRef	reachabilityRef;
David@0
   640
	enumerator = [reachabilities objectEnumerator];
David@0
   641
	while ((reachabilityRef = (SCNetworkReachabilityRef)[enumerator nextObject])) {
David@0
   642
		SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef,
David@0
   643
												   CFRunLoopGetCurrent(),
David@0
   644
												   kCFRunLoopDefaultMode);
David@0
   645
	}
David@0
   646
	
David@0
   647
	[hosts removeAllObjects];
David@0
   648
	[observers removeAllObjects];
David@0
   649
	[reachabilities removeAllObjects];
David@0
   650
	
David@0
   651
	[hostAndObserverListLock unlock];
David@0
   652
David@0
   653
	unsigned numObservers = [oldObservers count];
David@0
   654
	for (unsigned i = 0; i < numObservers; i++) {
David@0
   655
		NSString						*host = [oldHosts objectAtIndex:i];
David@0
   656
		id<AIHostReachabilityObserver>	observer = [oldObservers objectAtIndex:i];
David@0
   657
David@0
   658
		[self addObserver:observer
David@0
   659
				  forHost:host];
David@0
   660
	}
David@0
   661
	
David@0
   662
	[oldHosts release];
David@0
   663
	[oldObservers release];
David@0
   664
}
David@0
   665
David@0
   666
#pragma mark -
David@0
   667
#pragma mark CreateIPAddressListChangeCallbackSCF
David@0
   668
David@0
   669
/*CreateIPAddressListChangeCallbackSCF() and its supporting functions are from
David@0
   670
*	Apple's "Living in a Dynamic TCP/IP Environment, available at
David@0
   671
*	http://developer.apple.com/technotes/tn/tn1145.html
David@0
   672
*/
David@0
   673
David@0
   674
//Error Handling  ------------------------------------------------------------------------------------------------------
David@0
   675
// Error Handling
David@0
   676
// --------------
David@0
   677
// SCF returns errors in two ways:
David@0
   678
//
David@0
   679
// o The function result is usually set to something
David@0
   680
//   generic (like NULL or false) to indicate an error.
David@0
   681
//
David@0
   682
// o There is a call, SCError, that returns the error
David@0
   683
//   code for the most recent function.  These error codes
David@0
   684
//   are not in the OSStatus domain.
David@0
   685
//
David@0
   686
// We deal with this using two functions, MoreSCError
David@0
   687
// and MoreSCErrorBoolean.  Both of these take a generic
David@0
   688
// failure indicator (a pointer or a Boolean) and, if
David@0
   689
// that indicates an error, they call SCError to get the
David@0
   690
// real error code.  They also act as a bottleneck for
David@0
   691
// mapping SC errors into the OSStatus domain, although
David@0
   692
// I don't do that in this simple implementation.
David@0
   693
//
David@0
   694
// Note that I could have eliminated the failure indicator
David@0
   695
// parameter and just called SCError but I'm worried
David@0
   696
// about SCF returning an error indicator without setting
David@0
   697
// the SCError.  There's no justification for this worry
David@0
   698
// other than general paranoia (I know of no examples where
David@0
   699
// this happens),
David@0
   700
David@0
   701
static OSStatus MoreSCErrorBoolean(Boolean success)
David@0
   702
{
David@0
   703
    OSStatus err;
David@0
   704
    int scErr;
David@0
   705
	
David@0
   706
    err = noErr;
David@0
   707
    if ( ! success ) {
David@0
   708
        scErr = SCError();
David@0
   709
        if (scErr == kSCStatusOK) {
David@0
   710
            scErr = kSCStatusFailed;
David@0
   711
        }
David@0
   712
        // Return an SCF error directly as an OSStatus.
David@0
   713
        // That's a little cheesy.  In a real program
David@0
   714
        // you might want to do some mapping from SCF
David@0
   715
        // errors to a range within the OSStatus range.
David@0
   716
        err = scErr;
David@0
   717
    }
David@0
   718
    return err;
David@0
   719
}
David@0
   720
David@0
   721
static OSStatus MoreSCError(const void *value)
David@0
   722
{
David@0
   723
    return MoreSCErrorBoolean(value != NULL);
David@0
   724
}
David@0
   725
David@0
   726
static OSStatus CFQError(CFTypeRef cf)
David@0
   727
// Maps Core Foundation error indications (such as they
David@0
   728
// are) to the OSStatus domain.
David@0
   729
{
David@0
   730
    OSStatus err;
David@0
   731
	
David@0
   732
    err = noErr;
David@0
   733
    if (cf == NULL) {
David@0
   734
        err = coreFoundationUnknownErr;
David@0
   735
    }
David@0
   736
    return err;
David@0
   737
}
David@0
   738
David@0
   739
//CreateIPAddressListChangeCallbackSCF ----------------------------------------------------------------------------------
David@0
   740
David@0
   741
static OSStatus CreateIPAddressListChangeCallbackSCF(SCDynamicStoreCallBack callback,
David@0
   742
													 void *contextPtr,
David@0
   743
													 SCDynamicStoreRef *storeRef,
David@0
   744
													 CFRunLoopSourceRef *sourceRef)
David@0
   745
// Create a SCF dynamic store reference and a
David@0
   746
// corresponding CFRunLoop source.  If you add the
David@0
   747
// run loop source to your run loop then the supplied
David@0
   748
// callback function will be called when local IP
David@0
   749
// address list changes.
David@0
   750
{
David@0
   751
    OSStatus                err;
David@0
   752
    SCDynamicStoreContext   context = {0, NULL, NULL, NULL, NULL};
David@0
   753
    SCDynamicStoreRef       ref;
David@0
   754
    CFStringRef             pattern;
David@0
   755
    CFArrayRef              patternList;
David@0
   756
    CFRunLoopSourceRef      rls;
David@0
   757
	
David@0
   758
    assert(callback   != NULL);
David@0
   759
    assert( storeRef  != NULL);
David@0
   760
    assert(*storeRef  == NULL);
David@0
   761
    assert( sourceRef != NULL);
David@0
   762
    assert(*sourceRef == NULL);
David@0
   763
	
David@0
   764
    ref = NULL;
David@0
   765
    pattern = NULL;
David@0
   766
    patternList = NULL;
David@0
   767
    rls = NULL;
David@0
   768
	
David@0
   769
    // Create a connection to the dynamic store, then create
David@0
   770
    // a search pattern that finds all IPv4 entities.
David@0
   771
    // The pattern is "State:/Network/Service/[^/]+/IPv4".
David@0
   772
	
David@0
   773
    context.info = contextPtr;
David@0
   774
    ref = SCDynamicStoreCreate( NULL,
David@0
   775
                                CFSTR("AddIPAddressListChangeCallbackSCF"),
David@0
   776
                                callback,
David@0
   777
                                &context);
David@0
   778
    err = MoreSCError(ref);
David@0
   779
    if (err == noErr) {
David@0
   780
        pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(
David@0
   781
															  NULL,
David@0
   782
															  kSCDynamicStoreDomainState,
David@0
   783
															  kSCCompAnyRegex,
David@0
   784
															  kSCEntNetIPv4);
David@0
   785
        err = MoreSCError(pattern);
David@0
   786
    }
David@0
   787
	
David@0
   788
    // Create a pattern list containing just one pattern,
David@0
   789
    // then tell SCF that we want to watch changes in keys
David@0
   790
    // that match that pattern list, then create our run loop
David@0
   791
    // source.
David@0
   792
	
David@0
   793
    if (err == noErr) {
David@0
   794
        patternList = CFArrayCreate(NULL,
David@0
   795
                                    (const void **) &pattern, 1,
David@0
   796
                                    &kCFTypeArrayCallBacks);
David@0
   797
        err = CFQError(patternList);
David@0
   798
    }
David@0
   799
    if (err == noErr) {
David@0
   800
        err = MoreSCErrorBoolean(
David@0
   801
								 SCDynamicStoreSetNotificationKeys(
David@0
   802
																   ref,
David@0
   803
																   NULL,
David@0
   804
																   patternList)
David@0
   805
								 );
David@0
   806
    }
David@0
   807
    if (err == noErr) {
David@0
   808
        rls = SCDynamicStoreCreateRunLoopSource(NULL, ref, 0);
David@0
   809
        err = MoreSCError(rls);
David@0
   810
    }
David@0
   811
	
David@0
   812
    // Clean up.
David@0
   813
	
David@0
   814
    if (pattern) CFRelease(pattern);
David@0
   815
    if (patternList) CFRelease(patternList);
David@0
   816
    if (err != noErr) {
David@0
   817
        if (ref) {
David@0
   818
			CFRelease(ref);
David@0
   819
			ref = NULL;
David@0
   820
		}
David@0
   821
    }
David@0
   822
    *storeRef = ref;
David@0
   823
    *sourceRef = rls;
David@0
   824
	
David@0
   825
    assert( (err == noErr) == (*storeRef  != NULL) );
David@0
   826
    assert( (err == noErr) == (*sourceRef != NULL) );
David@0
   827
	
David@0
   828
    return err;
David@0
   829
}
David@0
   830
David@0
   831
@end