/* * Copyright (c) 2003-2005 Apple Computer, 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 * * 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 #include #include #include #include #define KERNEL_PRIVATE #include #undef KERNEL_PRIVATE #ifndef s6_addr16 #define s6_addr16 __u6_addr.__u6_addr16 #endif #include #define kSCNetworkFlagsFirstResolvePending (1<<31) #define N_QUICK 32 typedef enum { reachabilityTypeAddress, reachabilityTypeAddressPair, reachabilityTypeName } addressType; static CFStringRef __SCNetworkReachabilityCopyDescription (CFTypeRef cf); static void __SCNetworkReachabilityDeallocate (CFTypeRef cf); typedef struct { /* base CFType information */ CFRuntimeBase cfBase; /* lock */ pthread_mutex_t lock; /* address type */ addressType type; /* target host name */ const char *name; CFArrayRef resolvedAddress; /* CFArray[CFData] */ int resolvedAddressError; /* local & remote addresses */ struct sockaddr *localAddress; struct sockaddr *remoteAddress; /* current reachability flags */ SCNetworkConnectionFlags flags; uint16_t if_index; /* run loop source, callout, context, rl scheduling info */ CFRunLoopSourceRef rls; SCNetworkReachabilityCallBack rlsFunction; SCNetworkReachabilityContext rlsContext; CFMutableArrayRef rlList; /* [async] DNS query info */ Boolean haveDNS; CFMachPortRef dnsPort; CFRunLoopSourceRef dnsRLS; struct timeval dnsQueryStart; } SCNetworkReachabilityPrivate, *SCNetworkReachabilityPrivateRef; 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; /* * host "something has changed" notifications */ static pthread_mutex_t hn_lock = PTHREAD_MUTEX_INITIALIZER; static SCDynamicStoreRef hn_store = NULL; static CFRunLoopSourceRef hn_storeRLS = NULL; static CFMutableArrayRef hn_rlList = NULL; static CFMutableSetRef hn_targets = NULL; /* * DNS configuration */ typedef struct { dns_config_t *config; int refs; } dns_configuration_t; 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; static __inline__ CFTypeRef isA_SCNetworkReachability(CFTypeRef obj) { return (isA_CFType(obj, SCNetworkReachabilityGetTypeID())); } static void __log_query_time(Boolean found, Boolean async, struct timeval *start) { struct timeval dnsQueryComplete; struct timeval dnsQueryElapsed; if (!_sc_debug) { return; } if (start->tv_sec == 0) { return; } (void) gettimeofday(&dnsQueryComplete, NULL); timersub(&dnsQueryComplete, start, &dnsQueryElapsed); SCLog(TRUE, LOG_DEBUG, CFSTR("%ssync DNS complete%s (query time = %d.%3.3d)"), async ? "a" : "", found ? "" : ", host not found", dnsQueryElapsed.tv_sec, dnsQueryElapsed.tv_usec / 1000); return; } static int updatePPPStatus(SCDynamicStoreRef *storeP, const struct sockaddr *sa, const char *if_name, SCNetworkConnectionFlags *flags) { CFDictionaryRef dict = NULL; CFStringRef entity; CFIndex i; const void * keys_q[N_QUICK]; const void ** keys = keys_q; CFIndex n; CFStringRef ppp_if; int sc_status = kSCStatusReachabilityUnknown; SCDynamicStoreRef store = (storeP != NULL) ? *storeP : NULL; const void * values_q[N_QUICK]; const void ** values = values_q; switch (sa->sa_family) { case AF_INET : entity = kSCEntNetIPv4; break; case AF_INET6 : entity = kSCEntNetIPv6; break; default : goto done; } if (store == NULL) { store = SCDynamicStoreCreate(NULL, CFSTR("SCNetworkReachability"), NULL, NULL); if (store == NULL) { SCLog(_sc_verbose, LOG_INFO, CFSTR("updatePPPStatus SCDynamicStoreCreate() failed")); goto done; } } /* * grab a snapshot of the PPP configuration from the dynamic store */ { CFStringRef pattern; CFMutableArrayRef patterns; patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, entity); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetPPP); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetPPP); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); dict = SCDynamicStoreCopyMultiple(store, NULL, patterns); CFRelease(patterns); } if (dict == NULL) { /* if we could not access the dynamic store */ goto done; } sc_status = kSCStatusOK; /* * look for the service which matches the provided interface */ n = CFDictionaryGetCount(dict); if (n <= 0) { goto done; } ppp_if = CFStringCreateWithCStringNoCopy(NULL, if_name, kCFStringEncodingASCII, kCFAllocatorNull); if (n > (CFIndex)(sizeof(keys_q) / sizeof(CFTypeRef))) { keys = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0); values = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0); } CFDictionaryGetKeysAndValues(dict, keys, values); for (i=0; i < n; i++) { CFArrayRef components; CFStringRef key; CFNumberRef num; CFDictionaryRef p_setup; CFDictionaryRef p_state; int32_t ppp_status; CFStringRef service = NULL; CFStringRef s_key = (CFStringRef) keys[i]; CFDictionaryRef s_dict = (CFDictionaryRef)values[i]; CFStringRef s_if; if (!isA_CFString(s_key) || !isA_CFDictionary(s_dict)) { continue; } if (!CFStringHasSuffix(s_key, entity)) { continue; // if not an IPv4 or IPv6 entity } s_if = CFDictionaryGetValue(s_dict, kSCPropInterfaceName); if (!isA_CFString(s_if)) { continue; // if no interface } if (!CFEqual(ppp_if, s_if)) { continue; // if not this interface } /* * extract service ID, get PPP "state" entity (for status), and get * the "setup" entity (for dial-on-traffic flag) */ components = CFStringCreateArrayBySeparatingStrings(NULL, s_key, CFSTR("/")); if (CFArrayGetCount(components) != 5) { continue; } service = CFArrayGetValueAtIndex(components, 3); key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, service, kSCEntNetPPP); p_state = CFDictionaryGetValue(dict, key); CFRelease(key); key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, service, kSCEntNetPPP); p_setup = CFDictionaryGetValue(dict, key); CFRelease(key); CFRelease(components); // get PPP status if (!isA_CFDictionary(p_state)) { continue; } num = CFDictionaryGetValue(p_state, kSCPropNetPPPStatus); if (!isA_CFNumber(num)) { continue; } if (!CFNumberGetValue(num, kCFNumberSInt32Type, &ppp_status)) { continue; } switch (ppp_status) { case PPP_RUNNING : /* if we're really UP and RUNNING */ break; case PPP_ONHOLD : /* if we're effectively UP and RUNNING */ break; case PPP_IDLE : case PPP_STATERESERVED : /* if we're not connected at all */ SCLog(_sc_debug, LOG_INFO, CFSTR(" PPP link idle, dial-on-traffic to connect")); *flags |= kSCNetworkFlagsConnectionRequired; break; default : /* if we're in the process of [dis]connecting */ SCLog(_sc_debug, LOG_INFO, CFSTR(" PPP link, connection in progress")); *flags |= kSCNetworkFlagsConnectionRequired; break; } // check PPP dial-on-traffic status if (isA_CFDictionary(p_setup)) { num = CFDictionaryGetValue(p_setup, kSCPropNetPPPDialOnDemand); if (isA_CFNumber(num)) { int32_t ppp_demand; if (CFNumberGetValue(num, kCFNumberSInt32Type, &ppp_demand)) { if (ppp_demand) { *flags |= kSCNetworkFlagsConnectionAutomatic; if (ppp_status == PPP_IDLE) { *flags |= kSCNetworkFlagsInterventionRequired; } } } } } break; } CFRelease(ppp_if); if (keys != keys_q) { CFAllocatorDeallocate(NULL, keys); CFAllocatorDeallocate(NULL, values); } done : if (dict != NULL) CFRelease(dict); if (storeP != NULL) *storeP = store; return sc_status; } static int updatePPPAvailable(SCDynamicStoreRef *storeP, const struct sockaddr *sa, SCNetworkConnectionFlags *flags) { CFDictionaryRef dict = NULL; CFStringRef entity; CFIndex i; const void * keys_q[N_QUICK]; const void ** keys = keys_q; CFIndex n; int sc_status = kSCStatusReachabilityUnknown; SCDynamicStoreRef store = (storeP != NULL) ? *storeP : NULL; const void * values_q[N_QUICK]; const void ** values = values_q; if (sa == NULL) { entity = kSCEntNetIPv4; } else { switch (sa->sa_family) { case AF_INET : entity = kSCEntNetIPv4; break; case AF_INET6 : entity = kSCEntNetIPv6; break; default : goto done; } } if (store == NULL) { store = SCDynamicStoreCreate(NULL, CFSTR("SCNetworkReachability"), NULL, NULL); if (store == NULL) { SCLog(_sc_debug, LOG_INFO, CFSTR(" status = unknown (could not access SCDynamicStore")); goto done; } } /* * grab a snapshot of the PPP configuration from the dynamic store */ { CFStringRef pattern; CFMutableArrayRef patterns; patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, entity); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetPPP); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); dict = SCDynamicStoreCopyMultiple(store, NULL, patterns); CFRelease(patterns); } if (dict == NULL) { /* if we could not access the dynamic store */ goto done; } sc_status = kSCStatusOK; /* * look for an available service which will provide connectivity * for the requested address family. */ n = CFDictionaryGetCount(dict); if (n <= 0) { goto done; } if (n > (CFIndex)(sizeof(keys_q) / sizeof(CFTypeRef))) { keys = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0); values = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0); } CFDictionaryGetKeysAndValues(dict, keys, values); for (i = 0; i < n; i++) { CFArrayRef components; Boolean found = FALSE; CFStringRef p_key; CFDictionaryRef p_dict; CFStringRef service; CFStringRef s_key = (CFStringRef) keys[i]; CFDictionaryRef s_dict = (CFDictionaryRef)values[i]; if (!isA_CFString(s_key) || !isA_CFDictionary(s_dict)) { continue; } if (!CFStringHasSuffix(s_key, entity)) { continue; // if not an IPv4 or IPv6 entity } // extract service ID components = CFStringCreateArrayBySeparatingStrings(NULL, s_key, CFSTR("/")); if (CFArrayGetCount(components) != 5) { continue; } service = CFArrayGetValueAtIndex(components, 3); // check for PPP entity p_key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, service, kSCEntNetPPP); p_dict = CFDictionaryGetValue(dict, p_key); CFRelease(p_key); if (isA_CFDictionary(p_dict)) { CFNumberRef num; /* * we have a PPP service for this address family */ found = TRUE; *flags |= kSCNetworkFlagsReachable; *flags |= kSCNetworkFlagsTransientConnection; *flags |= kSCNetworkFlagsConnectionRequired; /* * get PPP dial-on-traffic status */ num = CFDictionaryGetValue(p_dict, kSCPropNetPPPDialOnDemand); if (isA_CFNumber(num)) { int32_t ppp_demand; if (CFNumberGetValue(num, kCFNumberSInt32Type, &ppp_demand)) { if (ppp_demand) { *flags |= kSCNetworkFlagsConnectionAutomatic; } } } if (_sc_debug) { SCLog(TRUE, LOG_INFO, CFSTR(" status = isReachable (after connect)")); SCLog(TRUE, LOG_INFO, CFSTR(" service = %@"), service); } } CFRelease(components); if (found) { break; } } if (keys != keys_q) { CFAllocatorDeallocate(NULL, keys); CFAllocatorDeallocate(NULL, values); } done : if (dict != NULL) CFRelease(dict); if (storeP != NULL) *storeP = store; return sc_status; } #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(u_long)) :\ sizeof(u_long))) 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 */ static Boolean checkAddress(SCDynamicStoreRef *storeP, const struct sockaddr *address, SCNetworkConnectionFlags *flags, uint16_t *if_index) { char buf[BUFLEN]; struct ifreq ifr; char if_name[IFNAMSIZ+1]; int isock; int n; pid_t pid = getpid(); int rsock; struct sockaddr *rti_info[RTAX_MAX]; struct rt_msghdr *rtm; struct sockaddr *sa; int sc_status = kSCStatusReachabilityUnknown; struct sockaddr_dl *sdl; int seq = (int)pthread_self(); SCDynamicStoreRef store = (storeP != NULL) ? *storeP : NULL; char *statusMessage = NULL; #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 *flags = 0; if (if_index != NULL) { *if_index = 0; } if (address == NULL) { /* special case: check only for available paths off the system */ goto checkAvailable; } switch (address->sa_family) { case AF_INET : case AF_INET6 : if (_sc_debug) { _SC_sockaddr_to_string(address, buf, sizeof(buf)); SCLog(TRUE, LOG_INFO, CFSTR("checkAddress(%s)"), buf); } break; default : /* * if no code for this address family (yet) */ SCLog(_sc_verbose, LOG_ERR, CFSTR("checkAddress(): unexpected address family %d"), address->sa_family); sc_status = kSCStatusInvalidArgument; goto done; } bzero(&buf, sizeof(buf)); rtm = (struct rt_msghdr *)&buf; rtm->rtm_msglen = sizeof(struct rt_msghdr); rtm->rtm_version = RTM_VERSION; #ifdef RTM_GET_SILENT rtm->rtm_type = RTM_GET_SILENT; #else rtm->rtm_type = RTM_GET; #endif rtm->rtm_flags = RTF_STATIC|RTF_UP|RTF_HOST|RTF_GATEWAY; rtm->rtm_addrs = RTA_DST|RTA_IFP; /* Both destination and device */ rtm->rtm_pid = pid; rtm->rtm_seq = seq; switch (address->sa_family) { case AF_INET6: { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)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 *) (rtm + 1); bcopy(address, sa, address->sa_len); n = ROUNDUP(sa->sa_len, sizeof(u_long)); rtm->rtm_msglen += n; sdl = (struct sockaddr_dl *) ((void *)sa + n); sdl->sdl_family = AF_LINK; sdl->sdl_len = sizeof (struct sockaddr_dl); n = ROUNDUP(sdl->sdl_len, sizeof(u_long)); rtm->rtm_msglen += n; #ifndef RTM_GET_SILENT pthread_mutex_lock(&lock); #endif rsock = socket(PF_ROUTE, SOCK_RAW, 0); if (rsock == -1) { #ifndef RTM_GET_SILENT pthread_mutex_unlock(&lock); #endif SCLog(TRUE, LOG_ERR, CFSTR("socket(PF_ROUTE) failed: %s"), strerror(errno)); sc_status = kSCStatusFailed; goto done; } #ifndef RTM_GET_SILENT if (setsockopt(rsock, SOL_SOCKET, SO_RCVBUF, &sosize, sizeof(sosize)) == -1) { (void)close(rsock); pthread_mutex_unlock(&lock); SCLog(TRUE, LOG_ERR, CFSTR("setsockopt(SO_RCVBUF) failed: %s"), strerror(errno)); sc_status = kSCStatusFailed; goto done; } #endif if (write(rsock, &buf, rtm->rtm_msglen) == -1) { int err = errno; (void)close(rsock); #ifndef RTM_GET_SILENT pthread_mutex_unlock(&lock); #endif if (err != ESRCH) { SCLog(TRUE, LOG_ERR, CFSTR("write() failed: %s"), strerror(err)); goto done; } goto checkAvailable; } /* * Type, seq, pid identify our response. * Routing sockets are broadcasters on input. */ do { int n; n = read(rsock, (void *)&buf, sizeof(buf)); if (n == -1) { int err = errno; if (err != EINTR) { (void)close(rsock); SCLog(TRUE, LOG_ERR, CFSTR("read() failed: %s"), strerror(err)); #ifndef RTM_GET_SILENT pthread_mutex_unlock(&lock); #endif goto done; } } } while (rtm->rtm_type != RTM_GET || rtm->rtm_seq != seq || rtm->rtm_pid != pid); (void)close(rsock); #ifndef RTM_GET_SILENT pthread_mutex_unlock(&lock); #endif get_rtaddrs(rtm->rtm_addrs, sa, rti_info); #ifdef DEBUG { int i; char buf[200]; SCLog(_sc_debug, LOG_DEBUG, CFSTR("rtm_flags = 0x%8.8x"), rtm->rtm_flags); for (i = 0; i < RTAX_MAX; i++) { if (rti_info[i] != NULL) { _SC_sockaddr_to_string(rti_info[i], buf, sizeof(buf)); SCLog(_sc_debug, LOG_DEBUG, CFSTR("%d: %s"), i, buf); } } } #endif /* DEBUG */ if ((rti_info[RTAX_IFP] == NULL) || (rti_info[RTAX_IFP]->sa_family != AF_LINK)) { /* no interface info */ goto done; } sdl = (struct sockaddr_dl *) rti_info[RTAX_IFP]; if ((sdl->sdl_nlen == 0) || (sdl->sdl_nlen > IFNAMSIZ)) { /* no interface name */ goto checkAvailable; } /* get the interface flags */ bzero(&ifr, sizeof(ifr)); bcopy(sdl->sdl_data, ifr.ifr_name, sdl->sdl_nlen); isock = socket(AF_INET, SOCK_DGRAM, 0); if (isock < 0) { SCLog(TRUE, LOG_NOTICE, CFSTR("socket() failed: %s"), strerror(errno)); goto done; } if (ioctl(isock, SIOCGIFFLAGS, (char *)&ifr) < 0) { SCLog(TRUE, LOG_NOTICE, CFSTR("ioctl() failed: %s"), strerror(errno)); (void)close(isock); goto done; } (void)close(isock); if (!(ifr.ifr_flags & IFF_UP)) { goto checkAvailable; } statusMessage = "isReachable"; *flags |= kSCNetworkFlagsReachable; if (rtm->rtm_flags & RTF_LOCAL) { statusMessage = "isReachable (is a local address)"; *flags |= kSCNetworkFlagsIsLocalAddress; } else if (ifr.ifr_flags & IFF_LOOPBACK) { statusMessage = "isReachable (is loopback network)"; *flags |= kSCNetworkFlagsIsLocalAddress; } else if (rti_info[RTAX_IFA]) { void *addr1 = (void *)address; void *addr2 = (void *)rti_info[RTAX_IFA]; size_t len = address->sa_len; if ((address->sa_family != rti_info[RTAX_IFA]->sa_family) && (address->sa_len != 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, rti_info[RTAX_IFA]->sa_family, rti_info[RTAX_IFA]->sa_len); goto done; } switch (address->sa_family) { case AF_INET : addr1 = &((struct sockaddr_in *)address)->sin_addr; addr2 = &((struct sockaddr_in *)rti_info[RTAX_IFA])->sin_addr; len = sizeof(struct in_addr); /* * check if 0.0.0.0 */ if (((struct sockaddr_in *)address)->sin_addr.s_addr == 0) { statusMessage = "isReachable (this host)"; *flags |= kSCNetworkFlagsIsLocalAddress; } break; case AF_INET6 : addr1 = &((struct sockaddr_in6 *)address)->sin6_addr; addr2 = &((struct sockaddr_in6 *)rti_info[RTAX_IFA])->sin6_addr; len = sizeof(struct in6_addr); break; default : break; } if (memcmp(addr1, addr2, len) == 0) { statusMessage = "isReachable (is interface address)"; *flags |= kSCNetworkFlagsIsLocalAddress; } } if (!(rtm->rtm_flags & RTF_GATEWAY) && (rti_info[RTAX_GATEWAY] != NULL) && (rti_info[RTAX_GATEWAY]->sa_family == AF_LINK) && !(ifr.ifr_flags & IFF_POINTOPOINT)) { *flags |= kSCNetworkFlagsIsDirect; } bzero(&if_name, sizeof(if_name)); bcopy(sdl->sdl_data, if_name, (sdl->sdl_nlen <= IFNAMSIZ) ? sdl->sdl_nlen : IFNAMSIZ); if (if_index != NULL) { *if_index = sdl->sdl_index; } if (_sc_debug) { SCLog(TRUE, LOG_INFO, CFSTR(" status = %s"), statusMessage); SCLog(TRUE, LOG_INFO, CFSTR(" device = %s (%hu)"), if_name, sdl->sdl_index); SCLog(TRUE, LOG_INFO, CFSTR(" ifr_flags = 0x%04hx"), ifr.ifr_flags); SCLog(TRUE, LOG_INFO, CFSTR(" rtm_flags = 0x%08x"), rtm->rtm_flags); } sc_status = kSCStatusOK; if (ifr.ifr_flags & IFF_POINTOPOINT) { /* * We have an interface which "claims" to be a valid path * off of the system. */ *flags |= kSCNetworkFlagsTransientConnection; /* * Check if this is a dial-on-demand PPP link that isn't * connected yet. */ sc_status = updatePPPStatus(&store, address, if_name, flags); } goto done; checkAvailable : sc_status = updatePPPAvailable(&store, address, flags); done : if (*flags == 0) { SCLog(_sc_debug, LOG_INFO, CFSTR(" cannot be reached")); } if (storeP != NULL) *storeP = store; if (sc_status != kSCStatusOK) { _SCErrorSet(sc_status); return FALSE; } return TRUE; } static CFStringRef __SCNetworkReachabilityCopyDescription(CFTypeRef cf) { CFAllocatorRef allocator = CFGetAllocator(cf); CFMutableStringRef result; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)cf; result = CFStringCreateMutable(allocator, 0); CFStringAppendFormat(result, NULL, CFSTR(" { "), cf, allocator); switch (targetPrivate->type) { case reachabilityTypeAddress : case reachabilityTypeAddressPair : { char buf[64]; if (targetPrivate->localAddress != NULL) { _SC_sockaddr_to_string(targetPrivate->localAddress, buf, sizeof(buf)); CFStringAppendFormat(result, NULL, CFSTR("local address=%s"), buf); } if (targetPrivate->remoteAddress != NULL) { _SC_sockaddr_to_string(targetPrivate->remoteAddress, buf, sizeof(buf)); CFStringAppendFormat(result, NULL, CFSTR("%s%saddress=%s"), targetPrivate->localAddress ? ", " : "", (targetPrivate->type == reachabilityTypeAddressPair) ? "remote " : "", buf); } break; } case reachabilityTypeName : { CFStringAppendFormat(result, NULL, CFSTR("name=%s"), targetPrivate->name); if (targetPrivate->resolvedAddress || (targetPrivate->resolvedAddressError != NETDB_SUCCESS)) { if (targetPrivate->resolvedAddress != NULL) { if (isA_CFArray(targetPrivate->resolvedAddress)) { CFIndex i; CFIndex n = CFArrayGetCount(targetPrivate->resolvedAddress); CFStringAppendFormat(result, NULL, CFSTR(" (")); for (i = 0; i < n; i++) { CFDataRef address; char buf[64]; struct sockaddr *sa; address = CFArrayGetValueAtIndex(targetPrivate->resolvedAddress, i); sa = (struct sockaddr *)CFDataGetBytePtr(address); _SC_sockaddr_to_string(sa, buf, sizeof(buf)); CFStringAppendFormat(result, NULL, CFSTR("%s%s"), i > 0 ? ", " : "", buf); } CFStringAppendFormat(result, NULL, CFSTR(")")); } else { CFStringAppendFormat(result, NULL, CFSTR(" (no addresses)")); } } else { CFStringAppendFormat(result, NULL, CFSTR(" (%s)"), gai_strerror(targetPrivate->resolvedAddressError)); } } else if (targetPrivate->dnsPort) { CFStringAppendFormat(result, NULL, CFSTR(" (DNS query active)")); } break; } } if (targetPrivate->rls != NULL) { CFStringAppendFormat(result, NULL, CFSTR(", flags=%8.8x, if_index=%hu"), targetPrivate->flags, targetPrivate->if_index); } CFStringAppendFormat(result, NULL, CFSTR(" }")); return result; } static void __SCNetworkReachabilityDeallocate(CFTypeRef cf) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)cf; /* release resources */ pthread_mutex_destroy(&targetPrivate->lock); if (targetPrivate->name != NULL) CFAllocatorDeallocate(NULL, (void *)targetPrivate->name); if (targetPrivate->resolvedAddress != NULL) CFRelease(targetPrivate->resolvedAddress); if (targetPrivate->localAddress != 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); } return; } static void __SCNetworkReachabilityInitialize(void) { __kSCNetworkReachabilityTypeID = _CFRuntimeRegisterClass(&__SCNetworkReachabilityClass); 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; } pthread_mutex_init(&targetPrivate->lock, NULL); targetPrivate->name = NULL; targetPrivate->resolvedAddress = NULL; targetPrivate->resolvedAddressError = NETDB_SUCCESS; targetPrivate->localAddress = NULL; targetPrivate->remoteAddress = NULL; targetPrivate->flags = 0; targetPrivate->if_index = 0; targetPrivate->rls = NULL; targetPrivate->rlsFunction = NULL; targetPrivate->rlsContext.info = NULL; targetPrivate->rlsContext.retain = NULL; targetPrivate->rlsContext.release = NULL; targetPrivate->rlsContext.copyDescription = NULL; targetPrivate->rlList = NULL; targetPrivate->haveDNS = FALSE; targetPrivate->dnsPort = NULL; targetPrivate->dnsRLS = NULL; return targetPrivate; } SCNetworkReachabilityRef SCNetworkReachabilityCreateWithAddress(CFAllocatorRef allocator, const struct sockaddr *address) { SCNetworkReachabilityPrivateRef targetPrivate; if ((address == NULL) || (address->sa_len == 0) || (address->sa_len > sizeof(struct sockaddr_storage))) { _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); return (SCNetworkReachabilityRef)targetPrivate; } 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) { if ((localAddress->sa_len == 0) || (localAddress->sa_len > sizeof(struct sockaddr_storage))) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } } if (remoteAddress != NULL) { if ((remoteAddress->sa_len == 0) || (remoteAddress->sa_len > sizeof(struct sockaddr_storage))) { _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) { targetPrivate->remoteAddress = CFAllocatorAllocate(NULL, remoteAddress->sa_len, 0); bcopy(remoteAddress, targetPrivate->remoteAddress, remoteAddress->sa_len); } return (SCNetworkReachabilityRef)targetPrivate; } SCNetworkReachabilityRef SCNetworkReachabilityCreateWithName(CFAllocatorRef allocator, const char *nodename) { struct sockaddr_in sin; struct sockaddr_in6 sin6; SCNetworkReachabilityPrivateRef targetPrivate; if (nodename == NULL) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } /* check if this "nodename" is really an IP[v6] address in disguise */ bzero(&sin, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; if (inet_aton(nodename, &sin.sin_addr) == 1) { /* if IPv4 address */ return SCNetworkReachabilityCreateWithAddress(allocator, (struct sockaddr *)&sin); } bzero(&sin6, sizeof(sin6)); sin6.sin6_len = sizeof(sin6); sin6.sin6_family = AF_INET6; if (inet_pton(AF_INET6, nodename, &sin6.sin6_addr) == 1) { /* if IPv6 address */ char *p; p = strchr(nodename, '%'); if (p != NULL) { sin6.sin6_scope_id = if_nametoindex(p+1); } return SCNetworkReachabilityCreateWithAddress(allocator, (struct sockaddr *)&sin6); } targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator); if (targetPrivate == NULL) { return NULL; } targetPrivate->type = reachabilityTypeName; targetPrivate->flags |= kSCNetworkFlagsFirstResolvePending; targetPrivate->name = CFAllocatorAllocate(NULL, strlen(nodename) + 1, 0); strcpy((char *)targetPrivate->name, nodename); return (SCNetworkReachabilityRef)targetPrivate; } CFTypeID SCNetworkReachabilityGetTypeID(void) { pthread_once(&initialized, __SCNetworkReachabilityInitialize); /* initialize runtime */ return __kSCNetworkReachabilityTypeID; } CFArrayRef SCNetworkReachabilityCopyResolvedAddress(SCNetworkReachabilityRef target, int *error_num) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } if (targetPrivate->type != reachabilityTypeName) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } if (error_num) { *error_num = targetPrivate->resolvedAddressError; } if (targetPrivate->resolvedAddress || (targetPrivate->resolvedAddressError != NETDB_SUCCESS)) { if (targetPrivate->resolvedAddress != NULL) { return CFRetain(targetPrivate->resolvedAddress); } else { /* if status is known but no resolved addresses to return */ _SCErrorSet(kSCStatusOK); return NULL; } } _SCErrorSet(kSCStatusReachabilityUnknown); return NULL; } static void __SCNetworkReachabilitySetResolvedAddress(int32_t status, struct addrinfo *res, SCNetworkReachabilityRef target) { struct addrinfo *resP; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; if (targetPrivate->resolvedAddress != NULL) { CFRelease(targetPrivate->resolvedAddress); targetPrivate->resolvedAddress = NULL; } if ((status == 0) && (res != NULL)) { CFMutableArrayRef addresses; addresses = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); for (resP = res; resP; resP = resP->ai_next) { CFDataRef newAddress; newAddress = CFDataCreate(NULL, (void *)resP->ai_addr, resP->ai_addr->sa_len); CFArrayAppendValue(addresses, newAddress); CFRelease(newAddress); } /* save the resolved address[es] */ targetPrivate->resolvedAddress = addresses; targetPrivate->resolvedAddressError = NETDB_SUCCESS; } else { SCLog(_sc_debug, LOG_INFO, CFSTR("getaddrinfo() failed: %s"), gai_strerror(status)); /* save the error associated with the attempt to resolve the name */ targetPrivate->resolvedAddress = CFRetain(kCFNull); targetPrivate->resolvedAddressError = status; } if (res) freeaddrinfo(res); if (targetPrivate->rls != NULL) { SCLog(_sc_debug, LOG_INFO, CFSTR("DNS request completed")); CFRunLoopSourceSignal(targetPrivate->rls); _SC_signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList); } return; } static void __SCNetworkReachabilityCallbackSetResolvedAddress(int32_t status, struct addrinfo *res, void *context) { SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)context; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; __log_query_time(((status == 0) && (res != NULL)), // if successful query TRUE, // async &targetPrivate->dnsQueryStart); // start time __SCNetworkReachabilitySetResolvedAddress(status, res, target); return; } /* * rankReachability() * Not reachable == 0 * Connection Required == 1 * Reachable == 2 */ static int rankReachability(SCNetworkConnectionFlags flags) { int rank = 0; if (flags & kSCNetworkFlagsReachable) rank = 2; if (flags & kSCNetworkFlagsConnectionRequired) rank = 1; return rank; } static void getaddrinfo_async_handleCFReply(CFMachPortRef port, void *msg, CFIndex size, void *info) { int32_t status; SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)info; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; pthread_mutex_lock(&targetPrivate->lock); status = getaddrinfo_async_handle_reply(msg); if ((status == 0) && (targetPrivate->resolvedAddress == NULL) && (targetPrivate->resolvedAddressError == NETDB_SUCCESS)) { // if request has been re-queued goto again; } if (port == targetPrivate->dnsPort) { CFRunLoopSourceInvalidate(targetPrivate->dnsRLS); CFRelease(targetPrivate->dnsRLS); targetPrivate->dnsRLS = NULL; CFRelease(targetPrivate->dnsPort); targetPrivate->dnsPort = NULL; } again : pthread_mutex_unlock(&targetPrivate->lock); return; } static Boolean check_resolver_reachability(SCDynamicStoreRef *storeP, dns_resolver_t *resolver, SCNetworkConnectionFlags *flags, Boolean *haveDNS) { int i; Boolean ok = TRUE; *flags = kSCNetworkFlagsReachable; *haveDNS = FALSE; for (i = 0; i < resolver->n_nameserver; i++) { struct sockaddr *address = resolver->nameserver[i]; SCNetworkConnectionFlags ns_flags = 0; *haveDNS = TRUE; if (address->sa_family != AF_INET) { /* * we need to skip non-IPv4 DNS server * addresses (at least until [3510431] has * been resolved). */ continue; } ok = checkAddress(storeP, address, &ns_flags, NULL); if (!ok) { /* not today */ goto done; } if (rankReachability(ns_flags) < rankReachability(*flags)) { /* return the worst case result */ *flags = ns_flags; } } done : return ok; } static Boolean check_matching_resolvers(SCDynamicStoreRef *storeP, dns_config_t *dns_config, const char *fqdn, SCNetworkConnectionFlags *flags, Boolean *haveDNS) { int i; Boolean matched = FALSE; const char *name = fqdn; while (!matched && (name != NULL)) { int len; /* * check if the provided name (or sub-component) * matches one of our resolver configurations. */ len = strlen(name); for (i = 0; i < dns_config->n_resolver; i++) { char *domain; dns_resolver_t *resolver; resolver = dns_config->resolver[i]; domain = resolver->domain; if (domain != NULL && (len == strlen(domain))) { if (strcasecmp(name, domain) == 0) { Boolean ok; /* * if name matches domain */ matched = TRUE; ok = check_resolver_reachability(storeP, resolver, flags, haveDNS); if (!ok) { /* not today */ return FALSE; } } } } 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_configuration_t * dns_configuration_retain() { pthread_mutex_lock(&dns_lock); if ((dns_configuration != NULL) && 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=%lu"), status); } if ((status != NOTIFY_STATUS_OK) || (check != 0)) { /* * if the snapshot needs to be refreshed */ 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++; } pthread_mutex_unlock(&dns_lock); return dns_configuration; } static void dns_configuration_release(dns_configuration_t *config) { pthread_mutex_lock(&dns_lock); config->refs--; if (config->refs == 0) { if ((dns_configuration != config)) { 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=%lu"), status); goto done; } status = notify_check(dns_token, &dns_check); if (status != NOTIFY_STATUS_OK) { SCLog(TRUE, LOG_INFO, CFSTR("notify_check() failed, status=%lu"), 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; } Boolean _SC_checkResolverReachability(SCDynamicStoreRef *storeP, SCNetworkConnectionFlags *flags, Boolean *haveDNS, const char * nodename) { dns_resolver_t *default_resolver; dns_configuration_t *dns; Boolean found = FALSE; char *fqdn = (char *)nodename; int i; Boolean isFQDN = FALSE; uint32_t len; Boolean ok = TRUE; /* * 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 = kSCNetworkFlagsReachable; *haveDNS = FALSE; len = strlen(fqdn); if (len == 0) { // if no nodename, return not reachable *flags = 0; return ok; } dns = dns_configuration_retain(); if (dns == NULL) { // if error goto done; } if (dns->config->n_resolver == 0) { // if no resolver configuration goto done; } *flags = kSCNetworkFlagsReachable; if (fqdn[len - 1] == '.') { isFQDN = TRUE; // trim trailing '.''s while ((len > 0) && (fqdn[len-1] == '.')) { if (fqdn == nodename) { fqdn = strdup(nodename); } fqdn[--len] = '\0'; } } default_resolver = dns->config->resolver[0]; /* * try the provided name */ found = check_matching_resolvers(storeP, dns->config, fqdn, flags, haveDNS); if (!found && !isFQDN && (dns->config->n_resolver > 1)) { /* * FQDN not specified, try w/search or default domain(s) too */ 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(storeP, dns->config, search_fqdn, flags, haveDNS); free(search_fqdn); } } else if (default_resolver->domain != NULL) { char *dp; int domain_parts = 1; for (dp = default_resolver->domain; *dp != '\0'; dp++) { if (*dp == '.') { domain_parts++; } } dp = default_resolver->domain; for (i = LOCALDOMAINPARTS; !found && (i <= domain_parts); 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(storeP, dns->config, search_fqdn, flags, haveDNS); free(search_fqdn); // move to the next component of the [default] domain dp = strchr(dp, '.') + 1; } } } if (!found) { /* * check the reachability of the default resolver */ ok = check_resolver_reachability(storeP, default_resolver, flags, haveDNS); } if (fqdn != nodename) free(fqdn); done : if (dns != NULL) { dns_configuration_release(dns); } return ok; } static Boolean startAsyncDNSQuery(SCNetworkReachabilityRef target) { CFMachPortContext context = { 0, (void *)target, CFRetain, CFRelease, CFCopyDescription }; int error; struct addrinfo hints; CFIndex i; CFIndex n; mach_port_t port; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; (void) gettimeofday(&targetPrivate->dnsQueryStart, NULL); bzero(&hints, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG; #ifdef AI_PARALLEL hints.ai_flags |= AI_PARALLEL; #endif /* AI_PARALLEL */ error = getaddrinfo_async_start(&port, targetPrivate->name, NULL, &hints, __SCNetworkReachabilityCallbackSetResolvedAddress, (void *)target); if (error != 0) { /* save the error associated with the attempt to resolve the name */ __SCNetworkReachabilityCallbackSetResolvedAddress(error, NULL, (void *)target); return FALSE; } targetPrivate->dnsPort = CFMachPortCreateWithPort(NULL, port, getaddrinfo_async_handleCFReply, &context, NULL); targetPrivate->dnsRLS = CFMachPortCreateRunLoopSource(NULL, targetPrivate->dnsPort, 0); 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); CFRunLoopAddSource(rl, targetPrivate->dnsRLS, rlMode); } return TRUE; } static Boolean __SCNetworkReachabilityGetFlags(SCDynamicStoreRef *storeP, SCNetworkReachabilityRef target, SCNetworkConnectionFlags *flags, uint16_t *if_index, Boolean async) { CFMutableArrayRef addresses = NULL; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; SCNetworkConnectionFlags my_flags = 0; uint16_t my_index = 0; Boolean ok = TRUE; *flags = 0; if (if_index != NULL) { *if_index = 0; } if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } switch (targetPrivate->type) { case reachabilityTypeAddress : case reachabilityTypeAddressPair : { /* * Check "local" address */ if (targetPrivate->localAddress != NULL) { /* * Check "local" address */ ok = checkAddress(storeP, targetPrivate->localAddress, &my_flags, &my_index); if (!ok) { goto error; /* not today */ } if (!(my_flags & kSCNetworkFlagsIsLocalAddress)) { goto error; /* not reachable, non-"local" address */ } } /* * Check "remote" address */ if (targetPrivate->remoteAddress != NULL) { /* * in cases where we have "local" and "remote" addresses * we need to re-initialize the to-be-returned flags. */ my_flags = 0; my_index = 0; /* * Check "remote" address */ ok = checkAddress(storeP, targetPrivate->remoteAddress, &my_flags, &my_index); if (!ok) { goto error; /* not today */ } } break; } case reachabilityTypeName : { struct timeval dnsQueryStart; int error; struct addrinfo hints; SCNetworkConnectionFlags ns_flags; struct addrinfo *res; addresses = (CFMutableArrayRef)SCNetworkReachabilityCopyResolvedAddress(target, &error); if ((addresses != NULL) || (error != NETDB_SUCCESS)) { /* if resolved or an error had been detected */ goto checkResolvedAddress; } /* check the reachability of the DNS servers */ ok = _SC_checkResolverReachability(storeP, &ns_flags, &targetPrivate->haveDNS, targetPrivate->name); if (!ok) { /* if we could not get DNS server info */ goto error; } 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. */ if (!checkAddress(storeP, NULL, &my_flags, &my_index)) { goto error; } if (async && (targetPrivate->rls != NULL)) { /* * return "host not found", set flags appropriately, * and schedule notification. */ __SCNetworkReachabilityCallbackSetResolvedAddress(EAI_NODATA, NULL, (void *)target); my_flags |= (targetPrivate->flags & kSCNetworkFlagsFirstResolvePending); SCLog(_sc_debug, LOG_INFO, CFSTR("no DNS servers are reachable")); CFRunLoopSourceSignal(targetPrivate->rls); _SC_signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList); } break; } if (async) { /* for async requests we return the last known status */ my_flags = targetPrivate->flags; my_index = targetPrivate->if_index; if (targetPrivate->dnsPort) { /* if request already in progress */ break; } SCLog(_sc_debug, LOG_INFO, CFSTR("start DNS query for \"%s\""), targetPrivate->name); /* * initiate an async DNS query */ if (!startAsyncDNSQuery(target)) { /* if we could not initiate the request, process error */ goto checkResolvedAddress; } /* if request initiated */ break; } SCLog(_sc_debug, LOG_INFO, CFSTR("check DNS for \"%s\""), targetPrivate->name); /* * OK, all of the DNS name servers are available. Let's * resolve the nodename into an address. */ if (_sc_debug) { (void) gettimeofday(&dnsQueryStart, NULL); } bzero(&hints, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG; #ifdef AI_PARALLEL hints.ai_flags |= AI_PARALLEL; #endif /* AI_PARALLEL */ error = getaddrinfo(targetPrivate->name, NULL, &hints, &res); __log_query_time(((error == 0) && (res != NULL)),// if successful query FALSE, // sync &dnsQueryStart); // start time __SCNetworkReachabilitySetResolvedAddress(error, res, target); addresses = (CFMutableArrayRef)SCNetworkReachabilityCopyResolvedAddress(target, &error); checkResolvedAddress : /* * We first assume that the requested host is NOT available. * Then, check each address for accessibility and return the * best status available. */ my_flags = 0; my_index = 0; if (isA_CFArray(addresses)) { CFIndex i; CFIndex n = CFArrayGetCount(addresses); for (i = 0; i < n; i++) { SCNetworkConnectionFlags ns_flags = 0; uint16_t ns_if_index = 0; struct sockaddr *sa; sa = (struct sockaddr *)CFDataGetBytePtr(CFArrayGetValueAtIndex(addresses, i)); ok = checkAddress(storeP, sa, &ns_flags, &ns_if_index); if (!ok) { goto error; /* not today */ } if (rankReachability(ns_flags) > rankReachability(my_flags)) { /* return the best case result */ my_flags = ns_flags; my_index = ns_if_index; if (rankReachability(my_flags) == 2) { /* we're in luck */ break; } } } } else { if ((error == EAI_NODATA) && !targetPrivate->haveDNS) { /* * No DNS servers are defined. Set flags based on * the availability of configured (but not active) * services. */ ok = checkAddress(storeP, NULL, &my_flags, &my_index); if (!ok) { goto error; /* not today */ } if ((my_flags & kSCNetworkFlagsReachable) && (my_flags & kSCNetworkFlagsConnectionRequired)) { /* * 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_flags = 0; my_index = 0; } } break; } } *flags = my_flags; if (if_index != NULL) { *if_index = my_index; } error : if (addresses != NULL) CFRelease(addresses); return ok; } Boolean SCNetworkReachabilityGetFlags(SCNetworkReachabilityRef target, SCNetworkConnectionFlags *flags) { Boolean ok; SCDynamicStoreRef store = NULL; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } if (targetPrivate->rlList != NULL) { // if being watched, return the last known (and what should be current) status *flags = targetPrivate->flags & ~kSCNetworkFlagsFirstResolvePending; return TRUE; } ok = __SCNetworkReachabilityGetFlags(&store, target, flags, NULL, FALSE); *flags &= ~kSCNetworkFlagsFirstResolvePending; if (store != NULL) CFRelease(store); return ok; } static void __SCNetworkReachabilityReachabilitySetNotifications(SCDynamicStoreRef store) { CFStringRef dns_key; CFStringRef key; CFMutableArrayRef keys; CFStringRef pattern; CFMutableArrayRef patterns; keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); // Setup:/Network/Global/IPv4 (for the ServiceOrder) key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainSetup, kSCEntNetIPv4); CFArrayAppendValue(keys, key); CFRelease(key); // Notify:com.apple.SystemConfiguration.dns_configuration dns_key = CFStringCreateWithCString(NULL, dns_configuration_notify_key(), kCFStringEncodingASCII); key = CFStringCreateWithFormat(NULL, NULL, CFSTR("Notify:%@"), dns_key); CFRelease(dns_key); CFArrayAppendValue(keys, key); CFRelease(key); // State:/Network/Global/IPv4 (default route) key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4); CFArrayAppendValue(keys, key); CFRelease(key); // Setup: per-service IPv4 info pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetIPv4); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); // Setup: per-service Interface info pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetInterface); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); // Setup: per-service PPP info (for kSCPropNetPPPDialOnDemand) pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetPPP); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); // State: per-interface IPv4 info pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); // State: per-interface IPv6 info pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv6); CFArrayAppendValue(patterns, pattern); CFRelease(pattern); (void)SCDynamicStoreSetNotificationKeys(store, keys, patterns); CFRelease(keys); CFRelease(patterns); return; } static void __SCNetworkReachabilityReachabilityHandleChanges(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info) { Boolean dnsConfigChanged = FALSE; CFStringRef dnsKey; CFIndex i; CFStringRef key; CFIndex nTargets; const void * targets_q[N_QUICK]; const void ** targets = targets_q; pthread_mutex_lock(&hn_lock); nTargets = CFSetGetCount(hn_targets); if (nTargets == 0) { /* if no addresses being monitored */ goto done; } if (CFArrayGetCount(changedKeys) == 0) { /* if no changes */ goto done; } SCLog(_sc_debug, LOG_INFO, CFSTR("process configuration change")); dnsKey = CFStringCreateWithCString(NULL, dns_configuration_notify_key(), kCFStringEncodingASCII); key = CFStringCreateWithFormat(NULL, NULL, CFSTR("Notify:%@"), dnsKey); CFRelease(dnsKey); if (CFArrayContainsValue(changedKeys, CFRangeMake(0, CFArrayGetCount(changedKeys)), key)) { dnsConfigChanged = TRUE; /* the DNS server(s) have changed */ } CFRelease(key); SCLog(_sc_debug && dnsConfigChanged, LOG_INFO, CFSTR(" DNS configuration changed")); if (nTargets > (CFIndex)(sizeof(targets_q) / sizeof(CFTypeRef))) targets = CFAllocatorAllocate(NULL, nTargets * sizeof(CFTypeRef), 0); CFSetGetValues(hn_targets, targets); for (i = 0; i < nTargets; i++) { SCNetworkReachabilityRef target = targets[i]; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; pthread_mutex_lock(&targetPrivate->lock); if (targetPrivate->type == reachabilityTypeName) { Boolean dnsChanged = dnsConfigChanged; if (!dnsChanged) { /* * if the DNS configuration didn't change we still need to * check that the DNS servers are accessible. */ SCNetworkConnectionFlags ns_flags; Boolean ok; /* check the reachability of the DNS servers */ ok = _SC_checkResolverReachability(&store, &ns_flags, &targetPrivate->haveDNS, targetPrivate->name); if (!ok || (rankReachability(ns_flags) < 2)) { /* if DNS servers are not reachable */ dnsChanged = TRUE; } } if (dnsChanged) { if (targetPrivate->dnsPort) { /* cancel the outstanding DNS query */ lu_async_call_cancel(CFMachPortGetPort(targetPrivate->dnsPort)); CFRunLoopSourceInvalidate(targetPrivate->dnsRLS); CFRelease(targetPrivate->dnsRLS); targetPrivate->dnsRLS = NULL; CFRelease(targetPrivate->dnsPort); targetPrivate->dnsPort = NULL; } /* schedule request to resolve the name again */ if (targetPrivate->resolvedAddress != NULL) { CFRelease(targetPrivate->resolvedAddress); targetPrivate->resolvedAddress = NULL; } targetPrivate->resolvedAddress = NULL; targetPrivate->resolvedAddressError = NETDB_SUCCESS; } } CFRunLoopSourceSignal(targetPrivate->rls); _SC_signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList); pthread_mutex_unlock(&targetPrivate->lock); } if (targets != targets_q) CFAllocatorDeallocate(NULL, targets); done : pthread_mutex_unlock(&hn_lock); return; } static void rlsPerform(void *info) { void *context_info; void (*context_release)(const void *); SCNetworkConnectionFlags flags; uint16_t if_index; Boolean ok; SCNetworkReachabilityCallBack rlsFunction; SCDynamicStoreRef store = NULL; SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)info; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; SCLog(_sc_debug, LOG_DEBUG, CFSTR("process reachability change")); pthread_mutex_lock(&targetPrivate->lock); /* update reachability, notify if status changed */ ok = __SCNetworkReachabilityGetFlags(&store, target, &flags, &if_index, TRUE); if (store != NULL) CFRelease(store); if (!ok) { /* if reachability status not available */ flags = 0; if_index = 0; } if ((targetPrivate->flags == flags) && (targetPrivate->if_index == if_index)) { /* if reachability flags and interface have not changed */ pthread_mutex_unlock(&targetPrivate->lock); SCLog(_sc_debug, LOG_DEBUG, CFSTR("flags/interface match (now %8.8x/%hu)"), flags, if_index); return; } else { SCLog(_sc_debug, LOG_DEBUG, CFSTR("flags/interface have changed (was %8.8x/%hu, now %8.8x/%hu)"), targetPrivate->flags, targetPrivate->if_index, flags, if_index); } /* update flags / interface */ targetPrivate->flags = flags; targetPrivate->if_index = if_index; /* 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; } pthread_mutex_unlock(&targetPrivate->lock); if (rlsFunction != NULL) { (*rlsFunction)(target, flags, context_info); } if (context_release != NULL) { (*context_release)(context_info); } return; } Boolean SCNetworkReachabilitySetCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityCallBack callout, SCNetworkReachabilityContext *context) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; pthread_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); } } pthread_mutex_unlock(&targetPrivate->lock); return TRUE; } Boolean SCNetworkReachabilityScheduleWithRunLoop(SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; Boolean init = FALSE; Boolean ok = FALSE; if (!isA_SCNetworkReachability(target) || runLoop == NULL || runLoopMode == NULL) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } /* schedule the SCNetworkReachability run loop source */ pthread_mutex_lock(&hn_lock); pthread_mutex_lock(&targetPrivate->lock); if (hn_store == NULL) { /* * if we are not monitoring any hosts, start watching */ if (!dns_configuration_watch()) { // if error _SCErrorSet(kSCStatusFailed); goto done; } hn_store = SCDynamicStoreCreate(NULL, CFSTR("SCNetworkReachability"), __SCNetworkReachabilityReachabilityHandleChanges, NULL); if (hn_store == NULL) { SCLog(_sc_verbose, LOG_INFO, CFSTR("SCDynamicStoreCreate() failed")); goto done; } __SCNetworkReachabilityReachabilitySetNotifications(hn_store); hn_storeRLS = SCDynamicStoreCreateRunLoopSource(NULL, hn_store, 0); hn_rlList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); hn_targets = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks); } if (targetPrivate->rls == NULL) { CFRunLoopSourceContext context = { 0 // version , (void *)target // info , CFRetain // retain , CFRelease // release , CFCopyDescription // copyDescription , CFEqual // equal , CFHash // hash , NULL // schedule , NULL // cancel , rlsPerform // perform }; targetPrivate->rls = CFRunLoopSourceCreate(NULL, 0, &context); targetPrivate->rlList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); init = TRUE; } 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); if (targetPrivate->dnsRLS != NULL) { /* if we have an active async DNS query too */ CFRunLoopAddSource(runLoop, targetPrivate->dnsRLS, runLoopMode); } } _SC_schedule(target, runLoop, runLoopMode, targetPrivate->rlList); /* schedule the SCNetworkReachability run loop source */ if (!_SC_isScheduled(NULL, runLoop, runLoopMode, hn_rlList)) { /* * if we do not already have SC notifications scheduled with * this runLoop / runLoopMode */ CFRunLoopAddSource(runLoop, hn_storeRLS, runLoopMode); } _SC_schedule(target, runLoop, runLoopMode, hn_rlList); CFSetAddValue(hn_targets, target); if (init) { SCNetworkConnectionFlags flags; uint16_t if_index; SCDynamicStoreRef store = NULL; /* * if we have yet to schedule SC notifications for this address * - initialize current reachability status */ if (__SCNetworkReachabilityGetFlags(&store, target, &flags, &if_index, TRUE)) { /* * if reachability status available * - set flags * - schedule notification to report status via callback */ targetPrivate->flags = flags; targetPrivate->if_index = if_index; CFRunLoopSourceSignal(targetPrivate->rls); _SC_signalRunLoop(target, targetPrivate->rls, targetPrivate->rlList); } else { /* if reachability status not available, async lookup started */ targetPrivate->flags = 0; targetPrivate->if_index = 0; } if (store != NULL) CFRelease(store); } ok = TRUE; done : pthread_mutex_unlock(&targetPrivate->lock); pthread_mutex_unlock(&hn_lock); return ok; } Boolean SCNetworkReachabilityUnscheduleFromRunLoop(SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; CFIndex n; Boolean ok = FALSE; if (!isA_SCNetworkReachability(target) || runLoop == NULL || runLoopMode == NULL) { _SCErrorSet(kSCStatusInvalidArgument); goto done; } pthread_mutex_lock(&hn_lock); pthread_mutex_lock(&targetPrivate->lock); if (targetPrivate->rls == NULL) { /* if not currently scheduled */ goto done; } if (!_SC_unschedule(NULL, runLoop, runLoopMode, targetPrivate->rlList, FALSE)) { /* if not currently scheduled */ goto done; } n = CFArrayGetCount(targetPrivate->rlList); if (n == 0 || !_SC_isScheduled(NULL, runLoop, runLoopMode, targetPrivate->rlList)) { /* * if this host is no longer scheduled for this runLoop / runLoopMode */ CFRunLoopRemoveSource(runLoop, targetPrivate->rls, runLoopMode); if (targetPrivate->dnsRLS != NULL) { /* if we have an active async DNS query too */ CFRunLoopRemoveSource(runLoop, targetPrivate->dnsRLS, runLoopMode); } if (n == 0) { /* * if this host is no longer scheduled */ CFRunLoopSourceInvalidate(targetPrivate->rls); /* cleanup SCNetworkReachability resources */ CFRelease(targetPrivate->rls); targetPrivate->rls = NULL; CFRelease(targetPrivate->rlList); targetPrivate->rlList = NULL; CFSetRemoveValue(hn_targets, target); /* cleanup notification resources */ if (targetPrivate->dnsPort) { /* if we have an active async DNS query too */ lu_async_call_cancel(CFMachPortGetPort(targetPrivate->dnsPort)); CFRunLoopSourceInvalidate(targetPrivate->dnsRLS); CFRelease(targetPrivate->dnsRLS); targetPrivate->dnsRLS = NULL; CFRelease(targetPrivate->dnsPort); targetPrivate->dnsPort = NULL; } } } (void)_SC_unschedule(target, runLoop, runLoopMode, hn_rlList, FALSE); n = CFArrayGetCount(hn_rlList); if (n == 0 || !_SC_isScheduled(NULL, runLoop, runLoopMode, hn_rlList)) { /* * if we no longer have any addresses scheduled for * this runLoop / runLoopMode */ CFRunLoopRemoveSource(runLoop, hn_storeRLS, runLoopMode); if (n == 0) { /* * if we are no longer monitoring any addresses */ CFRelease(hn_targets); hn_targets = NULL; CFRelease(hn_rlList); hn_rlList = NULL; CFRunLoopSourceInvalidate(hn_storeRLS); CFRelease(hn_storeRLS); hn_storeRLS = NULL; CFRelease(hn_store); hn_store = NULL; /* * until we start monitoring again, ensure that * any resources associated with tracking the * DNS configuration have been released. */ dns_configuration_unwatch(); } } ok = TRUE; done : pthread_mutex_unlock(&targetPrivate->lock); pthread_mutex_unlock(&hn_lock); return ok; }