/* * Copyright (c) 2003-2014 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Modification History * * April 12, 2011 Allan Nathanson * - add SCNetworkReachability "server" * * March 31, 2004 Allan Nathanson * - use [SC] DNS configuration information * * January 19, 2003 Allan Nathanson * - add advanced reachability APIs */ #include #include #include #include #include #include #include #include #include #include #include #include #include #if !TARGET_OS_IPHONE #include #endif // !TARGET_OS_IPHONE #include #include #include #include #include #include #include #include #include #include #include #include #define KERNEL_PRIVATE #include #undef KERNEL_PRIVATE #ifndef s6_addr16 #define s6_addr16 __u6_addr.__u6_addr16 #endif #include "SCNetworkConnectionInternal.h" #include "SCNetworkReachabilityInternal.h" #include #include #include #define DEBUG_REACHABILITY_TYPE_NAME "create w/name" #define DEBUG_REACHABILITY_TYPE_NAME_CLONE " > clone" #define DEBUG_REACHABILITY_TYPE_NAME_OPTIONS " + options" #define DEBUG_REACHABILITY_TYPE_ADDRESS "create w/address" #define DEBUG_REACHABILITY_TYPE_ADDRESS_CLONE " > clone" #define DEBUG_REACHABILITY_TYPE_ADDRESS_OPTIONS " + options" #define DEBUG_REACHABILITY_TYPE_ADDRESSPAIR "create w/address pair" #define DEBUG_REACHABILITY_TYPE_ADDRESSPAIR_CLONE " > clone" #define DEBUG_REACHABILITY_TYPE_ADDRESSPAIR_OPTIONS " + options" #define DEBUG_REACHABILITY_TYPE_PTR "create w/ptr" #define DEBUG_REACHABILITY_TYPE_PTR_CLONE " > clone" #define DEBUG_REACHABILITY_TYPE_PTR_OPTIONS " + options" #define DNS_FLAGS_FORMAT "[%s%s%s%s%s]" #define DNS_FLAGS_VALUES(t) t->dnsHaveV4 ? "4" : "", \ t->dnsHaveV6 ? "6" : "", \ t->dnsHavePTR ? "P" : "", \ t->dnsHaveTimeout ? "T" : "", \ t->dnsHaveError ? "E" : "" static pthread_mutexattr_t lock_attr; #define MUTEX_INIT(m) { \ int _lock_ = (pthread_mutex_init(m, &lock_attr) == 0); \ assert(_lock_); \ } #define MUTEX_LOCK(m) { \ int _lock_ = (pthread_mutex_lock(m) == 0); \ assert(_lock_); \ } #define MUTEX_UNLOCK(m) { \ int _unlock_ = (pthread_mutex_unlock(m) == 0); \ assert(_unlock_); \ } #define MUTEX_ASSERT_HELD(m) { \ int _locked_ = (pthread_mutex_lock(m) == EDEADLK); \ assert(_locked_); \ } #define SCNETWORKREACHABILITY_TRIGGER_KEY CFSTR("com.apple.SCNetworkReachability:FORCE-CHANGE") #define N_QUICK 64 static CFStringRef __SCNetworkReachabilityCopyDescription (CFTypeRef cf); static void __SCNetworkReachabilityDeallocate (CFTypeRef cf); static void reachPerform (void *info); static Boolean reachUpdate (SCNetworkReachabilityRef target); static void __SCNetworkReachabilityHandleChanges (SCDynamicStoreRef store, CFArrayRef changedKeys, void *info); static Boolean __SCNetworkReachabilityScheduleWithRunLoop (SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode, dispatch_queue_t queue, Boolean onDemand); static Boolean __SCNetworkReachabilityUnscheduleFromRunLoop (SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode, Boolean onDemand); static CFTypeID __kSCNetworkReachabilityTypeID = _kCFRuntimeNotATypeID; static const CFRuntimeClass __SCNetworkReachabilityClass = { 0, // version "SCNetworkReachability", // className NULL, // init NULL, // copy __SCNetworkReachabilityDeallocate, // dealloc NULL, // equal NULL, // hash NULL, // copyFormattingDesc __SCNetworkReachabilityCopyDescription // copyDebugDesc }; static pthread_once_t initialized = PTHREAD_ONCE_INIT; static const ReachabilityInfo NOT_REACHABLE = { 0, 0, 0, { 0 }, FALSE }; static const ReachabilityInfo NOT_REPORTED = { 0, 0xFFFFFFFF, 0, { 0 }, FALSE }; static int rtm_seq = 0; static const struct timeval TIME_ZERO = { 0, 0 }; static int dnsCount = 0; static int dnsGeneration = 0; static DNSServiceRef dnsMain = NULL; static CFMutableSetRef dnsUpdated = NULL; static Boolean D_serverBypass = FALSE; #if !TARGET_OS_IPHONE /* * Power capabilities (sleep/wake) */ #define POWER_CAPABILITIES_NETWORK ( kIOPMCapabilityCPU \ | kIOPMCapabilityNetwork \ | kIOPMCapabilityVideo) static IOPMSystemPowerStateCapabilities power_capabilities = POWER_CAPABILITIES_NETWORK; #endif // !TARGET_OS_IPHONE /* * host "something has changed" notifications */ // Note: protected by _hn_target_queue() static SCDynamicStoreRef hn_store = NULL; static CFMutableSetRef hn_targets = NULL; static dispatch_queue_t _hn_changes_queue() { static dispatch_once_t once; static dispatch_queue_t q = NULL; dispatch_once(&once, ^{ q = dispatch_queue_create("SCNetworkReachability.handleChanges", NULL); }); return q; } static dispatch_queue_t _hn_target_queue() { static dispatch_once_t once; static dispatch_queue_t q; dispatch_once(&once, ^{ q = dispatch_queue_create("SCNetworkReachability.targetManagement", NULL); }); return q; } /* * DNS configuration */ typedef struct { dns_config_t *config; int refs; } dns_configuration_t; // Note: protected by "dns_lock" static pthread_mutex_t dns_lock = PTHREAD_MUTEX_INITIALIZER; static dns_configuration_t *dns_configuration = NULL; static int dns_token; static Boolean dns_token_valid = FALSE; typedef enum { dns_query_async, dns_query_mdns, dns_query_mdns_timeout, } query_type; static void __mark_operation_start(struct timeval *queryStart, struct timeval *queryEnd) { (void) gettimeofday(queryStart, NULL); *queryEnd = TIME_ZERO; return; } static void __mark_operation_end(SCNetworkReachabilityRef target, Boolean found, query_type query_type, struct timeval *queryStart, struct timeval *queryEnd) { struct timeval queryElapsed; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; (void) gettimeofday(queryEnd, NULL); if (!_sc_debug && (query_type != dns_query_mdns_timeout)) { return; } if (!timerisset(queryStart)) { return; } timersub(queryEnd, queryStart, &queryElapsed); switch (query_type) { #define QUERY_TIME__FMT "%ld.%6.6d" #define QUERY_TIME__DIV 1 case dns_query_async : SCLog(TRUE, LOG_INFO, CFSTR("%sasync DNS complete%s (query time = " QUERY_TIME__FMT ")"), targetPrivate->log_prefix, found ? "" : ", host not found", queryElapsed.tv_sec, queryElapsed.tv_usec / QUERY_TIME__DIV); break; case dns_query_mdns : SCLog(TRUE, LOG_INFO, CFSTR("%s[m]DNS query complete%s (query time = " QUERY_TIME__FMT "), " DNS_FLAGS_FORMAT), targetPrivate->log_prefix, found ? "" : ", host not found", queryElapsed.tv_sec, queryElapsed.tv_usec / QUERY_TIME__DIV, DNS_FLAGS_VALUES(targetPrivate)); break; case dns_query_mdns_timeout : SCLog(TRUE, LOG_ERR, CFSTR("%s[m]DNS query timeout (query time = " QUERY_TIME__FMT "), " DNS_FLAGS_FORMAT), targetPrivate->log_prefix, queryElapsed.tv_sec, queryElapsed.tv_usec / QUERY_TIME__DIV, DNS_FLAGS_VALUES(targetPrivate)); break; } return; } static __inline__ Boolean __reach_changed(ReachabilityInfo *r1, ReachabilityInfo *r2) { if (r1->flags != r2->flags) { // if the reachability flags changed return TRUE; } if (r1->if_index != r2->if_index) { // if the target interface changed return TRUE; } if ((r1->sleeping != r2->sleeping) && !r2->sleeping) { // if our sleep/wake status changed and if we // are no longer sleeping return TRUE; } return FALSE; } static __inline__ void _reach_set(ReachabilityInfo *dst, const ReachabilityInfo *src, uint64_t cycle, unsigned int requested_if_index, const char *requested_if_name) { memcpy(dst, src, sizeof(ReachabilityInfo)); dst->cycle = cycle; if (!(dst->flags & kSCNetworkReachabilityFlagsReachable) || (dst->flags & kSCNetworkReachabilityFlagsConnectionRequired)) { // if not reachable or connection required, return the // requested if_index and if_name. dst->if_index = requested_if_index; if (requested_if_name != NULL) { strlcpy(dst->if_name, requested_if_name, sizeof(dst->if_name)); } else { dst->if_name[0] = '\0'; } } return; } #pragma mark - #pragma mark SCDynamicStore info typedef struct { SCDynamicStoreRef store; CFStringRef entity; CFDictionaryRef dict; CFIndex n; const void ** keys; const void * keys_q[N_QUICK]; const void ** values; const void * values_q[N_QUICK]; } ReachabilityStoreInfo, *ReachabilityStoreInfoRef; static ReachabilityStoreInfo S_storeInfo = { 0 }; static Boolean S_storeInfoActive = FALSE; static dispatch_queue_t _storeInfo_queue() { static dispatch_once_t once; static dispatch_queue_t q; dispatch_once(&once, ^{ q = dispatch_queue_create("SCNetworkReachability.storeInfo", NULL); }); return q; } static void ReachabilityStoreInfo_copy(ReachabilityStoreInfoRef src, ReachabilityStoreInfoRef dst) { if (src->dict != NULL) { dst->store = src->store; CFRetain(dst->store); dst->dict = src->dict; CFRetain(dst->dict); dst->n = src->n; if (dst->n > 0) { if (dst->n <= (CFIndex)(sizeof(dst->keys_q) / sizeof(CFTypeRef))) { dst->keys = dst->keys_q; dst->values = dst->values_q; } else { dst->keys = CFAllocatorAllocate(NULL, dst->n * sizeof(CFTypeRef), 0); dst->values = CFAllocatorAllocate(NULL, dst->n * sizeof(CFTypeRef), 0); } memcpy(dst->keys, src->keys, dst->n * sizeof(CFTypeRef)); memcpy(dst->values, src->values, dst->n * sizeof(CFTypeRef)); } } return; } static void ReachabilityStoreInfo_enable(Boolean enable) { dispatch_sync(_storeInfo_queue(), ^{ S_storeInfoActive = enable; }); return; } static void ReachabilityStoreInfo_free(ReachabilityStoreInfoRef store_info) { if ((store_info->n > 0) && (store_info->keys != store_info->keys_q)) { CFAllocatorDeallocate(NULL, store_info->keys); store_info->keys = NULL; CFAllocatorDeallocate(NULL, store_info->values); store_info->values = NULL; } store_info->n = 0; if (store_info->dict != NULL) { CFRelease(store_info->dict); store_info->dict = NULL; } if (store_info->store != NULL) { CFRelease(store_info->store); store_info->store = NULL; } return; } static void ReachabilityStoreInfo_init(ReachabilityStoreInfoRef store_info) { dispatch_sync(_storeInfo_queue(), ^{ bzero(store_info, sizeof(ReachabilityStoreInfo)); if (S_storeInfoActive && (S_storeInfo.dict != NULL)) { ReachabilityStoreInfo_copy(&S_storeInfo, store_info); } }); return; } static void ReachabilityStoreInfo_save(ReachabilityStoreInfoRef store_info) { dispatch_sync(_storeInfo_queue(), ^{ if ((store_info == NULL) || !_SC_CFEqual(store_info->dict, S_storeInfo.dict)) { // free any old info ReachabilityStoreInfo_free(&S_storeInfo); // save new info if (S_storeInfoActive && (store_info != NULL) && (store_info->dict != NULL)) { ReachabilityStoreInfo_copy(store_info, &S_storeInfo); } } }); return; } static void ReachabilityStoreInfo_keys(CFMutableArrayRef *fill_keys, CFMutableArrayRef *fill_patterns) { CFStringRef key; CFMutableArrayRef keys; CFStringRef pattern; CFMutableArrayRef patterns; keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); // get info for IPv4 services key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4); CFArrayAppendValue(keys, key); CFRelease(key); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetIPv4); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); // get info for IPv6 services key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6); CFArrayAppendValue(keys, key); CFRelease(key); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetIPv6); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv6); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); // get info for PPP services pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetPPP); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetPPP); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); // get info for VPN services pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetVPN); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetVPN); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); // get info for IPSec services // pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, // kSCDynamicStoreDomainSetup, // kSCCompAnyRegex, // kSCEntNetIPSec); // CFArrayAppendValue(patterns, pattern); // CFRelease(pattern); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPSec); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); // get info to identify "available" services pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetInterface); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); *fill_keys = keys; *fill_patterns = patterns; return; } static Boolean ReachabilityStoreInfo_fill(ReachabilityStoreInfoRef store_info) { CFMutableArrayRef keys; CFMutableArrayRef patterns; // get the SCDynamicStore info ReachabilityStoreInfo_keys(&keys, &patterns); store_info->dict = SCDynamicStoreCopyMultiple(store_info->store, keys, patterns); CFRelease(keys); CFRelease(patterns); if (store_info->dict == NULL) { return FALSE; } // and extract the keys/values for post-processing store_info->n = CFDictionaryGetCount(store_info->dict); if (store_info->n > 0) { if (store_info->n <= (CFIndex)(sizeof(store_info->keys_q) / sizeof(CFTypeRef))) { store_info->keys = store_info->keys_q; store_info->values = store_info->values_q; } else { store_info->keys = CFAllocatorAllocate(NULL, store_info->n * sizeof(CFTypeRef), 0); store_info->values = CFAllocatorAllocate(NULL, store_info->n * sizeof(CFTypeRef), 0); } CFDictionaryGetKeysAndValues(store_info->dict, store_info->keys, store_info->values); } return TRUE; } static Boolean ReachabilityStoreInfo_update(ReachabilityStoreInfoRef store_info, SCDynamicStoreRef *storeP, sa_family_t sa_family) { __block Boolean ok = TRUE; switch (sa_family) { case AF_UNSPEC : store_info->entity = NULL; break; case AF_INET : store_info->entity = kSCEntNetIPv4; break; case AF_INET6 : store_info->entity = kSCEntNetIPv6; break; default : return FALSE; } if (store_info->dict != NULL) { // if info already available return TRUE; } dispatch_sync(_storeInfo_queue(), ^{ if (S_storeInfoActive && (S_storeInfo.dict != NULL)) { // free any info ReachabilityStoreInfo_free(store_info); // copy the shared/available info ReachabilityStoreInfo_copy(&S_storeInfo, store_info); } if (store_info->store == NULL) { store_info->store = (storeP != NULL) ? *storeP : NULL; if (store_info->store != NULL) { // keep a reference to the passed in SCDynamicStore CFRetain(store_info->store); } else { store_info->store = SCDynamicStoreCreate(NULL, CFSTR("SCNetworkReachability"), NULL, NULL); if (store_info->store == NULL) { SCLog(TRUE, LOG_ERR, CFSTR("ReachabilityStoreInfo_update SCDynamicStoreCreate() failed")); return; } if (storeP != NULL) { // and pass back a reference *storeP = store_info->store; CFRetain(*storeP); } } } if (sa_family == AF_UNSPEC) { // if the address family was not specified than // all we wanted, for now, was to establish the // SCDynamicStore session return; } if (store_info->dict != NULL) { // or we have picked up the shared info return; } ok = ReachabilityStoreInfo_fill(store_info); if (!ok) { return; } if (!_SC_CFEqual(store_info->dict, S_storeInfo.dict)) { // free any old info ReachabilityStoreInfo_free(&S_storeInfo); // save new info if (S_storeInfoActive && (store_info->dict != NULL)) { ReachabilityStoreInfo_copy(store_info, &S_storeInfo); } } }); return ok; } #pragma mark - #pragma mark Reachability engine #define ROUNDUP(a, size) \ (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a)) #define NEXT_SA(ap) (ap) = (struct sockaddr *) \ ((caddr_t)(ap) + ((ap)->sa_len ? ROUNDUP((ap)->sa_len,\ sizeof(uint32_t)) :\ sizeof(uint32_t))) static void get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info) { int i; for (i = 0; i < RTAX_MAX; i++) { if (addrs & (1 << i)) { rti_info[i] = sa; NEXT_SA(sa); } else rti_info[i] = NULL; } } #define BUFLEN (sizeof(struct rt_msghdr) + 512) /* 8 * sizeof(struct sockaddr_in6) = 192 */ typedef struct { union { char bytes[BUFLEN]; struct rt_msghdr rtm; } buf; int error; struct sockaddr *rti_info[RTAX_MAX]; struct rt_msghdr *rtm; struct sockaddr_dl *sdl; } route_info, *route_info_p; /* * route_get() * returns zero if route exists and data returned, EHOSTUNREACH * if no route, or errno for any other error. */ static int route_get(const struct sockaddr *address, unsigned int if_index, route_info *info) { int n; int opt; pid_t pid = getpid(); int rsock; struct sockaddr *sa; int32_t seq = OSAtomicIncrement32Barrier(&rtm_seq); #ifndef RTM_GET_SILENT #warning Note: Using RTM_GET (and not RTM_GET_SILENT) static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; int sosize = 48 * 1024; #endif bzero(info, sizeof(*info)); info->rtm = &info->buf.rtm; info->rtm->rtm_msglen = sizeof(struct rt_msghdr); info->rtm->rtm_version = RTM_VERSION; #ifdef RTM_GET_SILENT info->rtm->rtm_type = RTM_GET_SILENT; #else info->rtm->rtm_type = RTM_GET; #endif info->rtm->rtm_flags = RTF_STATIC|RTF_UP|RTF_HOST|RTF_GATEWAY; info->rtm->rtm_addrs = RTA_DST|RTA_IFP; /* Both destination and device */ info->rtm->rtm_pid = pid; info->rtm->rtm_seq = seq; if (if_index != 0) { info->rtm->rtm_flags |= RTF_IFSCOPE; info->rtm->rtm_index = if_index; } switch (address->sa_family) { case AF_INET6: { struct sockaddr_in6 *sin6; /* ALIGN: caller ensures that the address is aligned */ sin6 = (struct sockaddr_in6 *)(void *)address; if ((IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) || IN6_IS_ADDR_MC_LINKLOCAL(&sin6->sin6_addr)) && (sin6->sin6_scope_id != 0)) { sin6->sin6_addr.s6_addr16[1] = htons(sin6->sin6_scope_id); sin6->sin6_scope_id = 0; } break; } } sa = (struct sockaddr *) (info->rtm + 1); bcopy(address, sa, address->sa_len); n = ROUNDUP(sa->sa_len, sizeof(uint32_t)); info->rtm->rtm_msglen += n; info->sdl = (struct sockaddr_dl *) ((void *)sa + n); info->sdl->sdl_family = AF_LINK; info->sdl->sdl_len = sizeof (struct sockaddr_dl); n = ROUNDUP(info->sdl->sdl_len, sizeof(uint32_t)); info->rtm->rtm_msglen += n; #ifndef RTM_GET_SILENT pthread_mutex_lock(&lock); #endif rsock = socket(PF_ROUTE, SOCK_RAW, PF_ROUTE); if (rsock == -1) { int error = errno; #ifndef RTM_GET_SILENT pthread_mutex_unlock(&lock); #endif SCLog(TRUE, LOG_ERR, CFSTR("socket(PF_ROUTE) failed: %s"), strerror(error)); return error; } opt = 1; if (ioctl(rsock, FIONBIO, &opt) < 0) { int error = errno; (void)close(rsock); #ifndef RTM_GET_SILENT pthread_mutex_unlock(&lock); #endif SCLog(TRUE, LOG_ERR, CFSTR("ioctl(FIONBIO) failed: %s"), strerror(error)); return error; } #ifndef RTM_GET_SILENT if (setsockopt(rsock, SOL_SOCKET, SO_RCVBUF, &sosize, sizeof(sosize)) == -1) { int error = errno; (void)close(rsock); pthread_mutex_unlock(&lock); SCLog(TRUE, LOG_ERR, CFSTR("setsockopt(SO_RCVBUF) failed: %s"), strerror(error)); return error; } #endif if (write(rsock, &info->buf, info->rtm->rtm_msglen) == -1) { int error = errno; (void)close(rsock); #ifndef RTM_GET_SILENT pthread_mutex_unlock(&lock); #endif if (error != ESRCH) { SCLog(TRUE, LOG_ERR, CFSTR("write() failed: %s"), strerror(error)); return error; } return EHOSTUNREACH; } /* * Type, seq, pid identify our response. * Routing sockets are broadcasters on input. */ while (TRUE) { ssize_t n; n = read(rsock, &info->buf, sizeof(info->buf)); if (n == -1) { int error = errno; if (error == EINTR) { continue; } (void)close(rsock); #ifndef RTM_GET_SILENT pthread_mutex_unlock(&lock); #endif SCLog(TRUE, LOG_ERR, CFSTR("SCNetworkReachability: routing socket" " read() failed: %s"), strerror(error)); return error; } if ((info->rtm->rtm_type == RTM_GET) && (info->rtm->rtm_seq == seq) && (info->rtm->rtm_pid == pid)) { break; } } (void)close(rsock); #ifndef RTM_GET_SILENT pthread_mutex_unlock(&lock); #endif get_rtaddrs(info->rtm->rtm_addrs, sa, info->rti_info); //#define LOG_RTADDRS #ifdef LOG_RTADDRS { int i; SCLog(_sc_debug, LOG_DEBUG, CFSTR("rtm_flags = 0x%8.8x"), info->rtm->rtm_flags); if ((info->rti_info[RTAX_NETMASK] != NULL) && (info->rti_info[RTAX_DST] != NULL)) { info->rti_info[RTAX_NETMASK]->sa_family = info->rti_info[RTAX_DST]->sa_family; } for (i = 0; i < RTAX_MAX; i++) { if (info->rti_info[i] != NULL) { char addr[128]; _SC_sockaddr_to_string(info->rti_info[i], addr, sizeof(addr)); SCLog(_sc_debug, LOG_DEBUG, CFSTR("%d: %s"), i, addr); } } } #endif // LOG_RTADDRS if ((info->rti_info[RTAX_IFP] == NULL) || (info->rti_info[RTAX_IFP]->sa_family != AF_LINK)) { /* no interface info */ SCLog(TRUE, LOG_DEBUG, CFSTR("route_get() no interface info")); return EINVAL; } /* ALIGN: accessors are retrieving byte values, cast ok. */ info->sdl = (struct sockaddr_dl *)(void *) info->rti_info[RTAX_IFP]; if ((info->sdl->sdl_nlen == 0) || (info->sdl->sdl_nlen > IFNAMSIZ)) { /* no interface name */ return EHOSTUNREACH; } return 0; } static void log_address(const char *str, const struct sockaddr *sa, unsigned int if_index, const char *log_prefix) { char addr[128]; char if_name[IFNAMSIZ + 1]; _SC_sockaddr_to_string(sa, addr, sizeof(addr)); if ((if_index != 0) && (if_indextoname(if_index, &if_name[1]) != NULL)) { if_name[0] = '%'; } else { if_name[0] = '\0'; } SCLog(TRUE, LOG_INFO, CFSTR("%s%s(%s%s)"), log_prefix, str, addr, if_name); return; } static int checkAddress_route(const struct sockaddr *address, unsigned int if_index, char *if_name, struct ifreq *ifr, ReachabilityInfo *reach_info, route_info *info, int *sc_status, const char *log_prefix) { int isock = -1; int ret = 0; char *statusMessage = NULL; struct sockaddr_in v4mapped; switch (address->sa_family) { case AF_INET : case AF_INET6 : if (_sc_debug) { log_address("checkAddress", address, if_index, log_prefix); } break; default : /* * if no code for this address family (yet) */ SCLog(TRUE, LOG_INFO, CFSTR("checkAddress(): unexpected address family %d"), address->sa_family); *sc_status = kSCStatusInvalidArgument; ret = EPERM; goto done; } if (address->sa_family == AF_INET6) { /* ALIGN: sin6_addr accessed aligned, cast ok. */ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)(void *)address; if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { bzero(&v4mapped, sizeof(v4mapped)); v4mapped.sin_len = sizeof(v4mapped); v4mapped.sin_family = AF_INET; v4mapped.sin_port = sin6->sin6_port; v4mapped.sin_addr.s_addr = sin6->sin6_addr.__u6_addr.__u6_addr32[3]; address = (struct sockaddr *)&v4mapped; } } ret = route_get(address, if_index, info); switch (ret) { case 0 : break; case EHOSTUNREACH : // if no route goto done; default : // if error *sc_status = ret; goto done; } /* get the interface flags */ isock = socket(AF_INET, SOCK_DGRAM, 0); if (isock == -1) { ret = errno; SCLog(TRUE, LOG_ERR, CFSTR("socket() failed: %s"), strerror(errno)); goto done; } bzero(ifr, sizeof(*ifr)); bcopy(info->sdl->sdl_data, ifr->ifr_name, info->sdl->sdl_nlen); if (ioctl(isock, SIOCGIFFLAGS, (char *)ifr) == -1) { ret = errno; SCLog(TRUE, LOG_ERR, CFSTR("ioctl(SIOCGIFFLAGS) failed: %s"), strerror(errno)); goto done; } if (!(ifr->ifr_flags & IFF_UP)) { ret = EHOSTUNREACH; goto done; } statusMessage = "isReachable"; reach_info->flags |= kSCNetworkReachabilityFlagsReachable; if (info->rtm->rtm_flags & RTF_LOCAL) { statusMessage = "isReachable (is a local address)"; reach_info->flags |= kSCNetworkReachabilityFlagsIsLocalAddress; } else if (ifr->ifr_flags & IFF_LOOPBACK) { statusMessage = "isReachable (is loopback network)"; reach_info->flags |= kSCNetworkReachabilityFlagsIsLocalAddress; } else if ((info->rti_info[RTAX_IFA] != NULL) && (info->rti_info[RTAX_IFA]->sa_family != AF_LINK)) { void *addr1 = (void *)address; void *addr2 = (void *)info->rti_info[RTAX_IFA]; size_t len = address->sa_len; if ((address->sa_family != info->rti_info[RTAX_IFA]->sa_family) && (address->sa_len != info->rti_info[RTAX_IFA]->sa_len)) { SCLog(TRUE, LOG_NOTICE, CFSTR("address family/length mismatch: %d/%d != %d/%d"), address->sa_family, address->sa_len, info->rti_info[RTAX_IFA]->sa_family, info->rti_info[RTAX_IFA]->sa_len); goto done; } switch (address->sa_family) { case AF_INET : /* ALIGN: cast ok, because only bcmp is used. */ addr1 = &((struct sockaddr_in *)(void *)address)->sin_addr; addr2 = &((struct sockaddr_in *)(void *)info->rti_info[RTAX_IFA])->sin_addr; len = sizeof(struct in_addr); /* * check if 0.0.0.0 */ /* ALIGN: sin_addr should be aligned, cast ok. */ if (((struct sockaddr_in *)(void *)address)->sin_addr.s_addr == 0) { statusMessage = "isReachable (this host)"; reach_info->flags |= kSCNetworkReachabilityFlagsIsLocalAddress; } break; case AF_INET6 : /* ALIGN: cast ok, because only bcmp is used. */ addr1 = &((struct sockaddr_in6 *)(void *)address)->sin6_addr; addr2 = &((struct sockaddr_in6 *)(void *)info->rti_info[RTAX_IFA])->sin6_addr; len = sizeof(struct in6_addr); break; default : break; } if (bcmp(addr1, addr2, len) == 0) { statusMessage = "isReachable (is interface address)"; reach_info->flags |= kSCNetworkReachabilityFlagsIsLocalAddress; } } if (!(info->rtm->rtm_flags & RTF_GATEWAY) && (info->rti_info[RTAX_GATEWAY] != NULL) && (info->rti_info[RTAX_GATEWAY]->sa_family == AF_LINK) && !(ifr->ifr_flags & IFF_POINTOPOINT)) { reach_info->flags |= kSCNetworkReachabilityFlagsIsDirect; } bzero(if_name, IFNAMSIZ); bcopy(info->sdl->sdl_data, if_name, (info->sdl->sdl_nlen <= IFNAMSIZ) ? info->sdl->sdl_nlen : IFNAMSIZ); strlcpy(reach_info->if_name, if_name, sizeof(reach_info->if_name)); reach_info->if_index = info->sdl->sdl_index; if (_sc_debug) { SCLog(TRUE, LOG_INFO, CFSTR("%s status = %s"), log_prefix, statusMessage); SCLog(TRUE, LOG_INFO, CFSTR("%s device = %s (%hu)"), log_prefix, if_name, info->sdl->sdl_index); SCLog(TRUE, LOG_INFO, CFSTR("%s sdl_type = 0x%x"), log_prefix, info->sdl->sdl_type); SCLog(TRUE, LOG_INFO, CFSTR("%s ifr_flags = 0x%04hx"), log_prefix, ifr->ifr_flags); SCLog(TRUE, LOG_INFO, CFSTR("%s rtm_flags = 0x%08x"), log_prefix, info->rtm->rtm_flags); } done : if (isock != -1) (void)close(isock); return ret; } static Boolean checkAddress(ReachabilityStoreInfoRef store_info, const struct sockaddr *address, unsigned int if_index, ReachabilityInfo *reach_info, const char *log_prefix) { route_info info; struct ifreq ifr; char if_name[IFNAMSIZ]; nwi_ifstate_t ifstate; nwi_state_t nwi_state; int ret; int sc_status = kSCStatusReachabilityUnknown; _reach_set(reach_info, &NOT_REACHABLE, reach_info->cycle, if_index, NULL); nwi_state = nwi_state_copy(); if (address != NULL) { ret = checkAddress_route(address, if_index, if_name, &ifr, reach_info, &info, &sc_status, log_prefix); } else { /* special case: check only for available paths off the system */ ret = EHOSTUNREACH; } if (ret == 0) { const struct sockaddr *vpn_server_address; sc_status = kSCStatusOK; ifstate = nwi_state_get_ifstate(nwi_state, if_name); if (ifstate == NULL) { goto done; } reach_info->flags |= nwi_ifstate_get_reachability_flags(ifstate); vpn_server_address = nwi_ifstate_get_vpn_server(ifstate); if (vpn_server_address != NULL) { char dst_if_name[IFNAMSIZ]; route_info dst_info; ret = route_get(vpn_server_address, 0, &dst_info); if (ret != 0) { goto done; } bzero(&dst_if_name, sizeof(dst_if_name)); bcopy(dst_info.sdl->sdl_data, dst_if_name, (dst_info.sdl->sdl_nlen <= IFNAMSIZ) ? dst_info.sdl->sdl_nlen : IFNAMSIZ); if (bcmp(if_name, dst_if_name, sizeof(if_name)) != 0) { nwi_ifstate_t ifstate; ifstate = nwi_state_get_ifstate(nwi_state, dst_if_name); if (ifstate != NULL) { reach_info->flags |= nwi_ifstate_get_reachability_flags(ifstate); } } } } else if (ret == EHOSTUNREACH) { if (if_index == 0) { int af; // if not "scoped" request af = (address != NULL) ? address->sa_family : AF_UNSPEC; reach_info->flags |= nwi_state_get_reachability_flags(nwi_state, af); sc_status = kSCStatusOK; } else { // if "scoped" request sc_status = kSCStatusNoKey; } } done: if (reach_info->flags == 0) { SCLog(_sc_debug, LOG_INFO, CFSTR("%s cannot be reached"), log_prefix); } if (nwi_state != NULL) { nwi_state_release(nwi_state); } if ((sc_status != kSCStatusOK) && (sc_status != kSCStatusNoKey)) { _SCErrorSet(sc_status); return FALSE; } return TRUE; } #pragma mark - #pragma mark SCNetworkReachability APIs static __inline__ CFTypeRef isA_SCNetworkReachability(CFTypeRef obj) { return (isA_CFType(obj, SCNetworkReachabilityGetTypeID())); } static Boolean addr_to_PTR_name(const struct sockaddr *sa, char *name, size_t name_len) { int n; switch (sa->sa_family) { case AF_INET : { union { in_addr_t s_addr; unsigned char b[4]; } rev; /* ALIGN: assuming sa is aligned, then cast ok. */ struct sockaddr_in *sin = (struct sockaddr_in *)(void *)sa; /* * build "PTR" query name * NNN.NNN.NNN.NNN.in-addr.arpa. */ rev.s_addr = sin->sin_addr.s_addr; n = snprintf(name, name_len, "%u.%u.%u.%u.in-addr.arpa.", rev.b[3], rev.b[2], rev.b[1], rev.b[0]); if ((n == -1) || (n >= name_len)) { return FALSE; } break; } case AF_INET6 : { int i; int s = 0; /* ALIGN: assume sa is aligned, cast ok. */ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)(void *)sa; size_t x = name_len; /* * build IPv6 "nibble" PTR query name (RFC 1886, RFC 3152) * N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.ip6.arpa. */ for (i = sizeof(sin6->sin6_addr) - 1; i >= 0; i--) { n = snprintf(&name[s], x, "%x.%x.", ( sin6->sin6_addr.s6_addr[i] & 0xf), ((sin6->sin6_addr.s6_addr[i] >> 4) & 0xf)); if ((n == -1) || (n >= x)) { return FALSE; } s += n; x -= n; } n = snprintf(&name[s], x, "ip6.arpa."); if ((n == -1) || (n >= x)) { return FALSE; } break; } default : return FALSE; } return TRUE; } CFStringRef _SCNetworkReachabilityCopyTargetDescription(SCNetworkReachabilityRef target) { CFAllocatorRef allocator = CFGetAllocator(target); CFMutableStringRef str; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; str = CFStringCreateMutable(allocator, 0); switch (targetPrivate->type) { case reachabilityTypeAddress : case reachabilityTypeAddressPair : { char buf[64]; if (targetPrivate->localAddress != NULL) { _SC_sockaddr_to_string(targetPrivate->localAddress, buf, sizeof(buf)); CFStringAppendFormat(str, NULL, CFSTR("local address = %s"), buf); } if (targetPrivate->remoteAddress != NULL) { _SC_sockaddr_to_string(targetPrivate->remoteAddress, buf, sizeof(buf)); CFStringAppendFormat(str, NULL, CFSTR("%s%saddress = %s"), targetPrivate->localAddress ? ", " : "", (targetPrivate->type == reachabilityTypeAddressPair) ? "remote " : "", buf); } break; } case reachabilityTypeName : { CFStringAppendFormat(str, NULL, CFSTR("name = %s"), targetPrivate->name); break; } case reachabilityTypePTR : { CFStringAppendFormat(str, NULL, CFSTR("ptr = %s"), targetPrivate->name); break; } } return str; } CFStringRef _SCNetworkReachabilityCopyTargetFlags(SCNetworkReachabilityRef target) { CFAllocatorRef allocator = CFGetAllocator(target); CFStringRef str; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; str = CFStringCreateWithFormat(allocator, NULL, CFSTR("flags = 0x%08x, if_index = %u%s"), targetPrivate->info.flags, targetPrivate->info.if_index, targetPrivate->info.sleeping ? ", z" : ""); return str; } static CFStringRef __SCNetworkReachabilityCopyDescription(CFTypeRef cf) { CFAllocatorRef allocator = CFGetAllocator(cf); CFMutableStringRef result; CFStringRef str; SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)cf; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; result = CFStringCreateMutable(allocator, 0); CFStringAppendFormat(result, NULL, CFSTR(" {"), cf, allocator); // add target description str = _SCNetworkReachabilityCopyTargetDescription(target); CFStringAppend(result, str); CFRelease(str); // add additional "name" info if (isReachabilityTypeName(targetPrivate->type)) { if (targetPrivate->dnsActive) { CFStringAppendFormat(result, NULL, CFSTR(" (DNS query active)")); } else if (targetPrivate->serverActive && (targetPrivate->info.flags & kSCNetworkReachabilityFlagsFirstResolvePending)) { CFStringAppendFormat(result, NULL, CFSTR(" (server query active)")); } else if ((targetPrivate->resolvedAddresses != NULL) || (targetPrivate->resolvedError != NETDB_SUCCESS)) { if (targetPrivate->resolvedAddresses != NULL) { if (isA_CFArray(targetPrivate->resolvedAddresses)) { CFIndex i; CFIndex n = CFArrayGetCount(targetPrivate->resolvedAddresses); CFStringAppendFormat(result, NULL, CFSTR(" (")); for (i = 0; i < n; i++) { CFDataRef address; CFStringAppendFormat(result, NULL, CFSTR("%s"), i > 0 ? ", " : ""); address = CFArrayGetValueAtIndex(targetPrivate->resolvedAddresses, i); if (isA_CFData(address)) { char buf[64]; struct sockaddr *sa; sa = (struct sockaddr *)CFDataGetBytePtr(address); _SC_sockaddr_to_string(sa, buf, sizeof(buf)); CFStringAppendFormat(result, NULL, CFSTR("%s"), buf); } else { CFStringAppendFormat(result, NULL, CFSTR("%@"), address); } } CFStringAppendFormat(result, NULL, CFSTR(")")); } else if (CFEqual(targetPrivate->resolvedAddresses, kCFNull)) { CFStringAppendFormat(result, NULL, CFSTR(" (%s)"), gai_strerror(targetPrivate->resolvedError)); } else { CFStringAppendFormat(result, NULL, CFSTR(" (no addresses)")); } } else { CFStringAppendFormat(result, NULL, CFSTR(" (%s)"), gai_strerror(targetPrivate->resolvedError)); } } if (targetPrivate->dnsFlags != 0) { CFStringAppendFormat(result, NULL, CFSTR(", " DNS_FLAGS_FORMAT), DNS_FLAGS_VALUES(targetPrivate)); } } if (targetPrivate->onDemandBypass) { CFStringAppendFormat(result, NULL, CFSTR(", !ondemand")); } if (targetPrivate->resolverBypass) { CFStringAppendFormat(result, NULL, CFSTR(", !resolve")); } // add flags if (targetPrivate->scheduled) { str = _SCNetworkReachabilityCopyTargetFlags(target); CFStringAppendFormat(result, NULL, CFSTR(", %@"), str); CFRelease(str); } CFStringAppendFormat(result, NULL, CFSTR("}")); return result; } static void __SCNetworkReachabilityDeallocate(CFTypeRef cf) { SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)cf; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; SCLog((_sc_debug && (_sc_log > 0)), LOG_INFO, CFSTR("%srelease"), targetPrivate->log_prefix); /* disconnect from the reachability server */ if (targetPrivate->serverActive) { __SCNetworkReachabilityServer_targetRemove(target); } /* release resources */ pthread_mutex_destroy(&targetPrivate->lock); if (targetPrivate->name != NULL) CFAllocatorDeallocate(NULL, (void *)targetPrivate->name); if (targetPrivate->resolvedAddresses != NULL) CFRelease(targetPrivate->resolvedAddresses); if (targetPrivate->localAddress != NULL) { if (targetPrivate->localAddress == targetPrivate->remoteAddress) { targetPrivate->remoteAddress = NULL; } CFAllocatorDeallocate(NULL, (void *)targetPrivate->localAddress); } if (targetPrivate->remoteAddress != NULL) CFAllocatorDeallocate(NULL, (void *)targetPrivate->remoteAddress); if (targetPrivate->rlsContext.release != NULL) { (*targetPrivate->rlsContext.release)(targetPrivate->rlsContext.info); } if (targetPrivate->onDemandName != NULL) { CFRelease(targetPrivate->onDemandName); } if (targetPrivate->onDemandRemoteAddress != NULL) { CFRelease(targetPrivate->onDemandRemoteAddress); } if (targetPrivate->onDemandServer != NULL) { CFRelease(targetPrivate->onDemandServer); } if (targetPrivate->onDemandServiceID != NULL) { CFRelease(targetPrivate->onDemandServiceID); } if (targetPrivate->serverDigest != NULL) { CFRelease(targetPrivate->serverDigest); } if (targetPrivate->serverGroup != NULL) { dispatch_release(targetPrivate->serverGroup); } if (targetPrivate->serverQueue != NULL) { dispatch_release(targetPrivate->serverQueue); } if (targetPrivate->serverWatchers != NULL) { CFRelease(targetPrivate->serverWatchers); } if (targetPrivate->nePolicyResult) { free(targetPrivate->nePolicyResult); } return; } static void __SCNetworkReachabilityInitialize(void) { __kSCNetworkReachabilityTypeID = _CFRuntimeRegisterClass(&__SCNetworkReachabilityClass); // provide a way to enable SCNetworkReachability logging without // having to set _sc_debug=1. if ((getenv("REACH_LOGGING") != NULL) || (CFPreferencesGetAppBooleanValue(CFSTR("com.apple.SCNetworkReachability.debug"), kCFPreferencesCurrentApplication, NULL))) { _sc_debug = TRUE; } // set per-process "bypass" of the SCNetworkReachability server if (getenv("REACH_SERVER_BYPASS") != NULL) { D_serverBypass = TRUE; } pthread_mutexattr_init(&lock_attr); pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_ERRORCHECK); return; } __private_extern__ dispatch_queue_t __SCNetworkReachability_concurrent_queue() { static dispatch_once_t once; static dispatch_queue_t q; dispatch_once(&once, ^{ q = dispatch_queue_create("SCNetworkReachability.concurrent", DISPATCH_QUEUE_CONCURRENT); }); return q; } /* * __SCNetworkReachabilityUpdateConcurrent * * Calls reachUpdate() * - caller must be holding a reference to the target * - caller must *not* be holding the target lock * - caller must be running on the __SCNetworkReachability_concurrent_queue() */ __private_extern__ void __SCNetworkReachabilityUpdateConcurrent(SCNetworkReachabilityRef target) { Boolean changed; unsigned int n; dispatch_queue_t queue; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; changed = reachUpdate((void *)target); if (!changed) { return; } n = _SC_ATOMIC_INC(&targetPrivate->pending); if (n > 0) { // if we already have a notification pending return; } MUTEX_LOCK(&targetPrivate->lock); queue = targetPrivate->dispatchQueue; if (queue != NULL) { dispatch_group_t group; dispatch_retain(queue); group = targetPrivate->dispatchGroup; dispatch_group_enter(group); MUTEX_UNLOCK(&targetPrivate->lock); dispatch_sync(queue, ^{ reachPerform((void *)target); dispatch_group_leave(group); }); dispatch_release(queue); } else { if (targetPrivate->rls != NULL) { CFRunLoopSourceSignal(targetPrivate->rls); _SC_signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList); } MUTEX_UNLOCK(&targetPrivate->lock); } return; } /* * __SCNetworkReachabilityUpdate * * Calls reachUpdate() [indirectly] * - caller can be holding the target lock * - caller can be running on any dispatch queue */ __private_extern__ void __SCNetworkReachabilityUpdate(SCNetworkReachabilityRef target) { CFRetain(target); dispatch_async(__SCNetworkReachability_concurrent_queue(), ^{ __SCNetworkReachabilityUpdateConcurrent(target); CFRelease(target); }); return; } static SCNetworkReachabilityPrivateRef __SCNetworkReachabilityCreatePrivate(CFAllocatorRef allocator) { SCNetworkReachabilityPrivateRef targetPrivate; uint32_t size; /* initialize runtime */ pthread_once(&initialized, __SCNetworkReachabilityInitialize); /* allocate target */ size = sizeof(SCNetworkReachabilityPrivate) - sizeof(CFRuntimeBase); targetPrivate = (SCNetworkReachabilityPrivateRef)_CFRuntimeCreateInstance(allocator, __kSCNetworkReachabilityTypeID, size, NULL); if (targetPrivate == NULL) { return NULL; } bzero((void *)targetPrivate + sizeof(CFRuntimeBase), size); MUTEX_INIT(&targetPrivate->lock); targetPrivate->cycle = 1; targetPrivate->last_notify = NOT_REPORTED; targetPrivate->serverBypass = D_serverBypass; targetPrivate->log_prefix[0] = '\0'; if (_sc_log > 0) { snprintf(targetPrivate->log_prefix, sizeof(targetPrivate->log_prefix), "[%p] ", targetPrivate); } return targetPrivate; } static const struct sockaddr * is_valid_address(const struct sockaddr *address) { const struct sockaddr *valid = NULL; static Boolean warned = FALSE; if ((address != NULL) && (address->sa_len <= sizeof(struct sockaddr_storage))) { switch (address->sa_family) { case AF_INET : if (address->sa_len >= sizeof(struct sockaddr_in)) { valid = address; } else { if (!warned) { SCLog(TRUE, LOG_ERR, CFSTR("SCNetworkReachabilityCreateWithAddress[Pair] called with \"struct sockaddr *\" len %d < %zu"), address->sa_len, sizeof(struct sockaddr_in)); warned = TRUE; } } break; case AF_INET6 : if (address->sa_len >= sizeof(struct sockaddr_in6)) { valid = address; } else if (!warned) { SCLog(TRUE, LOG_ERR, CFSTR("SCNetworkReachabilityCreateWithAddress[Pair] called with \"struct sockaddr *\" len %d < %zu"), address->sa_len, sizeof(struct sockaddr_in6)); warned = TRUE; } break; default : if (!warned) { SCLog(TRUE, LOG_ERR, CFSTR("SCNetworkReachabilityCreateWithAddress[Pair] called with invalid address family %d"), address->sa_family); warned = TRUE; } } } return valid; } SCNetworkReachabilityRef SCNetworkReachabilityCreateWithAddress(CFAllocatorRef allocator, const struct sockaddr *address) { SCNetworkReachabilityPrivateRef targetPrivate; address = is_valid_address(address); if (address == NULL) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator); if (targetPrivate == NULL) { return NULL; } targetPrivate->type = reachabilityTypeAddress; targetPrivate->remoteAddress = CFAllocatorAllocate(NULL, address->sa_len, 0); bcopy(address, targetPrivate->remoteAddress, address->sa_len); SCLog((_sc_debug && (_sc_log > 0)), LOG_INFO, CFSTR("%s%s %@"), targetPrivate->log_prefix, DEBUG_REACHABILITY_TYPE_ADDRESS, targetPrivate); return (SCNetworkReachabilityRef)targetPrivate; } static Boolean is_same_address(const struct sockaddr *a, const struct sockaddr *b) { const void *a_addr; const void *b_addr; size_t len; if ((a == NULL) || (b == NULL) || (a->sa_family != b->sa_family) || (a->sa_len != b->sa_len )) { return FALSE; } switch (a->sa_family) { case AF_INET : { struct sockaddr_in *a_sin = (struct sockaddr_in *)(void *)a; struct sockaddr_in *b_sin = (struct sockaddr_in *)(void *)b; /* ALIGN: assuming a (and b) are aligned, then cast ok. */ a_addr = &a_sin->sin_addr; b_addr = &b_sin->sin_addr; len = sizeof(struct in_addr); break; } case AF_INET6 : { struct sockaddr_in6 *a_sin6 = (struct sockaddr_in6 *)(void *)a; struct sockaddr_in6 *b_sin6 = (struct sockaddr_in6 *)(void *)b; if (a_sin6->sin6_scope_id != b_sin6->sin6_scope_id) { return FALSE; } a_addr = &a_sin6->sin6_addr; b_addr = &b_sin6->sin6_addr; len = sizeof(struct in6_addr); break; } default : a_addr = a; b_addr = b; len = a->sa_len; break; } return (bcmp(a_addr, b_addr, len) == 0); } SCNetworkReachabilityRef SCNetworkReachabilityCreateWithAddressPair(CFAllocatorRef allocator, const struct sockaddr *localAddress, const struct sockaddr *remoteAddress) { SCNetworkReachabilityPrivateRef targetPrivate; if ((localAddress == NULL) && (remoteAddress == NULL)) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } if (localAddress != NULL) { localAddress = is_valid_address(localAddress); if (localAddress == NULL) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } } if (remoteAddress != NULL) { remoteAddress = is_valid_address(remoteAddress); if (remoteAddress == NULL) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } } targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator); if (targetPrivate == NULL) { return NULL; } targetPrivate->type = reachabilityTypeAddressPair; if (localAddress != NULL) { targetPrivate->localAddress = CFAllocatorAllocate(NULL, localAddress->sa_len, 0); bcopy(localAddress, targetPrivate->localAddress, localAddress->sa_len); } if (remoteAddress != NULL) { if (is_same_address(localAddress, remoteAddress)) { targetPrivate->remoteAddress = targetPrivate->localAddress; } else { targetPrivate->remoteAddress = CFAllocatorAllocate(NULL, remoteAddress->sa_len, 0); bcopy(remoteAddress, targetPrivate->remoteAddress, remoteAddress->sa_len); } } SCLog((_sc_debug && (_sc_log > 0)), LOG_INFO, CFSTR("%s%s %@"), targetPrivate->log_prefix, DEBUG_REACHABILITY_TYPE_ADDRESSPAIR, targetPrivate); return (SCNetworkReachabilityRef)targetPrivate; } SCNetworkReachabilityRef SCNetworkReachabilityCreateWithName(CFAllocatorRef allocator, const char *nodename) { union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; } addr; size_t nodenameLen; SCNetworkReachabilityPrivateRef targetPrivate; if (nodename == NULL) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } nodenameLen = strlen(nodename); if (nodenameLen == 0) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } if (nodename[nodenameLen - 1] == '.') { int dots; size_t i; // trim trailing "."s do { --nodenameLen; } while ((nodenameLen > 0) && (nodename[nodenameLen - 1] == '.')); if (nodenameLen == 0) { // if only trailing "."s _SCErrorSet(kSCStatusInvalidArgument); return NULL; } // count the remaining "."s dots = 0; for (i = 0; i < nodenameLen; i++) { if (nodename[i] == '.') dots++; } if (dots == 0) { // if only a single-label, add back the FQDN "." nodenameLen++; } } if (_SC_string_to_sockaddr(nodename, AF_UNSPEC, (void *)&addr, sizeof(addr)) != NULL) { /* if this "nodename" is really an IP[v6] address in disguise */ return SCNetworkReachabilityCreateWithAddress(allocator, &addr.sa); } targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator); if (targetPrivate == NULL) { return NULL; } targetPrivate->type = reachabilityTypeName; targetPrivate->name = CFAllocatorAllocate(NULL, nodenameLen + 1, 0); strlcpy((char *)targetPrivate->name, nodename, nodenameLen + 1); targetPrivate->needResolve = TRUE; targetPrivate->info.flags |= kSCNetworkReachabilityFlagsFirstResolvePending; targetPrivate->serverInfo.flags |= kSCNetworkReachabilityFlagsFirstResolvePending; { /* make sure AppLayerVPN only is in client mode */ CFDictionaryRef appLayerVPNProperties; appLayerVPNProperties = VPNAppLayerCopyCurrentAppProperties(); if (appLayerVPNProperties != NULL) { targetPrivate->serverBypassForVPN = TRUE; targetPrivate->serverBypass = YES; CFRelease(appLayerVPNProperties); } } SCLog((_sc_debug && (_sc_log > 0)), LOG_INFO, CFSTR("%s%s %@"), targetPrivate->log_prefix, DEBUG_REACHABILITY_TYPE_NAME, targetPrivate); return (SCNetworkReachabilityRef)targetPrivate; } static SCNetworkReachabilityRef __SCNetworkReachabilityCreateWithPtr(CFAllocatorRef allocator, const char *ptrName, const struct sockaddr *ptrAddress) { SCNetworkReachabilityRef target; SCNetworkReachabilityPrivateRef targetPrivate; target = SCNetworkReachabilityCreateWithName(NULL, ptrName); if (target == NULL) { return NULL; } targetPrivate = (SCNetworkReachabilityPrivateRef)target; // change type targetPrivate->type = reachabilityTypePTR; // and keep the address targetPrivate->remoteAddress = CFAllocatorAllocate(NULL, ptrAddress->sa_len, 0); bcopy(ptrAddress, targetPrivate->remoteAddress, ptrAddress->sa_len); return target; } SCNetworkReachabilityRef SCNetworkReachabilityCreateWithOptions(CFAllocatorRef allocator, CFDictionaryRef options) { const struct sockaddr *addr_l = NULL; const struct sockaddr *addr_p = NULL; const struct sockaddr *addr_r = NULL; CFDataRef data; CFStringRef interface = NULL; CFStringRef nodename; CFBooleanRef onDemandBypass; CFBooleanRef resolverBypass; CFBooleanRef serverBypass; SCNetworkReachabilityRef target; SCNetworkReachabilityPrivateRef targetPrivate; if (!isA_CFDictionary(options)) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } nodename = CFDictionaryGetValue(options, kSCNetworkReachabilityOptionNodeName); if ((nodename != NULL) && (!isA_CFString(nodename) || (CFStringGetLength(nodename) == 0))) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } data = CFDictionaryGetValue(options, kSCNetworkReachabilityOptionLocalAddress); if (data != NULL) { if (!isA_CFData(data) || (CFDataGetLength(data) < sizeof(struct sockaddr_in))) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } addr_l = (const struct sockaddr *)CFDataGetBytePtr(data); } data = CFDictionaryGetValue(options, kSCNetworkReachabilityOptionPTRAddress); if (data != NULL) { if (!isA_CFData(data) || (CFDataGetLength(data) < sizeof(struct sockaddr_in))) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } addr_p = (const struct sockaddr *)CFDataGetBytePtr(data); } data = CFDictionaryGetValue(options, kSCNetworkReachabilityOptionRemoteAddress); if (data != NULL) { if (!isA_CFData(data) || (CFDataGetLength(data) < sizeof(struct sockaddr_in))) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } addr_r = (const struct sockaddr *)CFDataGetBytePtr(data); } interface = CFDictionaryGetValue(options, kSCNetworkReachabilityOptionInterface); if ((interface != NULL) && (!isA_CFString(interface) || (CFStringGetLength(interface) == 0))) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } onDemandBypass = CFDictionaryGetValue(options, kSCNetworkReachabilityOptionConnectionOnDemandBypass); if ((onDemandBypass != NULL) && !isA_CFBoolean(onDemandBypass)) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } resolverBypass = CFDictionaryGetValue(options, kSCNetworkReachabilityOptionResolverBypass); if ((resolverBypass != NULL) && !isA_CFBoolean(resolverBypass)) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } serverBypass = CFDictionaryGetValue(options, kSCNetworkReachabilityOptionServerBypass); if ((serverBypass != NULL) && !isA_CFBoolean(serverBypass)) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } if (nodename != NULL) { const char *name; if ((addr_l != NULL) || (addr_r != NULL) || (addr_p != NULL)) { // can't have both a nodename and an address _SCErrorSet(kSCStatusInvalidArgument); return NULL; } name = _SC_cfstring_to_cstring(nodename, NULL, 0, kCFStringEncodingUTF8); target = SCNetworkReachabilityCreateWithName(allocator, name); CFAllocatorDeallocate(NULL, (void *)name); } else if (addr_p != NULL) { char name[MAXHOSTNAMELEN]; if ((addr_l != NULL) || // can't have PTR and target address (addr_r != NULL) || // can't have PTR and target address !addr_to_PTR_name(addr_p, name, sizeof(name))) { // can't convert PTR _SCErrorSet(kSCStatusInvalidArgument); return NULL; } target = __SCNetworkReachabilityCreateWithPtr(NULL, name, addr_p); } else { if ((addr_l != NULL) && (addr_r != NULL)) { target = SCNetworkReachabilityCreateWithAddressPair(NULL, addr_l, addr_r); } else if (addr_r != NULL) { target = SCNetworkReachabilityCreateWithAddress(NULL, addr_r); } else if (addr_l != NULL) { target = SCNetworkReachabilityCreateWithAddressPair(NULL, addr_l, NULL); } else { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } } if (target == NULL) { return NULL; } targetPrivate = (SCNetworkReachabilityPrivateRef)target; if (interface != NULL) { if ((_SC_cfstring_to_cstring(interface, targetPrivate->if_name, sizeof(targetPrivate->if_name), kCFStringEncodingASCII) == NULL) || ((targetPrivate->if_index = if_nametoindex(targetPrivate->if_name)) == 0)) { CFRelease(targetPrivate); _SCErrorSet(kSCStatusInvalidArgument); return NULL; } } if (onDemandBypass != NULL) { targetPrivate->onDemandBypass = CFBooleanGetValue(onDemandBypass); } if (resolverBypass != NULL) { targetPrivate->resolverBypass = CFBooleanGetValue(resolverBypass); } /* if by name, make sure client-only VPN types stay in client mode */ if (serverBypass != NULL && targetPrivate->serverBypassForVPN == FALSE) { targetPrivate->serverBypass = CFBooleanGetValue(serverBypass); } if (_sc_debug && (_sc_log > 0)) { const char *opt = "???"; switch (targetPrivate->type) { case reachabilityTypeAddress : opt = DEBUG_REACHABILITY_TYPE_ADDRESS_OPTIONS; break; case reachabilityTypeAddressPair : opt = DEBUG_REACHABILITY_TYPE_ADDRESSPAIR_OPTIONS; break; case reachabilityTypeName : opt = DEBUG_REACHABILITY_TYPE_NAME_OPTIONS; break; case reachabilityTypePTR : opt = DEBUG_REACHABILITY_TYPE_PTR_OPTIONS; break; } SCLog(TRUE, LOG_INFO, CFSTR("%s%s %@"), targetPrivate->log_prefix, opt, targetPrivate); } return (SCNetworkReachabilityRef)targetPrivate; } static SCNetworkReachabilityRef __SCNetworkReachabilityCreateCopy(SCNetworkReachabilityRef target) { SCNetworkReachabilityRef clone = NULL; SCNetworkReachabilityPrivateRef clonePrivate; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; switch (targetPrivate->type) { case reachabilityTypeAddress : clone = SCNetworkReachabilityCreateWithAddress(NULL, targetPrivate->remoteAddress); break; case reachabilityTypeAddressPair : clone = SCNetworkReachabilityCreateWithAddressPair(NULL, targetPrivate->localAddress, targetPrivate->remoteAddress); break; case reachabilityTypeName : clone = SCNetworkReachabilityCreateWithName(NULL, targetPrivate->name); break; case reachabilityTypePTR : clone = __SCNetworkReachabilityCreateWithPtr(NULL, targetPrivate->name, targetPrivate->remoteAddress); break; } if (clone == NULL) { return NULL; } clonePrivate = (SCNetworkReachabilityPrivateRef)clone; clonePrivate->quiet = TRUE; clonePrivate->if_index = targetPrivate->if_index; bcopy(targetPrivate->if_name, clonePrivate->if_name, sizeof(clonePrivate->if_name)); clonePrivate->onDemandBypass = targetPrivate->onDemandBypass; clonePrivate->serverBypass = targetPrivate->serverBypass; clonePrivate->resolverBypass = targetPrivate->resolverBypass; if (_sc_debug && (_sc_log > 0)) { const char *opt = "???"; switch (clonePrivate->type) { case reachabilityTypeAddress : opt = DEBUG_REACHABILITY_TYPE_ADDRESS_CLONE; break; case reachabilityTypeAddressPair : opt = DEBUG_REACHABILITY_TYPE_ADDRESSPAIR_CLONE; break; case reachabilityTypeName : opt = DEBUG_REACHABILITY_TYPE_NAME_CLONE; break; case reachabilityTypePTR : opt = DEBUG_REACHABILITY_TYPE_PTR_CLONE; break; } SCLog(TRUE, LOG_INFO, CFSTR("%s%s %p %@"), clonePrivate->log_prefix, opt, targetPrivate, clone); } return clone; } CFTypeID SCNetworkReachabilityGetTypeID(void) { pthread_once(&initialized, __SCNetworkReachabilityInitialize); /* initialize runtime */ return __kSCNetworkReachabilityTypeID; } CFArrayRef /* CFArray[CFData], where each CFData is a (struct sockaddr *) */ SCNetworkReachabilityCopyResolvedAddress(SCNetworkReachabilityRef target, int *error_num) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } if (!isReachabilityTypeName(targetPrivate->type)) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } if (error_num) { *error_num = targetPrivate->resolvedError; } if (targetPrivate->resolvedAddresses != NULL) { if (isA_CFArray(targetPrivate->resolvedAddresses)) { return CFRetain(targetPrivate->resolvedAddresses); } else { /* if status is known but no resolved addresses to return */ _SCErrorSet(kSCStatusOK); return NULL; } } _SCErrorSet(kSCStatusReachabilityUnknown); return NULL; } static void __SCNetworkReachabilitySetResolvedError(SCNetworkReachabilityRef target, int32_t status) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; MUTEX_ASSERT_HELD(&targetPrivate->lock); __mark_operation_end(target, FALSE, // if successful query dns_query_async, // async &targetPrivate->dnsQueryStart, // start time &targetPrivate->dnsQueryEnd); // end time if (targetPrivate->resolvedAddresses != NULL) { CFRelease(targetPrivate->resolvedAddresses); targetPrivate->resolvedAddresses = NULL; } SCLog(_sc_debug, LOG_INFO, CFSTR("%scould not be resolved: %s"), targetPrivate->log_prefix, gai_strerror(status)); /* save the error associated with the attempt to resolve the name */ targetPrivate->resolvedAddresses = CFRetain(kCFNull); targetPrivate->resolvedError = status; targetPrivate->needResolve = FALSE; return; } /* * rankReachability() * Not reachable == 0 * Connection Required == 1 * Reachable == 2 */ static int rankReachability(SCNetworkReachabilityFlags flags) { int rank = 0; if (flags & kSCNetworkReachabilityFlagsReachable) rank = 2; if (flags & kSCNetworkReachabilityFlagsConnectionRequired) rank = 1; return rank; } #pragma mark - #pragma mark DNS name resolution static void update_resolver_reachability(ReachabilityStoreInfoRef store_info, dns_resolver_t *resolver, SCNetworkReachabilityFlags *flags, Boolean *haveDNS, uint32_t *resolver_if_index, const char *log_prefix) { if (resolver_if_index) *resolver_if_index = 0; if (resolver->n_nameserver > 0) { *flags = (SCNetworkReachabilityFlags)resolver->reach_flags; if (resolver_if_index != NULL) { *resolver_if_index = resolver->if_index; } *haveDNS = TRUE; } else { *flags = kSCNetworkReachabilityFlagsReachable; *haveDNS = FALSE; } return; } static Boolean check_matching_resolvers(ReachabilityStoreInfoRef store_info, dns_config_t *dns_config, const char *fqdn, unsigned int if_index, SCNetworkReachabilityFlags *flags, Boolean *haveDNS, uint32_t *resolver_if_index, int *dns_config_index, const char *log_prefix) { int i; Boolean matched = FALSE; const char *name = fqdn; int32_t n_resolvers; dns_resolver_t **resolvers; if (if_index == 0) { n_resolvers = dns_config->n_resolver; resolvers = dns_config->resolver; } else { n_resolvers = dns_config->n_scoped_resolver; resolvers = dns_config->scoped_resolver; } /* In case we couldn't find a match, setting an index of -1 and resolver_if_index 0 */ if (dns_config_index != NULL) *dns_config_index = -1; if (resolver_if_index != NULL) *resolver_if_index = 0; while (!matched && (name != NULL)) { size_t len; /* * check if the provided name (or sub-component) * matches one of our resolver configurations. */ len = strlen(name); for (i = 0; i < n_resolvers; i++) { char *domain; dns_resolver_t *resolver; resolver = resolvers[i]; if ((if_index != 0) && (if_index != resolver->if_index)) { continue; } domain = resolver->domain; if (domain != NULL && (len == strlen(domain))) { if (strcasecmp(name, domain) == 0) { /* * if name matches domain */ matched = TRUE; update_resolver_reachability(store_info, resolver, flags, haveDNS, resolver_if_index, log_prefix); if (dns_config_index != NULL) *dns_config_index = i; break; } } } if (!matched) { /* * we have not found a matching resolver, try * a less qualified domain */ name = strchr(name, '.'); if ((name != NULL) && (*name != '\0')) { name++; } else { name = NULL; } } } return matched; } static dns_resolver_t * get_default_resolver(dns_config_t *dns_config, unsigned int if_index) { int i; int32_t n_resolvers; dns_resolver_t *resolver = NULL; dns_resolver_t **resolvers; if (if_index == 0) { n_resolvers = dns_config->n_resolver; resolvers = dns_config->resolver; } else { n_resolvers = dns_config->n_scoped_resolver; resolvers = dns_config->scoped_resolver; } for (i = 0; i < n_resolvers; i++) { if ((if_index != 0) && (if_index != resolvers[i]->if_index)) { continue; } if (((if_index == 0) && (i == 0)) || ((if_index != 0) && (resolver == NULL))) { // if this is the first (aka default) resolver resolver = resolvers[i]; } else if ((resolvers[i]->domain == NULL) && (resolvers[i]->search_order < resolver->search_order)) { // if this is a default resolver with a lower search order resolver = resolvers[i]; } } return resolver; } static dns_configuration_t * dns_configuration_retain() { dns_configuration_t *config; pthread_mutex_lock(&dns_lock); if (dns_configuration != NULL) { Boolean refresh = TRUE; if (dns_token_valid) { int check = 0; uint32_t status; /* * check if the global [DNS] configuration snapshot needs * to be updated */ status = notify_check(dns_token, &check); if (status != NOTIFY_STATUS_OK) { SCLog(TRUE, LOG_INFO, CFSTR("notify_check() failed, status=%u"), status); } else if (check == 0) { // if the snapshot does not need to be refreshed refresh = FALSE; } } if (refresh) { if (dns_configuration->refs == 0) { dns_configuration_free(dns_configuration->config); CFAllocatorDeallocate(NULL, dns_configuration); } dns_configuration = NULL; } } if (dns_configuration == NULL) { dns_config_t *new_config; new_config = dns_configuration_copy(); if (new_config != NULL) { dns_configuration = CFAllocatorAllocate(NULL, sizeof(dns_configuration_t), 0); dns_configuration->config = new_config; dns_configuration->refs = 0; } } if (dns_configuration != NULL) { dns_configuration->refs++; } config = dns_configuration; pthread_mutex_unlock(&dns_lock); return config; } static void dns_configuration_release(dns_configuration_t *config) { pthread_mutex_lock(&dns_lock); config->refs--; if (config->refs == 0) { if (!dns_token_valid && (config == dns_configuration)) { dns_configuration = NULL; } if (config != dns_configuration) { dns_configuration_free(config->config); CFAllocatorDeallocate(NULL, config); } } pthread_mutex_unlock(&dns_lock); return; } static Boolean dns_configuration_watch() { int dns_check = 0; const char *dns_key; Boolean ok = FALSE; uint32_t status; pthread_mutex_lock(&dns_lock); dns_key = dns_configuration_notify_key(); if (dns_key == NULL) { SCLog(TRUE, LOG_INFO, CFSTR("dns_configuration_notify_key() failed")); goto done; } status = notify_register_check(dns_key, &dns_token); if (status == NOTIFY_STATUS_OK) { dns_token_valid = TRUE; } else { SCLog(TRUE, LOG_INFO, CFSTR("notify_register_check() failed, status=%u"), status); goto done; } status = notify_check(dns_token, &dns_check); if (status != NOTIFY_STATUS_OK) { SCLog(TRUE, LOG_INFO, CFSTR("notify_check() failed, status=%u"), status); (void)notify_cancel(dns_token); dns_token_valid = FALSE; goto done; } ok = TRUE; done : pthread_mutex_unlock(&dns_lock); return ok; } static void dns_configuration_unwatch() { pthread_mutex_lock(&dns_lock); (void)notify_cancel(dns_token); dns_token_valid = FALSE; if ((dns_configuration != NULL) && (dns_configuration->refs == 0)) { dns_configuration_free(dns_configuration->config); CFAllocatorDeallocate(NULL, dns_configuration); dns_configuration = NULL; } pthread_mutex_unlock(&dns_lock); return; } static void _SC_R_updateResolverReachability(ReachabilityStoreInfoRef store_info, SCNetworkReachabilityFlags *flags, Boolean *haveDNS, const char *nodename, unsigned int if_index, uint32_t *resolver_if_index, int *dns_config_index, const char *log_prefix ) { dns_resolver_t *default_resolver; dns_configuration_t *dns; Boolean found = FALSE; char *fqdn = (char *)nodename; int i; Boolean isFQDN = FALSE; size_t len; const int ndots = 1; Boolean useDefault = FALSE; if (resolver_if_index) *resolver_if_index = 0; if (dns_config_index) *dns_config_index = -1; /* * We first assume that all of the configured DNS servers * are available. Since we don't know which name server will * be consulted to resolve the specified nodename we need to * check the availability of ALL name servers. We can only * proceed if we know that our query can be answered. */ *flags = kSCNetworkReachabilityFlagsReachable; *haveDNS = FALSE; len = (nodename != NULL) ? strlen(nodename) : 0; if (len == 0) { // if no nodename, return not reachable *flags = 0; return; } dns = dns_configuration_retain(); if (dns == NULL) { // if error SCLog(_sc_debug, LOG_INFO, CFSTR("%sDNS: no configuration"), log_prefix); goto done; } default_resolver = get_default_resolver(dns->config, if_index); if (default_resolver == NULL) { // if no resolver configuration SCLog(_sc_debug, LOG_INFO, CFSTR("%sDNS: no resolvers"), log_prefix); goto done; } if (fqdn[len - 1] == '.') { isFQDN = TRUE; // trim trailing '.''s while ((len > 0) && (fqdn[len-1] == '.')) { if (fqdn == nodename) { fqdn = strdup(nodename); assert(fqdn != nodename); } fqdn[--len] = '\0'; } } /* * check if the provided name matches a supplemental domain */ found = check_matching_resolvers(store_info, dns->config, fqdn, if_index, flags, haveDNS, resolver_if_index, dns_config_index, log_prefix); if (!found && !isFQDN) { /* * if we did not match a supplemental domain name and if the * provided name has enough "."s then the first query will be * directed to the default resolver. */ char *cp; int dots; dots = 0; for (cp = fqdn; *cp != '\0'; cp++) { if (*cp == '.') dots++; } /* Per KB: HT4845 */ if (dots >= ndots) { useDefault = TRUE; } } if (!found && !isFQDN && !useDefault && (dns->config->n_resolver > 1)) { /* * FQDN not specified, try matching w/search domains */ if (default_resolver->n_search > 0) { for (i = 0; !found && (i < default_resolver->n_search); i++) { int ret; char *search_fqdn = NULL; ret = asprintf(&search_fqdn, "%s.%s", fqdn, default_resolver->search[i]); if (ret == -1) { continue; } // try the provided name with the search domain appended found = check_matching_resolvers(store_info, dns->config, search_fqdn, if_index, flags, haveDNS, resolver_if_index, dns_config_index, log_prefix); free(search_fqdn); } } else if (default_resolver->domain != NULL) { char *dp; int domain_parts = 0; // count domain parts for (dp = default_resolver->domain; *dp != '\0'; dp++) { if (*dp == '.') { domain_parts++; } } // remove trailing dots for (dp--; (dp >= default_resolver->domain) && (*dp == '.'); dp--) { *dp = '\0'; domain_parts--; } if (dp >= default_resolver->domain) { // dots are separators, bump # of components domain_parts++; } dp = default_resolver->domain; for (i = LOCALDOMAINPARTS; !found && (i <= (domain_parts - ndots)); i++) { int ret; char *search_fqdn = NULL; ret = asprintf(&search_fqdn, "%s.%s", fqdn, dp); if (ret == -1) { continue; } // try the provided name with the [default] domain appended found = check_matching_resolvers(store_info, dns->config, search_fqdn, if_index, flags, haveDNS, resolver_if_index, dns_config_index, log_prefix); free(search_fqdn); // move to the next component of the [default] domain dp = strchr(dp, '.') + 1; } } } if (!found) { // update the reachability of the default resolver update_resolver_reachability(store_info, default_resolver, flags, haveDNS, resolver_if_index, log_prefix); if (dns_config_index != NULL) *dns_config_index = 0; } done : if (fqdn != nodename) free(fqdn); if (dns != NULL) { dns_configuration_release(dns); } return; } Boolean __SC_checkResolverReachabilityInternal(SCDynamicStoreRef *storeP, SCNetworkReachabilityFlags *flags, Boolean *haveDNS, const char *nodename, uint32_t *resolver_if_index, int *dns_config_index) { Boolean ok; ReachabilityStoreInfo store_info; ReachabilityStoreInfo_init(&store_info); ok = ReachabilityStoreInfo_update(&store_info, storeP, AF_UNSPEC); if (!ok) { goto done; } _SC_R_updateResolverReachability(&store_info, flags, haveDNS, nodename, 0, resolver_if_index, dns_config_index, ""); done : ReachabilityStoreInfo_free(&store_info); return ok; } /* * _SC_checkResolverReachabilityByAddress() * * Given an IP address, determine whether a reverse DNS query can be issued * using the current network configuration. */ Boolean _SC_checkResolverReachabilityByAddress(SCDynamicStoreRef *storeP, SCNetworkReachabilityFlags *flags, Boolean *haveDNS, struct sockaddr *sa) { Boolean ok; char ptr_name[128]; ReachabilityStoreInfo store_info; ReachabilityStoreInfo_init(&store_info); ok = ReachabilityStoreInfo_update(&store_info, storeP, AF_UNSPEC); if (!ok) { goto done; } /* * Ideally, we would have an API that given a local IP * address would return the DNS server(s) that would field * a given PTR query. Fortunately, we do have an SPI which * which will provide this information given a "name" so we * take the address, convert it into the inverse query name, * and find out which servers should be consulted. */ ok = addr_to_PTR_name(sa, ptr_name, sizeof(ptr_name)); if (!ok) { goto done; } _SC_R_updateResolverReachability(&store_info, flags, haveDNS, ptr_name, 0, NULL, NULL, ""); done : ReachabilityStoreInfo_free(&store_info); return ok; } #pragma mark - #pragma mark DNSServiceGetAddrInfo support /* * DNS query handling * * Notes : * * 1. We have a "contract" with discoveryd that for EVERY network * or DNS configuration change that should warrant our [re-]starting * a query, discoveryd will acknowledge the latest DNS configuration. * * 2. IPMonitor also posts a notification AFTER every network or DNS * configuration change. * * 3. We use IPMonitor's "trailing edge" as a signal to restart any * by-name queries. */ // Note: protected by _hn_target_queue() static int dns_refresh_token; static Boolean dns_refresh_token_valid = FALSE; /* * dns_refresh_handler * * Called to notify/update all SCNetworkReachability by-name targets of * a network/DNS change. The change should [re-]start a DNS query to * resolve the name. * - should be exec'd on the _hn_target_queue() */ static void dns_refresh_handler() { CFArrayRef changes; CFStringRef key; __block SCDynamicStoreRef store = NULL; dispatch_sync(_hn_target_queue(), ^{ if (dns_refresh_token_valid && (hn_store != NULL)) { store = CFRetain(hn_store); } }); if (store == NULL) { return; } key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetDNS); changes = CFArrayCreate(NULL, (const void **)&key, 1, &kCFTypeArrayCallBacks); __SCNetworkReachabilityHandleChanges(store, changes, NULL); CFRelease(changes); CFRelease(key); CFRelease(store); return; } /* * dns_refresh_enable * * Called to monitor for network/DNS changes that should restart a DNS query. * - caller must be running on the _hn_target_queue() */ static Boolean dns_refresh_enable(dispatch_queue_t q) { uint32_t status; status = notify_register_dispatch(_SC_NOTIFY_NETWORK_CHANGE, &dns_refresh_token, q, ^(int token){ dns_refresh_handler(); }); if (status != NOTIFY_STATUS_OK) { SCLog(TRUE, LOG_INFO, CFSTR("notify_register_dispatch() failed, status=%u"), status); return FALSE; } dns_refresh_token_valid = TRUE; return TRUE; } /* * dns_refresh_disable * * Called to stop monitoring for network/DNS changes * - caller must be running on the _hn_target_queue() */ static void dns_refresh_disable() { (void)notify_cancel(dns_refresh_token); dns_refresh_token_valid = FALSE; return; } #pragma mark - #pragma mark [m]DNS Queries static void dequeueDNSQuery(SCNetworkReachabilityRef target); static dispatch_queue_t _dns_queue() { static dispatch_once_t once; static dispatch_queue_t q; dispatch_once(&once, ^{ q = dispatch_queue_create("SCNetworkReachability.DNSService", NULL); }); return q; } /* * _dns_complete */ static __inline__ Boolean _dns_complete(SCNetworkReachabilityRef target) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; if ((targetPrivate->dnsHaveV4 && targetPrivate->dnsHaveV6) || targetPrivate->dnsHavePTR || targetPrivate->dnsHaveError || targetPrivate->dnsHaveTimeout) { return TRUE; } return FALSE; } /* * _dns_notify * * Called to push out a target's DNS changes * - caller must be running on the _dns_queue() */ static void _dns_notify(const void *value, void *context) { SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)value; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; MUTEX_LOCK(&targetPrivate->lock); if (_dns_complete(target)) { __mark_operation_end(target, (targetPrivate->resolvedError == NETDB_SUCCESS), // if successful query dns_query_mdns, // [m]DNS query &targetPrivate->dnsQueryStart, // start time &targetPrivate->dnsQueryEnd); // end time // update target info if (targetPrivate->resolvedAddresses != NULL) { CFRelease(targetPrivate->resolvedAddresses); } targetPrivate->resolvedAddresses = targetPrivate->dnsAddresses; targetPrivate->dnsAddresses = NULL; targetPrivate->resolvedError = targetPrivate->dnsError; targetPrivate->dnsError = NETDB_SUCCESS; dequeueDNSQuery(target); targetPrivate->needResolve = FALSE; if (targetPrivate->scheduled) { __SCNetworkReachabilityUpdate(target); } } MUTEX_UNLOCK(&targetPrivate->lock); return; } typedef enum { MARK_NONE, MARK_ERROR, MARK_TIMEOUT, MARK_HAVE_V4, MARK_HAVE_V6, MARK_HAVE_PTR, } _dns_mark_t; /* * _dns_mark */ static __inline__ void _dns_mark(SCNetworkReachabilityRef target, _dns_mark_t mark) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; switch (mark) { case MARK_NONE : break; case MARK_ERROR : targetPrivate->dnsHaveError = TRUE; break; case MARK_TIMEOUT : targetPrivate->dnsHaveTimeout = TRUE; break; case MARK_HAVE_V4 : targetPrivate->dnsHaveV4 = TRUE; break; case MARK_HAVE_V6 : targetPrivate->dnsHaveV6 = TRUE; break; case MARK_HAVE_PTR : targetPrivate->dnsHavePTR = TRUE; break; } return; } /* * _dns_callback * * Called to process [m]DNS query updates * - caller must be running on the _dns_queue() */ static void _dns_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, _dns_mark_t dnsMark, CFTypeRef dnsAddress, // CFData(struct sockaddr) or CFString(ptr_name) void *context) { int failures = 0; Boolean restart = FALSE; SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)context; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; MUTEX_LOCK(&targetPrivate->lock); if (sdRef != targetPrivate->dnsTarget) { // if this DNSServiceRef is no longer associated with the target MUTEX_UNLOCK(&targetPrivate->lock); return; } switch (errorCode) { case kDNSServiceErr_NoError : if (dnsAddress != NULL) { CFMutableArrayRef addresses; CFIndex i; _dns_mark(target, dnsMark); if (targetPrivate->dnsAddresses != NULL) { if (isA_CFArray(targetPrivate->dnsAddresses)) { addresses = CFArrayCreateMutableCopy(NULL, 0, targetPrivate->dnsAddresses); } else { addresses = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); } CFRelease(targetPrivate->dnsAddresses); targetPrivate->dnsAddresses = NULL; } else { addresses = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); } i = CFArrayGetFirstIndexOfValue(addresses, CFRangeMake(0, CFArrayGetCount(addresses)), dnsAddress); if (flags & kDNSServiceFlagsAdd) { // add address if (i == kCFNotFound) { CFArrayAppendValue(addresses, dnsAddress); } #ifdef HANDLE_RMV_REQUESTS } else { // remove address if (i != kCFNotFound) { CFArrayRemoveValueAtIndex(addresses, i); } #endif // HANDLE_RMV_REQUESTS } if (CFArrayGetCount(addresses) > 0) { targetPrivate->dnsAddresses = addresses; targetPrivate->dnsError = NETDB_SUCCESS; } else { // if host not found targetPrivate->dnsAddresses = CFRetain(kCFNull); targetPrivate->dnsError = EAI_NONAME; CFRelease(addresses); } } break; case kDNSServiceErr_BadParam : _dns_mark(target, MARK_ERROR); if (targetPrivate->dnsAddresses != NULL) { CFRelease(targetPrivate->dnsAddresses); } targetPrivate->dnsAddresses = CFRetain(kCFNull); targetPrivate->dnsError = EAI_NONAME; break; case kDNSServiceErr_NoSuchRecord : _dns_mark(target, dnsMark); if (targetPrivate->dnsAddresses == NULL) { targetPrivate->dnsAddresses = CFRetain(kCFNull); targetPrivate->dnsError = EAI_NONAME; } break; case kDNSServiceErr_Timeout : _dns_mark(target, MARK_TIMEOUT); if (targetPrivate->dnsAddresses == NULL) { targetPrivate->dnsAddresses = CFRetain(kCFNull); targetPrivate->dnsError = EAI_NONAME; } break; default : SCLog(TRUE, LOG_ERR, CFSTR("%sSCNetworkReachability _dns_callback w/error=%d (n=%d)"), targetPrivate->log_prefix, errorCode, targetPrivate->dnsFailures + 1); // fall through case kDNSServiceErr_ServiceNotRunning : _dns_mark(target, MARK_ERROR); // bump per-target failure count failures = ++targetPrivate->dnsFailures; // Check to see if we've seen too many failures for this target if (failures > 2) { // if so, there's little point in retrying over // and over again so let's just return an error // and move on. if (targetPrivate->dnsAddresses != NULL) { CFRelease(targetPrivate->dnsAddresses); } targetPrivate->dnsAddresses = CFRetain(kCFNull); targetPrivate->dnsError = EAI_NONAME; } else if (targetPrivate->dnsGeneration == dnsGeneration) { // if not, then "discoveryd" crashed or some // other/unexpected error occurred. In this // case, we'll try again with a clean slate and // restart all requests. if (dnsMain != NULL) { DNSServiceRefDeallocate(dnsMain); dnsMain = NULL; dnsCount = 0; dnsGeneration++; restart = TRUE; } } break; } // update DNS failure count (and [re-]set to zero if we're OK) targetPrivate->dnsFailures = failures; MUTEX_UNLOCK(&targetPrivate->lock); if (restart) { SCLog(TRUE, LOG_DEBUG, CFSTR("%sreconnecting SCNetworkReachability w/\"discoveryd\" (%d)"), targetPrivate->log_prefix, dnsGeneration); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 500 * NSEC_PER_MSEC), _hn_changes_queue(), ^{ dns_refresh_handler(); }); // and flush the dnsUpdated queue as any DNS results we may have // accumulated are no longer valid. if (dnsUpdated != NULL) { CFRelease(dnsUpdated); dnsUpdated = NULL; } return; } if (targetPrivate->dnsHaveTimeout) { targetPrivate->dnsNoAddressesSinceLastTimeout = TRUE; } else if (targetPrivate->dnsNoAddressesSinceLastTimeout && isA_CFArray(targetPrivate->dnsAddresses) && CFArrayGetCount(targetPrivate->dnsAddresses) > 0) { targetPrivate->dnsNoAddressesSinceLastTimeout = FALSE; } // the "more coming" flag applies to DNSService callouts for any/all // hosts that are being watched so we need to keep track of the targets // we have updated. When we [finally] have the last callout then we // push our notifications for all of the updated targets. if (dnsUpdated == NULL) { dnsUpdated = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks); } CFSetAddValue(dnsUpdated, target); if (!(flags & kDNSServiceFlagsMoreComing)) { CFSetApplyFunction(dnsUpdated, _dns_notify, NULL); CFRelease(dnsUpdated); dnsUpdated = NULL; } return; } /* * _dns_getaddrinfo_callback * * Called to process [m]DNS query updates * - caller must be running on the _dns_queue() */ static void _dns_getaddrinfo_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *hostname, const struct sockaddr *address, uint32_t ttl, void *context) { CFDataRef dnsAddress = NULL; _dns_mark_t dnsMark = MARK_NONE; if (address != NULL) { switch (errorCode) { case kDNSServiceErr_NoError : dnsAddress = CFDataCreate(NULL, (void *)address, address->sa_len); // ... and fall through case kDNSServiceErr_NoSuchRecord : switch (address->sa_family) { case AF_INET : dnsMark = MARK_HAVE_V4; break; case AF_INET6 : dnsMark = MARK_HAVE_V6; break; } break; default : break; } } _dns_callback(sdRef, flags, errorCode, dnsMark, dnsAddress, context); if (dnsAddress != NULL) { CFRelease(dnsAddress); } return; } static CFStringRef _dns_copy_domain_name(const uint8_t *rdata, uint16_t rdlen) { CFMutableStringRef domain; const uint8_t *label; uint8_t label_len; domain = CFStringCreateMutable(NULL, 0); label = rdata; label_len = *(label++); while (label_len != 0) { while (label_len-- > 0) { uint8_t byte = *label++; if ((byte == '.') || (byte == '\\')) { // if escape needed CFStringAppendFormat(domain, NULL, CFSTR("\\%c"), byte); } else if (byte <= ' ') { CFStringAppendFormat(domain, NULL, CFSTR("\\%c%c%c"), '0' + (byte / 100), '0' + ((byte / 10) % 10), '0' + (byte % 10)); } else { CFStringAppendFormat(domain, NULL, CFSTR("%c"), byte); } } label_len = *(label++); if (label_len != 0) { CFStringAppendFormat(domain, NULL, CFSTR(".")); } } return domain; } static void _dns_queryrecord_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context) { _dns_mark_t dnsMark = MARK_NONE; CFStringRef dnsPTRName = NULL; SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)context; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; // for now, we only support using DNSServiceQueryRecord for PTR queries assert(targetPrivate->type == reachabilityTypePTR); if (rdata != NULL) { switch (errorCode) { case kDNSServiceErr_NoError : if (rrtype == kDNSServiceType_PTR) { dnsPTRName = _dns_copy_domain_name(rdata, rdlen); } // ... and fall through case kDNSServiceErr_NoSuchRecord : dnsMark = MARK_HAVE_PTR; break; default : break; } } _dns_callback(sdRef, flags, errorCode, dnsMark, dnsPTRName, context); if (dnsPTRName != NULL) { CFRelease(dnsPTRName); } return; } static Boolean enqueueDNSQuery(SCNetworkReachabilityRef target) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; MUTEX_ASSERT_HELD(&targetPrivate->lock); // clear DNS flags, mark the query active targetPrivate->dnsFlags = 0; targetPrivate->dnsActive = TRUE; // track the DNS resolution time __mark_operation_start(&targetPrivate->dnsQueryStart, &targetPrivate->dnsQueryEnd); CFRetain(target); dispatch_async(_dns_queue(), ^{ DNSServiceErrorType err; const char *fcn = "???"; DNSServiceRef sdRef = NULL; if (targetPrivate->dnsTarget != NULL) { // if already running CFRelease(target); return; } // if needed, start interacting with "discoveryd" if (dnsMain == NULL) { err = DNSServiceCreateConnection(&dnsMain); if (err != kDNSServiceErr_NoError) { SCLog(TRUE, LOG_ERR, CFSTR("%sDNSServiceCreateConnection(&dnsMain) failed, error = %d"), targetPrivate->log_prefix, err); goto done; } err = DNSServiceSetDispatchQueue(dnsMain, _dns_queue()); if (err != kDNSServiceErr_NoError) { SCLog(TRUE, LOG_ERR, CFSTR("%sDNSServiceSetDispatchQueue() failed, error = %d"), targetPrivate->log_prefix, err); DNSServiceRefDeallocate(dnsMain); dnsMain = NULL; dnsGeneration++; goto done; } } // start a query for this target sdRef = dnsMain; switch (targetPrivate->type) { case reachabilityTypeName : fcn = "DNSServiceGetAddrInfo"; err = DNSServiceGetAddrInfo(&sdRef, // sdRef kDNSServiceFlagsReturnIntermediates // flags | kDNSServiceFlagsShareConnection | kDNSServiceFlagsSuppressUnusable | kDNSServiceFlagsTimeout, targetPrivate->if_index, // interfaceIndex 0, // protocol targetPrivate->name, // hostname _dns_getaddrinfo_callback, // callback (void *)target); // context break; case reachabilityTypePTR : fcn = "DNSServiceQueryRecord"; err = DNSServiceQueryRecord(&sdRef, // sdRef kDNSServiceFlagsReturnIntermediates // flags | kDNSServiceFlagsShareConnection | kDNSServiceFlagsSuppressUnusable | kDNSServiceFlagsTimeout, targetPrivate->if_index, // interfaceIndex targetPrivate->name, // fullname kDNSServiceType_PTR, // rrtype kDNSServiceClass_IN, // rrclass _dns_queryrecord_callback, // callback (void *)target); // context break; default : err = kDNSServiceErr_Unknown; break; } switch (err) { case kDNSServiceErr_NoError : dnsCount++; break; default : SCLog(TRUE, LOG_ERR, CFSTR("%s%s() failed, error = %d (%d)"), targetPrivate->log_prefix, fcn, err, dnsCount); // fall through case kDNSServiceErr_BadParam : if (dnsCount == 0) { // if this was the first request DNSServiceRefDeallocate(dnsMain); dnsMain = NULL; dnsGeneration++; } sdRef = NULL; break; } done : MUTEX_LOCK(&targetPrivate->lock); if (err == kDNSServiceErr_NoError) { targetPrivate->dnsGeneration = dnsGeneration; targetPrivate->dnsTarget = sdRef; } else { targetPrivate->dnsActive = FALSE; // queue up the returned error dispatch_async(_dns_queue(), ^{ _dns_callback(NULL, // sdRef 0, // flags err, // errorCode MARK_ERROR, // dnsMark NULL, // dnsAddress (void *)target); // context CFRelease(target); }); } MUTEX_UNLOCK(&targetPrivate->lock); return; }); return TRUE; } static void dequeueDNSQuery(SCNetworkReachabilityRef target) { DNSServiceRef sdRef; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; MUTEX_ASSERT_HELD(&targetPrivate->lock); // terminate the [target] [m]DNS query sdRef = targetPrivate->dnsTarget; targetPrivate->dnsTarget = NULL; // mark the query NOT active targetPrivate->dnsActive = FALSE; // don't do anything if the sdRef is not valid if (sdRef != NULL) { int generation; generation = targetPrivate->dnsGeneration; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3LL * NSEC_PER_SEC), _dns_queue(), ^{ if (generation == dnsGeneration) { // if we're pointing to the same DNSService // generation as the main/active session // deallocate per-target query DNSServiceRefDeallocate(sdRef); dnsCount--; if (dnsCount == 0) { // if no more queries active DNSServiceRefDeallocate(dnsMain); dnsMain = NULL; dnsGeneration++; } } CFRelease(target); }); } if (targetPrivate->dnsAddresses != NULL) { CFRelease(targetPrivate->dnsAddresses); targetPrivate->dnsAddresses = NULL; } targetPrivate->dnsError = NETDB_SUCCESS; return; } #pragma mark - #pragma mark Synchronous DNS query support #define SYNC_DNS_QUERY_TIMEOUT_NSEC 35 * NSEC_PER_SEC // 35s static void sync_DNS_query_callback(SCNetworkReachabilityRef clone, SCNetworkReachabilityFlags cloneFlags, void *info) { dispatch_semaphore_t s = (dispatch_semaphore_t)info; dispatch_semaphore_signal(s); return; } static void sync_DNS_query(SCNetworkReachabilityRef target) { SCNetworkReachabilityRef clone; SCNetworkReachabilityPrivateRef clonePrivate; SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL }; dispatch_queue_t q; long ret; dispatch_semaphore_t s; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; clone = __SCNetworkReachabilityCreateCopy(target); if (clone == NULL) { return; } clonePrivate = (SCNetworkReachabilityPrivateRef)clone; q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); s = dispatch_semaphore_create(0); // start async query context.info = (void *)s; SCNetworkReachabilitySetCallback(clone, sync_DNS_query_callback, &context); SCNetworkReachabilitySetDispatchQueue(clone, q); // wait for reply (or timeout) ret = dispatch_semaphore_wait(s, dispatch_time(DISPATCH_TIME_NOW, SYNC_DNS_QUERY_TIMEOUT_NSEC)); if (ret != 0) { dispatch_sync(_dns_queue(), ^{ // mark as both a timeout *and* an error _dns_mark(clone, MARK_TIMEOUT); _dns_mark(clone, MARK_ERROR); __mark_operation_end(clone, FALSE, // if successful query dns_query_mdns_timeout, // [m]DNS query &clonePrivate->dnsQueryStart, // start time &clonePrivate->dnsQueryEnd); // end time MUTEX_LOCK(&clonePrivate->lock); // update target info with what's available if (clonePrivate->resolvedAddresses != NULL) { CFRelease(clonePrivate->resolvedAddresses); clonePrivate->resolvedAddresses = NULL; } if ((clonePrivate->dnsAddresses != NULL) && isA_CFArray(clonePrivate->dnsAddresses) && (CFArrayGetCount(clonePrivate->dnsAddresses) > 0)) { clonePrivate->resolvedAddresses = CFArrayCreateMutableCopy(NULL, 0, clonePrivate->dnsAddresses); } if (clonePrivate->resolvedAddresses != NULL) { // if timeout w/partial results clonePrivate->resolvedError = NETDB_SUCCESS; } else { // if timeout w/no results clonePrivate->resolvedAddresses = CFRetain(kCFNull); clonePrivate->resolvedError = EAI_NONAME; } MUTEX_UNLOCK(&clonePrivate->lock); }); } // cancel request SCNetworkReachabilitySetDispatchQueue(clone, NULL); SCNetworkReachabilitySetCallback(clone, NULL, NULL); // transfer reply if (clonePrivate->resolvedAddresses != NULL) CFRetain(clonePrivate->resolvedAddresses); if (targetPrivate->resolvedAddresses != NULL) CFRelease(targetPrivate->resolvedAddresses); targetPrivate->resolvedAddresses = clonePrivate->resolvedAddresses; targetPrivate->resolvedError = clonePrivate->resolvedError; targetPrivate->resolverFlags = clonePrivate->resolverFlags; targetPrivate->cycle = clonePrivate->cycle; targetPrivate->dnsFlags = clonePrivate->dnsFlags; memcpy(&targetPrivate->info, &clonePrivate->info, sizeof(ReachabilityInfo)); memcpy(&targetPrivate->last_notify, &clonePrivate->last_notify, sizeof(ReachabilityInfo)); CFRelease(clone); dispatch_release(s); return; } #pragma mark - #pragma mark Network Information support // Note: protected by _hn_target_queue() static int network_changed_token; static Boolean network_changed_token_valid = FALSE; /* * nwi_refresh_handler * * Called to notify/update network changed events * - should be exec'd on the _hn_changes_queue() */ static void nwi_refresh_handler() { CFArrayRef changes; CFStringRef key; __block SCDynamicStoreRef store = NULL; dispatch_sync(_hn_target_queue(), ^{ if (network_changed_token_valid && (hn_store != NULL)) { store = CFRetain(hn_store); } }); if (store == NULL) { return; } // Fake a network change. key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4); changes = CFArrayCreate(NULL, (const void **)&key, 1, &kCFTypeArrayCallBacks); __SCNetworkReachabilityHandleChanges(store, changes, NULL); CFRelease(changes); CFRelease(key); CFRelease(store); return; } /* * nwi_refresh_enable * * Called to monitor for network changes. * - caller must be running on the _hn_target_queue() * - passed in queue should be _hn_changes_queue() */ static Boolean nwi_refresh_enable(dispatch_queue_t q) { uint32_t status; status = notify_register_dispatch(_SC_NOTIFY_NETWORK_CHANGE_NWI, // trailing nwi_state_get_notify_key() &network_changed_token, q, ^(int token){ nwi_refresh_handler(); }); if (status != NOTIFY_STATUS_OK) { SCLog(TRUE, LOG_INFO, CFSTR("notify_register_dispatch() failed for network changes, status=%u"), status); return FALSE; } network_changed_token_valid = TRUE; return TRUE; } /* * nwi_refresh_disable * * Called to stop monitoring for network changes * - caller must be running on the _hn_target_queue() */ static void nwi_refresh_disable() { if (network_changed_token_valid) { (void)notify_cancel(network_changed_token); network_changed_token_valid = FALSE; } return; } #pragma mark - #pragma mark Sleep/wake support #if !TARGET_OS_IPHONE // Note: protected by _hn_target_queue() static IOPMConnection power_changed_connection = NULL; static const CFStringRef power_changed_key = CFSTR("*** EARLY WAKE ***"); /* * power_refresh_handler * * Called to notify/update power capability changed events * - should be exec'd on the _hn_changes_queue() */ static void power_refresh_handler(void *param, IOPMConnection connection, IOPMConnectionMessageToken token, IOPMSystemPowerStateCapabilities capabilities) { Boolean change; IOReturn ret; __block SCDynamicStoreRef store = NULL; dispatch_sync(_hn_target_queue(), ^{ if ((power_changed_connection != NULL) && (hn_store != NULL)) { store = CFRetain(hn_store); } }); if (store == NULL) { return; } // check for [relevant] changes change = ((power_capabilities ^ capabilities) & POWER_CAPABILITIES_NETWORK) != 0; // update capabilities power_capabilities = capabilities; if (change) { CFArrayRef changes; // fake a network change. changes = CFArrayCreate(NULL, (const void **)&power_changed_key, 1, &kCFTypeArrayCallBacks); __SCNetworkReachabilityHandleChanges(store, changes, NULL); CFRelease(changes); } ret = IOPMConnectionAcknowledgeEvent(connection, token); if (ret != kIOReturnSuccess) { SCLog(TRUE, LOG_ERR, CFSTR("IOPMConnectionAcknowledgeEvent failed, 0x%08x"), ret); } CFRelease(store); return; } /* * power_refresh_enable * * Called to monitor power changes. * - caller must be running on the _hn_target_queue() * - passed in queue should be _hn_changes_queue() */ static Boolean power_refresh_enable(dispatch_queue_t q) { IOPMConnection connection = NULL; IOReturn ret; ret = IOPMConnectionCreate(CFSTR("com.apple.SCNetworkReachability"), kIOPMEarlyWakeNotification | kIOPMSleepWakeInterest, &connection); if (ret != kIOReturnSuccess) { SCLog(TRUE, LOG_ERR, CFSTR("IOPMConnectionCreate failed, 0x%08x"), ret); goto failed; } ret = IOPMConnectionSetNotification(connection, NULL, power_refresh_handler); if (ret != kIOReturnSuccess) { SCLog(TRUE, LOG_ERR, CFSTR("IOPMConnectionSetNotification failed, 0x%08x"), ret); goto failed; } power_changed_connection = connection; IOPMConnectionSetDispatchQueue(connection, q); power_capabilities = IOPMConnectionGetSystemCapabilities(); return TRUE; failed: if (connection != NULL) { IOPMConnectionRelease(connection); } return FALSE; } static void power_refresh_disable() { if (power_changed_connection != NULL) { IOPMConnectionSetDispatchQueue(power_changed_connection, NULL); IOPMConnectionRelease(power_changed_connection); power_changed_connection = NULL; } return; } #endif // !TARGET_OS_IPHONE #pragma mark - #pragma mark OnDemand SCNetworkServiceRef SCNetworkReachabilityCopyOnDemandService(SCNetworkReachabilityRef target, CFDictionaryRef *userOptions) { SCNetworkServiceRef service = NULL; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } if (targetPrivate->onDemandServiceID != NULL) { service = _SCNetworkServiceCopyActive(NULL, targetPrivate->onDemandServiceID); } if (userOptions != NULL) { if (targetPrivate->onDemandName != NULL) { *userOptions = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue((CFMutableDictionaryRef)*userOptions, kSCNetworkConnectionSelectionOptionOnDemandHostName, targetPrivate->onDemandName); } else { *userOptions = NULL; } } return service; } static void __SCNetworkReachabilityOnDemandCheckCallback(SCNetworkReachabilityRef onDemandServer, SCNetworkReachabilityFlags onDemandFlags, void *info) { SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)info; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; MUTEX_LOCK(&targetPrivate->lock); if (!targetPrivate->scheduled) { // if not currently scheduled MUTEX_UNLOCK(&targetPrivate->lock); return; } SCLog(_sc_debug, LOG_INFO, CFSTR("%sOnDemand \"server\" status changed (now 0x%08x)"), targetPrivate->log_prefix, onDemandFlags); if (targetPrivate->type == reachabilityTypeName) { // make sure that we resolve the name again targetPrivate->needResolve = TRUE; } __SCNetworkReachabilityUpdate(target); MUTEX_UNLOCK(&targetPrivate->lock); return; } static Boolean __SCNetworkReachabilityOnDemandCheck(ReachabilityStoreInfoRef store_info, SCNetworkReachabilityRef target, Boolean onDemandRetry, SCNetworkReachabilityFlags *flags) { SCNetworkConnectionRef connection = NULL; SCNetworkConnectionType connectionType = kSCNetworkConnectionTypeUnknown; Boolean isAppLayerVPN = FALSE; Boolean isOnDemandService = FALSE; Boolean ok = FALSE; CFStringRef onDemandRemoteAddress = NULL; CFStringRef onDemandServiceID = NULL; SCNetworkConnectionStatus onDemandStatus = kSCNetworkConnectionInvalid; CFMutableDictionaryRef selectOptions = NULL; Boolean success = FALSE; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; MUTEX_ASSERT_HELD(&targetPrivate->lock); if (targetPrivate->onDemandName == NULL) { targetPrivate->onDemandName = CFStringCreateWithCString(NULL, targetPrivate->name, kCFStringEncodingUTF8); } /* * check if an OnDemand VPN configuration matches the name. */ connection = SCNetworkConnectionCreate(kCFAllocatorDefault, NULL, NULL); if (connection == NULL) { goto done; } /* set select options */ selectOptions = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (selectOptions == NULL) { goto done; } CFDictionaryAddValue(selectOptions, kSCNetworkConnectionSelectionOptionOnDemandHostName, targetPrivate->onDemandName); CFDictionaryAddValue(selectOptions, kSCNetworkConnectionSelectionOptionOnDemandRetry, onDemandRetry ? kCFBooleanTrue : kCFBooleanFalse); CFDictionaryAddValue(selectOptions, kSCNetworkConnectionSelectionOptionNoUserPrefs, kCFBooleanTrue); /* select service. May be On Demand or App Layer VPN */ if (!SCNetworkConnectionSelectServiceWithOptions(connection, selectOptions)) { goto done; } /* get reachability flags (of VPN server) */ (void) SCNetworkConnectionGetReachabilityInfo(connection, flags, NULL); connectionType = SCNetworkConnectionGetType(connection); if (connectionType == kSCNetworkConnectionTypeAppLayerVPN) { isAppLayerVPN = TRUE; } /* get on-demand info */ onDemandServiceID = SCNetworkConnectionCopyServiceID(connection); if (SCNetworkConnectionCopyOnDemandInfo(connection, &onDemandRemoteAddress, &onDemandStatus)) { if (onDemandRemoteAddress != NULL) { isOnDemandService = TRUE; ok = TRUE; } } /* handle non-OnDemand App Layer VPN */ if (isAppLayerVPN && !isOnDemandService) { SCLog(_sc_debug, LOG_INFO, CFSTR("%s status * = 0x%08x (App Layer VPN)"), targetPrivate->log_prefix, *flags); if (*flags & kSCNetworkReachabilityFlagsReachable) { // if VPN "server" is reachable if (!(*flags & kSCNetworkReachabilityFlagsTransientConnection)) { // start w/clean flags if not already layered on a transient network *flags = kSCNetworkReachabilityFlagsReachable; } *flags |= kSCNetworkReachabilityFlagsTransientConnection; if (onDemandStatus != kSCNetworkConnectionConnected) { *flags |= kSCNetworkReachabilityFlagsConnectionRequired; } SCLog(_sc_debug, LOG_INFO, CFSTR("%s status = isReachable%s"), (onDemandStatus != kSCNetworkConnectionConnected) ? " (after App Layer connect)" : "", targetPrivate->log_prefix); } success = TRUE; goto done; } if (!_SC_CFEqual(targetPrivate->onDemandRemoteAddress, onDemandRemoteAddress) || !_SC_CFEqual(targetPrivate->onDemandServiceID, onDemandServiceID)) { if (targetPrivate->onDemandRemoteAddress != NULL) { CFRelease(targetPrivate->onDemandRemoteAddress); targetPrivate->onDemandRemoteAddress = NULL; } if (targetPrivate->onDemandServer != NULL) { SCNetworkReachabilitySetCallback(targetPrivate->onDemandServer, NULL, NULL); if (targetPrivate->dispatchQueue != NULL) { // unschedule __SCNetworkReachabilityUnscheduleFromRunLoop(targetPrivate->onDemandServer, NULL, NULL, TRUE); } else if (targetPrivate->rls != NULL) { CFIndex i; CFIndex n; // unschedule n = CFArrayGetCount(targetPrivate->rlList); for (i = 0; i < n; i += 3) { CFRunLoopRef rl = (CFRunLoopRef)CFArrayGetValueAtIndex(targetPrivate->rlList, i+1); CFStringRef rlMode = (CFStringRef) CFArrayGetValueAtIndex(targetPrivate->rlList, i+2); __SCNetworkReachabilityUnscheduleFromRunLoop(targetPrivate->onDemandServer, rl, rlMode, TRUE); } } CFRelease(targetPrivate->onDemandServer); targetPrivate->onDemandServer = NULL; } if (targetPrivate->onDemandServiceID != NULL) { CFRelease(targetPrivate->onDemandServiceID); targetPrivate->onDemandServiceID = NULL; } } if (ok) { if (onDemandStatus != kSCNetworkConnectionConnected) { /* * if we have a VPN configuration matching the name *and* we need to * bring the VPN up. Combine our flags with those of the VPN server. */ if (targetPrivate->onDemandServer == NULL) { SCNetworkReachabilityPrivateRef demandPrivate; CFMutableDictionaryRef options; options = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(options, kSCNetworkReachabilityOptionNodeName, onDemandRemoteAddress); CFDictionarySetValue(options, kSCNetworkReachabilityOptionConnectionOnDemandBypass, kCFBooleanTrue); if (targetPrivate->serverBypass) { CFDictionarySetValue(options, kSCNetworkReachabilityOptionServerBypass, kCFBooleanTrue); } targetPrivate->onDemandServer = SCNetworkReachabilityCreateWithOptions(NULL, options); CFRelease(options); // indent OnDemand target demandPrivate = (SCNetworkReachabilityPrivateRef)targetPrivate->onDemandServer; strlcat(demandPrivate->log_prefix, ".... ", sizeof(demandPrivate->log_prefix)); if (targetPrivate->scheduled) { SCNetworkReachabilityContext context = { 0, NULL, CFRetain, CFRelease, CFCopyDescription }; context.info = (void *)target; SCNetworkReachabilitySetCallback(targetPrivate->onDemandServer, __SCNetworkReachabilityOnDemandCheckCallback, &context); // schedule server reachability to match that of the target if (targetPrivate->dispatchQueue != NULL) { __SCNetworkReachabilityScheduleWithRunLoop(targetPrivate->onDemandServer, NULL, NULL, targetPrivate->dispatchQueue, TRUE); } else { CFIndex i; CFIndex n; n = CFArrayGetCount(targetPrivate->rlList); for (i = 0; i < n; i += 3) { CFRunLoopRef rl = (CFRunLoopRef)CFArrayGetValueAtIndex(targetPrivate->rlList, i+1); CFStringRef rlMode = (CFStringRef) CFArrayGetValueAtIndex(targetPrivate->rlList, i+2); __SCNetworkReachabilityScheduleWithRunLoop(targetPrivate->onDemandServer, rl, rlMode, NULL, TRUE); } } } } SCLog(_sc_debug, LOG_INFO, CFSTR("%s status * = 0x%08x"), targetPrivate->log_prefix, *flags); if ((*flags & kSCNetworkReachabilityFlagsReachable) && !(*flags & kSCNetworkReachabilityFlagsConnectionRequired)) { // if VPN "server" is [still] reachable if (!(*flags & kSCNetworkReachabilityFlagsTransientConnection)) { // start w/clean flags if not already layered on a transient network *flags = kSCNetworkReachabilityFlagsReachable; } *flags |= kSCNetworkReachabilityFlagsTransientConnection; *flags |= kSCNetworkReachabilityFlagsConnectionRequired; *flags |= kSCNetworkReachabilityFlagsConnectionOnDemand; // set 'InterventionRequired' if the OnDemand connection is paused if (SCNetworkConnectionIsOnDemandSuspended(connection)) { *flags |= kSCNetworkReachabilityFlagsInterventionRequired; } if (_sc_debug) { SCLog(TRUE, LOG_INFO, CFSTR("%s service * = %@"), targetPrivate->log_prefix, onDemandServiceID); SCLog(TRUE, LOG_INFO, CFSTR("%s status = isReachable (after OnDemand connect)"), targetPrivate->log_prefix); } success = TRUE; } } if (onDemandRemoteAddress != NULL) { if (targetPrivate->onDemandRemoteAddress == NULL) { targetPrivate->onDemandRemoteAddress = CFRetain(onDemandRemoteAddress); } } if (onDemandServiceID != NULL) { if (targetPrivate->onDemandServiceID == NULL) { targetPrivate->onDemandServiceID = CFRetain(onDemandServiceID); } } } done: if (onDemandServiceID != NULL) { CFRelease(onDemandServiceID); } if (onDemandRemoteAddress != NULL) { CFRelease(onDemandRemoteAddress); } if (connection != NULL) { CFRelease(connection); } if (selectOptions != NULL) { CFRelease(selectOptions); } return success; } /* * OnDemand configuration handling * * Notes : * * 1. We have a "contract" with discoveryd that for EVERY network * or DNS configuration change that should warrant our [re-]starting * a query, discoveryd will acknowledge the latest DNS configuration. * * 2. IPMonitor also posts a notification AFTER every network or DNS * configuration change. * * 3. We use IPMonitor's "trailing edge" as a signal to restart any * by-name queries. */ // Note: protected by _hn_target_queue() static int onDemand_refresh_token; static Boolean onDemand_refresh_token_valid = FALSE; /* * onDemand_refresh_handler * * Called to notify/update all SCNetworkReachability targets of * OnDemand changes. * - should be exec'd on the _hn_changes_queue() */ static void onDemand_refresh_handler() { CFArrayRef changes; CFStringRef key; __block SCDynamicStoreRef store = NULL; dispatch_sync(_hn_target_queue(), ^{ if (onDemand_refresh_token_valid && (hn_store != NULL)) { store = CFRetain(hn_store); } }); if (store == NULL) { return; } key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetOnDemand); changes = CFArrayCreate(NULL, (const void **)&key, 1, &kCFTypeArrayCallBacks); __SCNetworkReachabilityHandleChanges(store, changes, NULL); CFRelease(changes); CFRelease(key); CFRelease(store); return; } /* * onDemand_refresh_enable * * Called to monitor for OnDemand changes. * - caller must be running on the _hn_target_queue() */ static Boolean onDemand_refresh_enable(dispatch_queue_t q) { uint32_t status; status = notify_register_dispatch(kSCNETWORKCONNECTION_ONDEMAND_NOTIFY_KEY, &onDemand_refresh_token, q, ^(int token){ onDemand_refresh_handler(); }); if (status != NOTIFY_STATUS_OK) { SCLog(TRUE, LOG_INFO, CFSTR("notify_register_dispatch() failed, status=%u"), status); return FALSE; } onDemand_refresh_token_valid = TRUE; return TRUE; } /* * onDemand_refresh_disable * * Called to stop monitoring for OnDemand changes * - caller must be running on the _hn_target_queue() */ static void onDemand_refresh_disable() { (void)notify_cancel(onDemand_refresh_token); onDemand_refresh_token_valid = FALSE; return; } #pragma mark - #pragma mark Reachability Flags static Boolean __SCNetworkReachabilityGetFlags(ReachabilityStoreInfoRef store_info, SCNetworkReachabilityRef target, ReachabilityInfo *reach_info, Boolean async) { CFMutableArrayRef addresses = NULL; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; ReachabilityInfo my_info = NOT_REACHABLE; Boolean ok = TRUE; MUTEX_ASSERT_HELD(&targetPrivate->lock); _reach_set(reach_info, &NOT_REACHABLE, reach_info->cycle, targetPrivate->if_index, targetPrivate->if_name); if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } #if TARGET_OS_IPHONE if (isReachabilityTypeName(targetPrivate->type) && !async && pthread_is_threaded_np() && pthread_main_np()) { SCLog(TRUE, LOG_WARNING, CFSTR("Warning: sync SCNetworkReachability (by-name) query on main thread")); } #endif // TARGET_OS_IPHONE if (!targetPrivate->serverBypass) { if (!targetPrivate->serverActive) { ok = __SCNetworkReachabilityServer_targetAdd(target); if (!ok) { targetPrivate->serverBypass = TRUE; } } if (targetPrivate->serverActive) { ok = __SCNetworkReachabilityServer_targetStatus(target); if (!ok) { SCLog(TRUE, LOG_DEBUG, CFSTR("__SCNetworkReachabilityGetFlags _targetStatus() failed")); _SCErrorSet(kSCStatusFailed); goto done; } targetPrivate->cycle = targetPrivate->serverInfo.cycle; _reach_set(&my_info, &targetPrivate->serverInfo, targetPrivate->serverInfo.cycle, targetPrivate->if_index, targetPrivate->if_name); goto done; } } switch (targetPrivate->type) { case reachabilityTypeAddress : case reachabilityTypeAddressPair : { /* * Check "local" address */ if (targetPrivate->localAddress != NULL) { /* * Check "local" address */ ok = checkAddress(store_info, targetPrivate->localAddress, targetPrivate->if_index, &my_info, targetPrivate->log_prefix); if (!ok) { goto done2; /* not today */ } if (!(my_info.flags & kSCNetworkReachabilityFlagsIsLocalAddress)) { goto done2; /* not reachable, non-"local" address */ } } /* * Check "remote" address */ if ((targetPrivate->remoteAddress != NULL) && (targetPrivate->localAddress != targetPrivate->remoteAddress)) { /* * in cases where we have different "local" and "remote" addresses * we need to re-initialize the to-be-returned flags. */ my_info = NOT_REACHABLE; /* * Check "remote" address */ ok = checkAddress(store_info, targetPrivate->remoteAddress, targetPrivate->if_index, &my_info, targetPrivate->log_prefix); if (!ok) { goto done2; /* not today */ } } break; } case reachabilityTypeName : case reachabilityTypePTR : { int error; int ns_dns_config = -1; SCNetworkReachabilityFlags ns_flags = 0; uint32_t ns_if_index = 0; addresses = (CFMutableArrayRef)SCNetworkReachabilityCopyResolvedAddress(target, &error); if ((addresses != NULL) || (error != NETDB_SUCCESS)) { /* if resolved or an error had been detected */ if (!async) { /* if not an async request */ goto checkResolvedAddresses; } else if (targetPrivate->dnsActive) { /* if [m]DNS query active */ goto checkResolvedAddresses; } else if (!targetPrivate->needResolve) { /* * if this is an async request (i.e. someone is watching the reachability * of this target), if no query active, and if no query is needed */ goto checkResolvedAddresses; } } if (!targetPrivate->onDemandBypass) { Boolean onDemand; SCNetworkReachabilityFlags onDemandFlags = 0; /* * before we attempt our initial DNS query, check if there is * an OnDemand configuration that we should be using. */ onDemand = __SCNetworkReachabilityOnDemandCheck(store_info, target, FALSE, &onDemandFlags); if (onDemand) { /* if OnDemand connection is needed */ my_info.flags = onDemandFlags; goto done; } } targetPrivate->dnsBlocked = FALSE; /* update the reachability of the DNS servers */ _SC_R_updateResolverReachability(store_info, &ns_flags, &targetPrivate->haveDNS, targetPrivate->name, targetPrivate->if_index, &ns_if_index, &ns_dns_config, targetPrivate->log_prefix); // save resolver reachability flags targetPrivate->resolverFlags = ns_flags; if (rankReachability(ns_flags) < 2) { /* * if DNS servers are not (or are no longer) reachable, set * flags based on the availability of configured (but not * active) services. */ SCLog(_sc_debug, LOG_INFO, CFSTR("%sDNS server(s) not available"), targetPrivate->log_prefix); if (!targetPrivate->dnsBlocked) { ok = checkAddress(store_info, NULL, targetPrivate->if_index, &my_info, targetPrivate->log_prefix); if (!ok) { SCLog(_sc_debug, LOG_INFO, CFSTR("%sNo available networks"), targetPrivate->log_prefix); goto done2; } } else { // if not checking "available" networks my_info.flags = ns_flags; my_info.if_index = ns_if_index; } if (async && targetPrivate->scheduled) { /* * return "host not found", set flags appropriately, * and schedule notification. */ __SCNetworkReachabilitySetResolvedError(target, EAI_NONAME); my_info.flags |= (targetPrivate->info.flags & kSCNetworkReachabilityFlagsFirstResolvePending); SCLog(_sc_debug, LOG_INFO, CFSTR("%sno DNS servers are reachable"), targetPrivate->log_prefix); __SCNetworkReachabilityUpdate(target); } break; } if (targetPrivate->resolverBypass) { if (targetPrivate->haveDNS) { /* * if we are not resolving the name, and if we have * one or more DNS resolvers, then return flags that * reflect the reachability of the resolvers (and * not the actual name). */ my_info.flags = ns_flags; my_info.if_index = ns_if_index; } break; } if (async) { /* for async requests we return the last known status */ my_info = targetPrivate->info; if (targetPrivate->dnsActive) { /* if [m]DNS query active */ if (_sc_debug && !targetPrivate->quiet) { SCLog(TRUE, LOG_INFO, CFSTR("%swaiting for DNS reply"), targetPrivate->log_prefix); } if ((addresses != NULL) || (error != NETDB_SUCCESS)) { /* updated reachability based on the previous reply */ goto checkResolvedAddresses; } break; } SCLog(_sc_debug, LOG_INFO, CFSTR("%sstart DNS query for name = %s"), targetPrivate->log_prefix, targetPrivate->name); /* * initiate an DNS query w/DNSServiceGetAddrInfo */ enqueueDNSQuery(target); break; } SCLog(_sc_debug, LOG_INFO, CFSTR("%scheckName(%s)"), targetPrivate->log_prefix, targetPrivate->name); /* * OK, all of the DNS name servers are available. Let's * resolve the nodename into an address. */ sync_DNS_query(target); if (!(targetPrivate->dnsHaveTimeout && targetPrivate->dnsHaveError)) { // if target reach info is valid memcpy(reach_info, &targetPrivate->info, sizeof(ReachabilityInfo)); goto done2; } if (addresses != NULL) CFRelease(addresses); addresses = (CFMutableArrayRef)SCNetworkReachabilityCopyResolvedAddress(target, &error); checkResolvedAddresses : /* * We first assume that the requested host is NOT available. * Then, check each address for accessibility and return the * best status available. */ my_info = NOT_REACHABLE; if ((targetPrivate->type == reachabilityTypeName) && isA_CFArray(addresses)) { CFIndex i; CFIndex n = CFArrayGetCount(addresses); struct sockaddr *sa; for (i = 0; i < n; i++) { ReachabilityInfo ns_info = NOT_REACHABLE; sa = (struct sockaddr *)CFDataGetBytePtr(CFArrayGetValueAtIndex(addresses, i)); ok = checkAddress(store_info, sa, targetPrivate->if_index, &ns_info, targetPrivate->log_prefix); if (!ok) { goto done2; /* not today */ } if (rankReachability(ns_info.flags) > rankReachability(my_info.flags)) { /* return the best case result */ my_info = ns_info; if (rankReachability(my_info.flags) == 2) { /* can't get any better than REACHABLE */ break; } } } if (_sc_debug) { for (i++; i < n; i++) { sa = (struct sockaddr *)CFDataGetBytePtr(CFArrayGetValueAtIndex(addresses, i)); log_address("skipAddress", sa, targetPrivate->if_index, targetPrivate->log_prefix); } } } else if ((targetPrivate->type == reachabilityTypePTR) && isA_CFArray(addresses)) { CFIndex i; CFIndex n = CFArrayGetCount(addresses); my_info = NOT_REACHABLE; for (i = 0; i < n; i++) { if (i == 0) { my_info.flags = kSCNetworkReachabilityFlagsReachable; } if (_sc_debug) { CFStringRef ptrName; ptrName = CFArrayGetValueAtIndex(addresses, i); SCLog(TRUE, LOG_INFO, CFSTR("%sPTR name(%@)"), targetPrivate->log_prefix, ptrName); } } } else { if ((error == EAI_NONAME) #if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) || (error == EAI_NODATA) #endif ) { /* * the target host name could not be resolved */ if (!targetPrivate->onDemandBypass) { Boolean onDemand; SCNetworkReachabilityFlags onDemandFlags = 0; /* * our initial DNS query failed, check again to see if there * there is an OnDemand configuration that we should be using. */ onDemand = __SCNetworkReachabilityOnDemandCheck(store_info, target, TRUE, &onDemandFlags); if (onDemand) { /* if OnDemand connection is needed */ my_info.flags = onDemandFlags; goto done; } } if (!targetPrivate->haveDNS) { /* * No DNS servers are defined. Set flags based on * the availability of configured (but not active) * services. */ ok = checkAddress(store_info, NULL, targetPrivate->if_index, &my_info, targetPrivate->log_prefix); if (!ok) { goto done2; /* not today */ } if ((my_info.flags & kSCNetworkReachabilityFlagsReachable) && (my_info.flags & kSCNetworkReachabilityFlagsConnectionRequired)) { /* * Since we might pick up a set of DNS servers when this connection * is established, don't reply with a "HOST NOT FOUND" error just yet. */ break; } /* Host not found, not reachable! */ my_info = NOT_REACHABLE; } } } break; } } done: _reach_set(reach_info, &my_info, targetPrivate->cycle, targetPrivate->if_index, targetPrivate->if_name); done2 : if (addresses != NULL) CFRelease(addresses); return ok; } int SCNetworkReachabilityGetInterfaceIndex(SCNetworkReachabilityRef target) { int if_index = -1; Boolean ok = TRUE; ReachabilityStoreInfo store_info; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return if_index; } ReachabilityStoreInfo_init(&store_info); MUTEX_LOCK(&targetPrivate->lock); if (targetPrivate->scheduled) { // if being watched, return the last known (and what should be current) status goto done; } ok = __SCNetworkReachabilityGetFlags(&store_info, target, &targetPrivate->info, FALSE); done : /* Only return the if_index if the connection is reachable not for reachable connection * required etc ... */ if (ok && rankReachability(targetPrivate->info.flags) == 2) { if_index = targetPrivate->info.if_index; } MUTEX_UNLOCK(&targetPrivate->lock); ReachabilityStoreInfo_free(&store_info); return if_index; } Boolean SCNetworkReachabilityGetFlags(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags *flags) { Boolean ok = TRUE; ReachabilityStoreInfo store_info; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } ReachabilityStoreInfo_init(&store_info); MUTEX_LOCK(&targetPrivate->lock); if (targetPrivate->scheduled) { // if being watched, return the last known (and what should be current) status *flags = targetPrivate->info.flags & kSCNetworkReachabilityFlagsMask; if (isReachabilityTypeName(targetPrivate->type) && targetPrivate->dnsNoAddressesSinceLastTimeout) { targetPrivate->needResolve = TRUE; ReachabilityInfo tmp_reach_info = NOT_REACHABLE; __SCNetworkReachabilityGetFlags(&store_info, target, &tmp_reach_info, TRUE); } goto done; } ok = __SCNetworkReachabilityGetFlags(&store_info, target, &targetPrivate->info, FALSE); if (_sc_debug) { SCLog(TRUE, LOG_INFO, CFSTR("%s flags = 0x%08x"), targetPrivate->log_prefix, targetPrivate->info.flags); } *flags = targetPrivate->info.flags & kSCNetworkReachabilityFlagsMask; done : MUTEX_UNLOCK(&targetPrivate->lock); ReachabilityStoreInfo_free(&store_info); return ok; } #pragma mark - #pragma mark Notifications /* * __SCNetworkReachabilityHandleChanges * * Called to process network configuration changes and determine * if a reachability notification is warranted. * - should be exec'd on the _hn_changes_queue() */ static void __SCNetworkReachabilityHandleChanges(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info) { Boolean dnsConfigChanged = FALSE; CFIndex i; Boolean forcedChange = FALSE; CFStringRef key; Boolean match; CFIndex nChanges; CFIndex nGlobals = 0; CFIndex nTargets; Boolean neChanged = FALSE; Boolean networkConfigChanged = FALSE; struct timeval now; Boolean onDemandConfigChanged = FALSE; #if !TARGET_OS_IPHONE Boolean powerStatusChanged = FALSE; #endif // !TARGET_OS_IPHONE ReachabilityStoreInfo store_info; const void * targets_q[N_QUICK]; const void ** targets = targets_q; __block CFSetRef watchers = NULL; nChanges = CFArrayGetCount(changedKeys); if (nChanges == 0) { /* if no changes */ return; } dispatch_sync(_hn_target_queue(), ^{ /* grab the currently watched targets */ if (hn_targets != NULL) { watchers = CFSetCreateCopy(NULL, hn_targets); } }); nTargets = (watchers != NULL) ? CFSetGetCount(watchers) : 0; if (nTargets == 0) { /* if no addresses being monitored */ goto done; } /* grab the current time */ (void)gettimeofday(&now, NULL); #if !TARGET_OS_IPHONE match = CFArrayContainsValue(changedKeys, CFRangeMake(0, nChanges), power_changed_key); if (match) { /* handle "early" wake notification */ nGlobals++; powerStatusChanged = TRUE; } #endif // !TARGET_OS_IPHONE key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetDNS); match = CFArrayContainsValue(changedKeys, CFRangeMake(0, nChanges), key); CFRelease(key); if (match) { nGlobals++; dnsConfigChanged = TRUE; /* the DNS server(s) have changed */ } key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetOnDemand); match = CFArrayContainsValue(changedKeys, CFRangeMake(0, nChanges), key); CFRelease(key); if (match) { nGlobals++; onDemandConfigChanged = TRUE; /* the OnDemand configuration has changed */ // force OnDemand configuration refresh (if SC notification arrives before BSD notify) __SCNetworkConnectionForceOnDemandConfigurationRefresh(); } match = CFArrayContainsValue(changedKeys, CFRangeMake(0, nChanges), SCNETWORKREACHABILITY_TRIGGER_KEY); if (match) { nGlobals++; forcedChange = TRUE; /* an SCDynamicStore driven "network" change */ } if (nChanges > nGlobals) { networkConfigChanged = TRUE; } if (_sc_debug) { unsigned int changes = 0; static const char *change_strings[] = { // with no "power" status change "", // 00000 "network ", // 00001 "DNS ", // 00010 "network and DNS ", // 00011 "OnDemand ", // 00100 "network and OnDemand ", // 00101 "DNS and OnDemand ", // 00110 "network, DNS, and OnDemand ", // 00111 "NE ", // 01000 "network and NE ", // 01001 "DNS and NE ", // 01010 "network, DNS, and NE ", // 01011 "OnDemand and NE ", // 01100 "network, OnDemand, and NE ", // 01101 "DNS, OnDemand, and NE ", // 01110 "network, DNS, OnDemand, and NE ", // 01111 #if !TARGET_OS_IPHONE // with "power" status change "power", // 10000 "network and power ", // 10001 "DNS and power ", // 10010 "network, DNS, and power ", // 10011 "OnDemand and power ", // 10100 "network, OnDemand, and power ", // 10101 "DNS, OnDemand, and power ", // 10110 "network, DNS, OnDemand, and power ", // 10111 "NE and power ", // 11000 "network, NE, and power ", // 11001 "DNS, NE, and power ", // 11010 "network, DNS, NE, and power ", // 11011 "OnDemand, NE, and power ", // 11100 "network, OnDemand, NE, and power ", // 11101 "DNS, OnDemand, NE, and power ", // 11110 "network, DNS, OnDemand, NE, and power ", // 11111 #endif // !TARGET_OS_IPHONE }; #if !TARGET_OS_IPHONE #define PWR 16 if (powerStatusChanged) { changes |= PWR; } #endif // !TARGET_OS_IPHONE #define NE 8 if (neChanged) { changes |= NE; } #define VOD 4 if (onDemandConfigChanged) { changes |= VOD; } #define DNS 2 if (dnsConfigChanged) { changes |= DNS; } #define NET 1 if (networkConfigChanged) { changes |= NET; } SCLog(TRUE, LOG_INFO, CFSTR("process %s%s%sconfiguration change"), forcedChange ? "[forced] " : "", change_strings[changes]); } ReachabilityStoreInfo_init(&store_info); if (nTargets > (CFIndex)(sizeof(targets_q) / sizeof(CFTypeRef))) targets = CFAllocatorAllocate(NULL, nTargets * sizeof(CFTypeRef), 0); CFSetGetValues(watchers, targets); for (i = 0; i < nTargets; i++) { Boolean dnsNeedsUpdate = FALSE; SCNetworkReachabilityRef target = targets[i]; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; MUTEX_LOCK(&targetPrivate->lock); if (dnsConfigChanged) { targetPrivate->last_dns = now; } if (networkConfigChanged) { targetPrivate->last_network = now; } #if !TARGET_OS_IPHONE if (powerStatusChanged) { targetPrivate->last_power = now; } #endif // !TARGET_OS_IPHONE if (isReachabilityTypeName(targetPrivate->type)) { Boolean dnsChanged = (dnsConfigChanged | dnsNeedsUpdate | onDemandConfigChanged | neChanged); if (!dnsChanged) { /* * if the DNS configuration didn't change we still need to * check that the DNS servers are accessible. */ Boolean ns_blocked = FALSE; int ns_dns_config = -1; SCNetworkReachabilityFlags ns_flags = 0; uint32_t ns_if_index = 0; Boolean ok; /* update the reachability of the DNS servers */ ok = ReachabilityStoreInfo_update(&store_info, &store, AF_UNSPEC); if (ok) { _SC_R_updateResolverReachability(&store_info, &ns_flags, &targetPrivate->haveDNS, targetPrivate->name, targetPrivate->if_index, &ns_if_index, &ns_dns_config, targetPrivate->log_prefix); } else { ns_flags = kSCNetworkReachabilityFlagsReachable; dnsChanged = TRUE; } if (rankReachability(ns_flags) < 2) { /* * if DNS servers are not (or are no longer) reachable, set * flags based on the availability of configured (but not * active) services. */ SCLog(_sc_debug, LOG_INFO, CFSTR("%sDNS server(s) not available"), targetPrivate->log_prefix); dnsChanged = TRUE; } if ((targetPrivate->dnsBlocked != ns_blocked) || (targetPrivate->resolverFlags != ns_flags)) { // if the DNS blocked or resolver reachability changed targetPrivate->dnsBlocked = ns_blocked; targetPrivate->resolverFlags = ns_flags; dnsChanged = TRUE; } } if (dnsChanged) { if (targetPrivate->dnsActive) { // if we have an outstanding [m]DNS query SCLog(_sc_debug, LOG_INFO, CFSTR("%scancel [m]DNS query for name = %s"), targetPrivate->log_prefix, targetPrivate->name); dequeueDNSQuery(target); } /* schedule request to resolve the name again */ targetPrivate->needResolve = TRUE; } } if (forcedChange) { targetPrivate->cycle++; } if (targetPrivate->scheduled) { __SCNetworkReachabilityUpdate(target); } MUTEX_UNLOCK(&targetPrivate->lock); } if (targets != targets_q) CFAllocatorDeallocate(NULL, targets); ReachabilityStoreInfo_free(&store_info); done : if (watchers != NULL) CFRelease(watchers); return; } /* * __SCNetworkReachabilityHandleStoreChanges * * Called to process SCDynamicStore network configuration changes. * - should be exec'd on the _hn_changes_queue() */ static void __SCNetworkReachabilityHandleStoreChanges(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info) { nwi_state_t nwi_state; if ((CFArrayGetCount(changedKeys) == 1) && CFArrayContainsValue(changedKeys, CFRangeMake(0, 1), SCNETWORKREACHABILITY_TRIGGER_KEY)) { goto update; } /* "something" [else] changed, start fresh */ ReachabilityStoreInfo_save(NULL); nwi_state = nwi_state_copy(); if (nwi_state != NULL) { // if we have some networking nwi_state_release(nwi_state); return; } // if no networking, use the [SC] changes to add/update // the kSCNetworkReachabilityFlagsConnectionRequired flag update : __SCNetworkReachabilityHandleChanges(store, changedKeys, info); return; } #if !TARGET_OS_IPHONE static Boolean darkWakeNotify(SCNetworkReachabilityRef target) { return FALSE; } static Boolean systemIsAwake(IOPMSystemPowerStateCapabilities power_capabilities) { if ((power_capabilities & POWER_CAPABILITIES_NETWORK) != POWER_CAPABILITIES_NETWORK) { // if we're not fully awake (from a networking point of view). return FALSE; } return TRUE; } #endif // !TARGET_OS_IPHONE static void reachPerform(void *info) { void *context_info; void (*context_release)(const void *); unsigned int n; ReachabilityInfo reach_info; SCNetworkReachabilityCallBack rlsFunction; SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)info; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; n = _SC_ATOMIC_ZERO(&targetPrivate->pending); if (_sc_debug && (n > 1)) { SCLog(TRUE, LOG_DEBUG, CFSTR("%sdelivering SCNetworkReachability notifications (%u)"), targetPrivate->log_prefix, n); } MUTEX_LOCK(&targetPrivate->lock); if (!targetPrivate->scheduled) { // if no longer scheduled SCLog(_sc_debug, LOG_DEBUG, CFSTR("%sskipping SCNetworkReachability callback, no longer scheduled"), targetPrivate->log_prefix); MUTEX_UNLOCK(&targetPrivate->lock); return; } // capture current state memcpy(&reach_info, &targetPrivate->info, sizeof(ReachabilityInfo)); // callout rlsFunction = targetPrivate->rlsFunction; if (targetPrivate->rlsContext.retain != NULL) { context_info = (void *)(*targetPrivate->rlsContext.retain)(targetPrivate->rlsContext.info); context_release = targetPrivate->rlsContext.release; } else { context_info = targetPrivate->rlsContext.info; context_release = NULL; } // update last notification info _reach_set(&targetPrivate->last_notify, &reach_info, targetPrivate->cycle, targetPrivate->if_index, targetPrivate->if_name); (void)gettimeofday(&targetPrivate->last_push, NULL); MUTEX_UNLOCK(&targetPrivate->lock); if (rlsFunction != NULL) { (*rlsFunction)(target, reach_info.flags & kSCNetworkReachabilityFlagsMask, context_info); } if (context_release != NULL) { (*context_release)(context_info); } return; } /* * reachUpdate * * - caller must *not* be holding the target lock * - caller must be running on the __SCNetworkReachability_concurrent_queue() */ static Boolean reachUpdate(SCNetworkReachabilityRef target) { uint64_t cycle; Boolean defer = FALSE; Boolean forced; Boolean ok; ReachabilityInfo reach_info = NOT_REACHABLE; ReachabilityStoreInfo store_info; Boolean target_debug; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; target_debug = (_sc_debug && !targetPrivate->quiet); if (target_debug) { SCLog(TRUE, LOG_INFO, CFSTR("%schecking target reachability"), targetPrivate->log_prefix); } MUTEX_LOCK(&targetPrivate->lock); if (!targetPrivate->scheduled) { // if not currently scheduled MUTEX_UNLOCK(&targetPrivate->lock); return FALSE; } /* update reachability, notify if status changed */ ReachabilityStoreInfo_init(&store_info); ok = __SCNetworkReachabilityGetFlags(&store_info, target, &reach_info, TRUE); ReachabilityStoreInfo_free(&store_info); if (!ok) { /* if reachability status not available */ if (target_debug) { SCLog(TRUE, LOG_INFO, CFSTR("%sflags not available"), targetPrivate->log_prefix); } reach_info = NOT_REACHABLE; } #if !TARGET_OS_IPHONE /* * We want to defer the notification if this is a maintenance wake *and* * the reachability flags that we would be reporting to the application * are better than those that we last reported. */ if (!systemIsAwake(power_capabilities)) { /* if this is a maintenace wake */ reach_info.sleeping = TRUE; if (rankReachability(reach_info.flags) >= rankReachability(targetPrivate->info.flags)) { /* * don't report the change if the new reachability flags are * the same or "better" */ defer = !darkWakeNotify(target); } else if (!__reach_changed(&targetPrivate->last_notify, &reach_info)) { /* * if we have already posted this change */ defer = !darkWakeNotify(target); } } #endif // !TARGET_OS_IPHONE cycle = targetPrivate->cycle; forced = ((cycle != 0) && (targetPrivate->info.cycle != cycle)); /* * (A) (B) (C) * reach resolve * changed forced pending * ======= ======= ======= * N N N No change * N N Y No change * N Y N Change (forced && !resolve pending) * N Y Y No change (suppress forced w/resolve pending) * Y N/A N/A Change * Y N/A N/A Change * Y N/A N/A Change * Y N/A N/A Change * * Change == A || (B && !C) * No Change == !(A || (B && !C)) * No Change == !A && !(B && !C) * No Change == !A && (!B || C) * No Change == (!B || C) && !A */ if ((!forced || (reach_info.flags == kSCNetworkReachabilityFlagsFirstResolvePending)) && !__reach_changed(&targetPrivate->info, &reach_info)) { if (target_debug) { if (targetPrivate->info.sleeping == reach_info.sleeping) { SCLog(TRUE, LOG_INFO, CFSTR("%sflags/interface match (now 0x%08x/%u%s)%s%s"), targetPrivate->log_prefix, reach_info.flags, reach_info.if_index, reach_info.sleeping ? ", z" : "", defer ? ", deferred" : "", forced ? ", forced" : ""); } else { SCLog(TRUE, LOG_INFO, CFSTR("%sflags/interface equiv (was 0x%08x/%u%s, now 0x%08x/%u%s)%s%s"), targetPrivate->log_prefix, targetPrivate->info.flags, targetPrivate->info.if_index, targetPrivate->info.sleeping ? ", z" : "", reach_info.flags, reach_info.if_index, reach_info.sleeping ? ", z" : "", defer ? ", deferred" : "", forced ? ", forced" : ""); } } MUTEX_UNLOCK(&targetPrivate->lock); return FALSE; } if (target_debug) { SCLog(TRUE, LOG_INFO, CFSTR("%sflags/interface have changed (was 0x%08x/%u%s, now 0x%08x/%u%s)%s%s"), targetPrivate->log_prefix, targetPrivate->info.flags, targetPrivate->info.if_index, targetPrivate->info.sleeping ? ", z" : "", reach_info.flags, reach_info.if_index, reach_info.sleeping ? ", z" : "", defer ? ", deferred" : "", forced ? ", forced" : ""); } /* update flags / interface */ _reach_set(&targetPrivate->info, &reach_info, cycle, targetPrivate->if_index, targetPrivate->if_name); /* as needed, defer the notification */ if (defer) { MUTEX_UNLOCK(&targetPrivate->lock); return FALSE; } MUTEX_UNLOCK(&targetPrivate->lock); return TRUE; } Boolean SCNetworkReachabilitySetCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityCallBack callout, SCNetworkReachabilityContext *context) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; MUTEX_LOCK(&targetPrivate->lock); if (targetPrivate->rlsContext.release != NULL) { /* let go of the current context */ (*targetPrivate->rlsContext.release)(targetPrivate->rlsContext.info); } targetPrivate->rlsFunction = callout; targetPrivate->rlsContext.info = NULL; targetPrivate->rlsContext.retain = NULL; targetPrivate->rlsContext.release = NULL; targetPrivate->rlsContext.copyDescription = NULL; if (context) { bcopy(context, &targetPrivate->rlsContext, sizeof(SCNetworkReachabilityContext)); if (context->retain != NULL) { targetPrivate->rlsContext.info = (void *)(*context->retain)(context->info); } } MUTEX_UNLOCK(&targetPrivate->lock); return TRUE; } static CFStringRef reachRLSCopyDescription(const void *info) { SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)info; return CFStringCreateWithFormat(NULL, NULL, CFSTR(" {target = %p}"), target); } static Boolean __SCNetworkReachabilityScheduleWithRunLoop(SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode, dispatch_queue_t queue, Boolean onDemand) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; Boolean init = FALSE; __block Boolean ok = FALSE; MUTEX_LOCK(&targetPrivate->lock); if ((targetPrivate->dispatchQueue != NULL) || // if we are already scheduled with a dispatch queue ((queue != NULL) && targetPrivate->scheduled)) { // if we are already scheduled on a CFRunLoop _SCErrorSet(kSCStatusInvalidArgument); goto done; } if (!targetPrivate->serverBypass) { if (!targetPrivate->serverActive) { ok = __SCNetworkReachabilityServer_targetAdd(target); if (!ok) { targetPrivate->serverBypass = TRUE; } } if (targetPrivate->serverActive) { if (targetPrivate->scheduled) { // if already scheduled goto watch; } ok = __SCNetworkReachabilityServer_targetSchedule(target); if (!ok) { SCLog(TRUE, LOG_DEBUG, CFSTR("__SCNetworkReachabilityScheduleWithRunLoop _targetMonitor() failed")); _SCErrorSet(kSCStatusFailed); goto done; } goto watch; } } /* schedule the SCNetworkReachability did-something-change handler */ dispatch_sync(_hn_target_queue(), ^{ ok = FALSE; if (!onDemand && (hn_store == NULL)) { CFMutableArrayRef keys; CFMutableArrayRef patterns; Boolean watch_dns_configuration = FALSE; Boolean watch_dns_changes = FALSE; Boolean watch_nwi = FALSE; Boolean watch_onDemand_networking = FALSE; #if !TARGET_OS_IPHONE Boolean watch_power = FALSE; #endif // !TARGET_OS_IPHONE hn_store = SCDynamicStoreCreate(NULL, CFSTR("SCNetworkReachability"), __SCNetworkReachabilityHandleStoreChanges, NULL); if (hn_store == NULL) { SCLog(TRUE, LOG_ERR, CFSTR("SCDynamicStoreCreate() failed")); return; } ReachabilityStoreInfo_keys(&keys, &patterns); CFArrayAppendValue(keys, SCNETWORKREACHABILITY_TRIGGER_KEY); // force posting reach change ok = SCDynamicStoreSetNotificationKeys(hn_store, keys, patterns); CFRelease(keys); CFRelease(patterns); if (!ok) { SCLog(TRUE, LOG_ERR, CFSTR("SCDynamicStoreSetNotificationKeys() failed")); CFRelease(hn_store); hn_store = NULL; return; } ok = SCDynamicStoreSetDispatchQueue(hn_store, _hn_changes_queue()); if (!ok) { SCLog(TRUE, LOG_ERR, CFSTR("SCDynamicStoreSetDispatchQueue() failed")); CFRelease(hn_store); hn_store = NULL; return; } // watch for network information changes watch_nwi = nwi_refresh_enable(_hn_changes_queue()); if (!watch_nwi) { goto fail; } // watch for DNS configuration (resolver reachability) changes watch_dns_configuration = dns_configuration_watch(); if (!watch_dns_configuration) { goto fail; } // watch for changes affecting DNS queries watch_dns_changes = dns_refresh_enable(_hn_changes_queue()); if (!watch_dns_changes) { goto fail; } #if !TARGET_OS_IPHONE // watch for power capabilities (sleep/wake) changes watch_power = power_refresh_enable(_hn_changes_queue()); if (!watch_power) { goto fail; } #endif // !TARGET_OS_IPHONE // watch for OnDemand network changes watch_onDemand_networking = onDemand_refresh_enable(_hn_changes_queue()); if (!watch_onDemand_networking) { goto fail; } hn_targets = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks); ReachabilityStoreInfo_enable(TRUE); goto scheduled; fail : ok = FALSE; if (watch_onDemand_networking) { onDemand_refresh_disable(); } #if !TARGET_OS_IPHONE if (watch_power) { power_refresh_disable(); } #endif // !TARGET_OS_IPHONE if (watch_dns_changes) { dns_refresh_disable(); } if (watch_dns_configuration) { dns_configuration_unwatch(); } if (watch_nwi) { nwi_refresh_disable(); } SCDynamicStoreSetDispatchQueue(hn_store, NULL); CFRelease(hn_store); hn_store = NULL; _SCErrorSet(kSCStatusFailed); return; } scheduled : CFSetAddValue(hn_targets, target); ok = TRUE; }); if (!ok) { goto done; } watch : if (!targetPrivate->scheduled) { CFRunLoopSourceContext context = { 0 // version , (void *)target // info , CFRetain // retain , CFRelease // release , reachRLSCopyDescription // copyDescription , CFEqual // equal , CFHash // hash , NULL // schedule , NULL // cancel , reachPerform // perform }; if (runLoop != NULL) { targetPrivate->rls = CFRunLoopSourceCreate(NULL, 0, &context); targetPrivate->rlList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); } if (isReachabilityTypeName(targetPrivate->type)) { /* * we're now scheduled so let's ensure that we * are starting with a clean slate before we * resolve the name */ if (targetPrivate->resolvedAddresses != NULL) { CFRelease(targetPrivate->resolvedAddresses); targetPrivate->resolvedAddresses = NULL; } targetPrivate->resolvedError = NETDB_SUCCESS; targetPrivate->needResolve = TRUE; _reach_set(&targetPrivate->info, &NOT_REACHABLE, targetPrivate->info.cycle, targetPrivate->if_index, targetPrivate->if_name); targetPrivate->info.flags |= kSCNetworkReachabilityFlagsFirstResolvePending; _reach_set(&targetPrivate->serverInfo, &NOT_REACHABLE, targetPrivate->serverInfo.cycle, targetPrivate->if_index, targetPrivate->if_name); targetPrivate->serverInfo.flags |= kSCNetworkReachabilityFlagsFirstResolvePending; } targetPrivate->scheduled = TRUE; init = TRUE; } if (queue != NULL) { // retain dispatch queue dispatch_retain(queue); targetPrivate->dispatchQueue = queue; // // We've taken a reference to the client's dispatch_queue and we // want to hold on to that reference until we've processed any/all // notifications. To facilitate this we create a group, dispatch // any notification blocks to via that group, and when the caller // has told us to stop the notifications (unschedule) we wait for // the group to empty and use the group's finalizer to release // our reference to the client's queue. // // make sure that we have group to track any async requests targetPrivate->dispatchGroup = dispatch_group_create(); // retain the target ... and release it when the group is released CFRetain(target); dispatch_set_context(targetPrivate->dispatchGroup, (void *)target); dispatch_set_finalizer_f(targetPrivate->dispatchGroup, (dispatch_function_t)CFRelease); } else { if (!_SC_isScheduled(NULL, runLoop, runLoopMode, targetPrivate->rlList)) { /* * if we do not already have host notifications scheduled with * this runLoop / runLoopMode */ CFRunLoopAddSource(runLoop, targetPrivate->rls, runLoopMode); } _SC_schedule(target, runLoop, runLoopMode, targetPrivate->rlList); } if (init) { ReachabilityInfo reach_info = NOT_REACHABLE; ReachabilityStoreInfo store_info; /* * if we have yet to schedule SC notifications for this address * - initialize current reachability status */ ReachabilityStoreInfo_init(&store_info); if (__SCNetworkReachabilityGetFlags(&store_info, target, &reach_info, TRUE)) { /* * if reachability status available * - set flags * - schedule notification to report status via callback */ reach_info.flags |= (targetPrivate->info.flags & kSCNetworkReachabilityFlagsFirstResolvePending); _reach_set(&targetPrivate->info, &reach_info, targetPrivate->cycle, targetPrivate->if_index, targetPrivate->if_name); __SCNetworkReachabilityUpdate(target); } else { /* if reachability status not available, async lookup started */ _reach_set(&targetPrivate->info, &NOT_REACHABLE, targetPrivate->cycle, targetPrivate->if_index, targetPrivate->if_name); _reach_set(&targetPrivate->serverInfo, &NOT_REACHABLE, targetPrivate->cycle, targetPrivate->if_index, targetPrivate->if_name); } ReachabilityStoreInfo_free(&store_info); } if (targetPrivate->onDemandServer != NULL) { __SCNetworkReachabilityScheduleWithRunLoop(targetPrivate->onDemandServer, runLoop, runLoopMode, queue, TRUE); } SCLog((_sc_debug && (_sc_log > 0)), LOG_INFO, CFSTR("%sscheduled"), targetPrivate->log_prefix); ok = TRUE; done : MUTEX_UNLOCK(&targetPrivate->lock); return ok; } static Boolean __SCNetworkReachabilityUnscheduleFromRunLoop(SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode, Boolean onDemand) { dispatch_group_t drainGroup = NULL; dispatch_queue_t drainQueue = NULL; CFIndex n = 0; Boolean ok = FALSE; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; // hold a reference while we unschedule CFRetain(target); MUTEX_LOCK(&targetPrivate->lock); if (((runLoop == NULL) && (targetPrivate->dispatchQueue == NULL)) || // if we should be scheduled on a dispatch queue (but are not) ((runLoop != NULL) && (targetPrivate->dispatchQueue != NULL))) { // if we should be scheduled on a CFRunLoop (but are not) _SCErrorSet(kSCStatusInvalidArgument); goto done; } if (!targetPrivate->scheduled) { // if not currently scheduled _SCErrorSet(kSCStatusInvalidArgument); goto done; } // unschedule the target specific sources if (targetPrivate->dispatchQueue != NULL) { if (targetPrivate->onDemandServer != NULL) { SCNetworkReachabilitySetCallback(targetPrivate->onDemandServer, NULL, NULL); __SCNetworkReachabilityUnscheduleFromRunLoop(targetPrivate->onDemandServer, NULL, NULL, TRUE); } // save dispatchQueue, release reference when we've queue'd blocks complete, allow re-scheduling drainGroup = targetPrivate->dispatchGroup; targetPrivate->dispatchGroup = NULL; drainQueue = targetPrivate->dispatchQueue; targetPrivate->dispatchQueue = NULL; } else { if (!_SC_unschedule(target, runLoop, runLoopMode, targetPrivate->rlList, FALSE)) { // if not currently scheduled _SCErrorSet(kSCStatusInvalidArgument); goto done; } if (targetPrivate->onDemandServer != NULL) { __SCNetworkReachabilityUnscheduleFromRunLoop(targetPrivate->onDemandServer, runLoop, runLoopMode, TRUE); } n = CFArrayGetCount(targetPrivate->rlList); if ((n == 0) || !_SC_isScheduled(NULL, runLoop, runLoopMode, targetPrivate->rlList)) { // if target is no longer scheduled for this runLoop / runLoopMode CFRunLoopRemoveSource(runLoop, targetPrivate->rls, runLoopMode); if (n == 0) { // if *all* notifications have been unscheduled if (targetPrivate->onDemandServer != NULL) { SCNetworkReachabilitySetCallback(targetPrivate->onDemandServer, NULL, NULL); } CFRelease(targetPrivate->rlList); targetPrivate->rlList = NULL; CFRunLoopSourceInvalidate(targetPrivate->rls); CFRelease(targetPrivate->rls); targetPrivate->rls = NULL; } } } if (n == 0) { // // Cancel our request for server monitoring // if (targetPrivate->serverActive) { ok = __SCNetworkReachabilityServer_targetUnschedule(target); if (!ok) { SCLog(TRUE, LOG_DEBUG, CFSTR("__SCNetworkReachabilityUnscheduleFromRunLoop _targetMonitor() failed")); _SCErrorSet(kSCStatusFailed); } } // if *all* notifications have been unscheduled targetPrivate->scheduled = FALSE; } if (targetPrivate->serverActive) { goto unwatch; } if (n == 0) { if (targetPrivate->dnsActive) { // if we have an active [m]DNS query dequeueDNSQuery(target); } dispatch_sync(_hn_target_queue(), ^{ CFSetRemoveValue(hn_targets, target); if (onDemand) { return; } if (CFSetGetCount(hn_targets) > 0) { return; } // if we are no longer monitoring any targets SCDynamicStoreSetDispatchQueue(hn_store, NULL); CFRelease(hn_store); hn_store = NULL; CFRelease(hn_targets); hn_targets = NULL; ReachabilityStoreInfo_enable(FALSE); ReachabilityStoreInfo_save(NULL); /* * until we start monitoring again, ensure that * any resources associated with watching network * and configuration changes have been released. */ // OnDemand configuration onDemand_refresh_disable(); #if !TARGET_OS_IPHONE // sleep/wake & power capabilities power_refresh_disable(); #endif // !TARGET_OS_IPHONE // outstanding DNS queries dns_refresh_disable(); // DNS configuration dns_configuration_unwatch(); // nwi nwi_refresh_disable(); }); } unwatch : SCLog((_sc_debug && (_sc_log > 0)), LOG_INFO, CFSTR("%sunscheduled"), targetPrivate->log_prefix); ok = TRUE; done : MUTEX_UNLOCK(&targetPrivate->lock); if (drainGroup != NULL) { dispatch_group_notify(drainGroup, __SCNetworkReachability_concurrent_queue(), ^{ // release group/queue references dispatch_release(drainQueue); dispatch_release(drainGroup); // releases our target reference }); } // release our reference CFRelease(target); return ok; } Boolean SCNetworkReachabilityScheduleWithRunLoop(SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode) { if (!isA_SCNetworkReachability(target) || (runLoop == NULL) || (runLoopMode == NULL)) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } return __SCNetworkReachabilityScheduleWithRunLoop(target, runLoop, runLoopMode, NULL, FALSE); } Boolean SCNetworkReachabilityUnscheduleFromRunLoop(SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode) { if (!isA_SCNetworkReachability(target) || (runLoop == NULL) || (runLoopMode == NULL)) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } return __SCNetworkReachabilityUnscheduleFromRunLoop(target, runLoop, runLoopMode, FALSE); } Boolean SCNetworkReachabilitySetDispatchQueue(SCNetworkReachabilityRef target, dispatch_queue_t queue) { Boolean ok = FALSE; if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } if (queue != NULL) { ok = __SCNetworkReachabilityScheduleWithRunLoop(target, NULL, NULL, queue, FALSE); } else { ok = __SCNetworkReachabilityUnscheduleFromRunLoop(target, NULL, NULL, FALSE); } return ok; }