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