/* * Copyright (c) 2003-2019 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 #include #include #include #include #include #include #include #include #define SC_LOG_HANDLE __log_SCNetworkReachability #define SC_LOG_HANDLE_TYPE static #include #include #include #include "SCD.h" #include "SCNetworkReachabilityInternal.h" #include #define DEBUG_REACHABILITY_TYPE_NAME "create w/name" #define DEBUG_REACHABILITY_TYPE_NAME_OPTIONS " + options" #define DEBUG_REACHABILITY_TYPE_ADDRESS "create w/address" #define DEBUG_REACHABILITY_TYPE_ADDRESS_OPTIONS " + options" #define DEBUG_REACHABILITY_TYPE_ADDRESSPAIR "create w/address pair" #define DEBUG_REACHABILITY_TYPE_ADDRESSPAIR_OPTIONS " + options" #define DEBUG_REACHABILITY_TYPE_PTR "create w/ptr" #define DEBUG_REACHABILITY_TYPE_PTR_OPTIONS " + options" 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 N_QUICK 64 #define REACHABILITY_NETWORK_EXTENSION_AGENT_DOMAIN "NetworkExtension" #define REACHABILITY_AGENT_DATA_KEY "data" static CFStringRef __SCNetworkReachabilityCopyDescription (CFTypeRef cf); static void __SCNetworkReachabilityDeallocate (CFTypeRef cf); static SCNetworkReachabilityFlags __SCNetworkReachabilityGetFlagsFromPath(nw_path_t path, ReachabilityAddressType type, nw_resolver_status_t resolverStatus, nw_array_t resolvedEndpoints, Boolean resolvedEndpointUseFlags, SCNetworkReachabilityFlags resolvedEndpointFlags); static Boolean __SCNetworkReachabilitySetDispatchQueue(SCNetworkReachabilityPrivateRef targetPrivate, dispatch_queue_t queue); 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 dispatch_queue_t _callback_queue() { static dispatch_once_t once; static dispatch_queue_t q; dispatch_once(&once, ^{ q = dispatch_queue_create("SCNetworkReachability.callback", NULL); }); return q; } static os_log_t __log_SCNetworkReachability(void) { static os_log_t log = NULL; if (log == NULL) { log = os_log_create("com.apple.SystemConfiguration", "SCNetworkReachability"); } return log; } #pragma mark - #pragma mark SCNetworkReachability APIs static __inline__ CFTypeRef isA_SCNetworkReachability(CFTypeRef obj) { return (isA_CFType(obj, SCNetworkReachabilityGetTypeID())); } static 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->localAddressEndpoint != NULL) { _SC_sockaddr_to_string(nw_endpoint_get_address(targetPrivate->localAddressEndpoint), buf, sizeof(buf)); CFStringAppendFormat(str, NULL, CFSTR("local address = %s"), buf); } if (targetPrivate->remoteAddressEndpoint != NULL) { _SC_sockaddr_to_string(nw_endpoint_get_address(targetPrivate->remoteAddressEndpoint), buf, sizeof(buf)); CFStringAppendFormat(str, NULL, CFSTR("%s%saddress = %s"), targetPrivate->localAddressEndpoint ? ", " : "", (targetPrivate->type == reachabilityTypeAddressPair) ? "remote " : "", buf); } else { CFStringAppendFormat(str, NULL, CFSTR("default path")); } break; } case reachabilityTypeName : { CFStringAppendFormat(str, NULL, CFSTR("name = %s"), nw_endpoint_get_hostname(targetPrivate->hostnameEndpoint)); break; } case reachabilityTypePTR : { char buf[64]; if (targetPrivate->remoteAddressEndpoint != NULL) { _SC_sockaddr_to_string(nw_endpoint_get_address(targetPrivate->remoteAddressEndpoint), buf, sizeof(buf)); CFStringAppendFormat(str, NULL, CFSTR("ptr = %s"), buf); } break; } } if (targetPrivate->parameters != NULL) { unsigned int if_index; if_index = nw_parameters_get_required_interface_index(targetPrivate->parameters); if (if_index != 0) { CFStringAppendFormat(str, NULL, CFSTR(", if_index = %u"), if_index); } } return str; } static 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"), __SCNetworkReachabilityGetFlagsFromPath(targetPrivate->lastPath, targetPrivate->type, targetPrivate->lastResolverStatus, targetPrivate->lastResolvedEndpoints, targetPrivate->lastResolvedEndpointHasFlags, targetPrivate->lastResolvedEndpointFlags), targetPrivate->lastResolvedEndpointHasFlags ? targetPrivate->lastResolvedEndpointInterfaceIndex : nw_path_get_interface_index(targetPrivate->lastPath)); return str; } static CFStringRef __SCNetworkReachabilityCopyDescription(CFTypeRef cf) { CFAllocatorRef allocator = CFGetAllocator(cf); CFMutableStringRef result; CFStringRef str; SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)cf; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; MUTEX_LOCK(&targetPrivate->lock); 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->resolver && targetPrivate->lastResolverStatus == nw_resolver_status_invalid) { CFStringAppendFormat(result, NULL, CFSTR(" (DNS query active)")); } else if (targetPrivate->lastResolverStatus != nw_resolver_status_invalid) { CFStringAppendFormat(result, NULL, CFSTR(" (%s"), (targetPrivate->lastResolverStatus == nw_resolver_status_complete) ? "complete" : "in progress"); if (nw_array_get_count(targetPrivate->lastResolvedEndpoints) > 0) { nw_array_apply(targetPrivate->lastResolvedEndpoints, ^bool(size_t index, nw_object_t object) { #pragma unused(index) nw_endpoint_t endpoint = (nw_endpoint_t)object; nw_endpoint_type_t endpoint_type = nw_endpoint_get_type(endpoint); if (endpoint_type == nw_endpoint_type_address) { char buf[64]; const struct sockaddr *sa = nw_endpoint_get_address(endpoint); _SC_sockaddr_to_string(sa, buf, sizeof(buf)); CFStringAppendFormat(result, NULL, CFSTR(", %s"), buf); } else if (endpoint_type == nw_endpoint_type_host) { CFStringAppendFormat(result, NULL, CFSTR(", %s"), nw_endpoint_get_hostname(endpoint)); } else { CFStringAppendFormat(result, NULL, CFSTR(", unexpected nw_endpoint type: %d"), endpoint_type); } return TRUE; }); } else { CFStringAppendFormat(result, NULL, CFSTR(", no addresses")); } CFStringAppendFormat(result, NULL, CFSTR(")")); } } 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("}")); MUTEX_UNLOCK(&targetPrivate->lock); return result; } static void __SCNetworkReachabilityDeallocate(CFTypeRef cf) { SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)cf; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; SC_log(LOG_DEBUG, "%srelease", targetPrivate->log_prefix); /* release resources */ MUTEX_LOCK(&targetPrivate->lock); targetPrivate->scheduled = FALSE; if (targetPrivate->hostnameEndpoint) { nw_release(targetPrivate->hostnameEndpoint); targetPrivate->hostnameEndpoint = NULL; } if (targetPrivate->localAddressEndpoint) { nw_release(targetPrivate->localAddressEndpoint); targetPrivate->localAddressEndpoint = NULL; } if (targetPrivate->remoteAddressEndpoint) { nw_release(targetPrivate->remoteAddressEndpoint); targetPrivate->remoteAddressEndpoint = NULL; } if (targetPrivate->parameters) { nw_release(targetPrivate->parameters); targetPrivate->parameters = NULL; } if (targetPrivate->lastPath) { nw_release(targetPrivate->lastPath); targetPrivate->lastPath = NULL; } if (targetPrivate->lastPathParameters) { nw_release(targetPrivate->lastPathParameters); targetPrivate->lastPathParameters = NULL; } if (targetPrivate->lastResolvedEndpoints) { nw_release(targetPrivate->lastResolvedEndpoints); targetPrivate->lastResolvedEndpoints = NULL; } if (targetPrivate->rlsContext.release != NULL) { (*targetPrivate->rlsContext.release)(targetPrivate->rlsContext.info); } MUTEX_UNLOCK(&targetPrivate->lock); pthread_mutex_destroy(&targetPrivate->lock); return; } static void __SCNetworkReachabilityInitialize(void) { __kSCNetworkReachabilityTypeID = _CFRuntimeRegisterClass(&__SCNetworkReachabilityClass); pthread_mutexattr_init(&lock_attr); pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_ERRORCHECK); 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; } /* initialize non-zero/NULL members */ MUTEX_INIT(&targetPrivate->lock); 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) { SC_log(LOG_WARNING, "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) { SC_log(LOG_WARNING, "SCNetworkReachabilityCreateWithAddress[Pair] called with \"struct sockaddr *\" len %d < %zu", address->sa_len, sizeof(struct sockaddr_in6)); warned = TRUE; } break; default : if (!warned) { SC_log(LOG_WARNING, "SCNetworkReachabilityCreateWithAddress[Pair] called with invalid address family %d", address->sa_family); warned = TRUE; } } } return valid; } static bool __SCNetworkReachabilityAddressIsEmpty(const struct sockaddr *address) { if (address == NULL) { return TRUE; } if (address->sa_family == AF_INET) { return (((struct sockaddr_in *)(void *)address)->sin_addr.s_addr == 0); } else if (address->sa_family == AF_INET6) { return IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)(void *)address)->sin6_addr); } else { return FALSE; } } SCNetworkReachabilityRef SCNetworkReachabilityCreateWithAddress(CFAllocatorRef allocator, const struct sockaddr *address) { const struct sockaddr *targetAddress; SCNetworkReachabilityPrivateRef targetPrivate; targetAddress = is_valid_address(address); if (targetAddress == NULL) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator); if (targetPrivate == NULL) { return NULL; } targetPrivate->type = reachabilityTypeAddress; if (!__SCNetworkReachabilityAddressIsEmpty(targetAddress)) { targetPrivate->remoteAddressEndpoint = nw_endpoint_create_address(targetAddress); } SC_log(LOG_DEBUG, "%s%s %@", targetPrivate->log_prefix, DEBUG_REACHABILITY_TYPE_ADDRESS, targetPrivate); return (SCNetworkReachabilityRef)targetPrivate; } #if !TARGET_OS_IPHONE static Boolean is_ipv4_loopback(const struct sockaddr *sa) { uint32_t addr; struct sockaddr_in *sin = (struct sockaddr_in *)(void *)sa; if ((sa == NULL) || (sa->sa_len < sizeof(struct sockaddr_in)) || (sa->sa_family != AF_INET)) { return FALSE; } addr = ntohl(sin->sin_addr.s_addr); return IN_LOOPBACK(addr) ? TRUE : FALSE; } #endif // !TARGET_OS_IPHONE 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; } } #if !TARGET_OS_IPHONE // Check/fix for loopback IP --> remote IP (rdar://26561383) if ((localAddress != NULL) && (remoteAddress != NULL) && is_ipv4_loopback(localAddress) && !is_ipv4_loopback(remoteAddress)) { static Boolean warned = FALSE; if (!warned) { SC_log(LOG_WARNING, "BUG: SCNetworkReachabilityCreateWithAddressPair() called with local "); SC_log(LOG_WARNING, "address and remote address. To return the expected (but actually"); SC_log(LOG_WARNING, "incorrect) result, switched to SCNetworkReachabilityCreateWithAddress() with"); SC_log(LOG_WARNING, "the remote address."); warned = TRUE; } return SCNetworkReachabilityCreateWithAddress(allocator, remoteAddress); } #endif // !TARGET_OS_IPHONE targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator); if (targetPrivate == NULL) { return NULL; } targetPrivate->type = reachabilityTypeAddressPair; if (localAddress != NULL) { targetPrivate->localAddressEndpoint = nw_endpoint_create_address(localAddress); } if (remoteAddress != NULL) { if (is_same_address(localAddress, remoteAddress)) { targetPrivate->remoteAddressEndpoint = nw_retain(targetPrivate->localAddressEndpoint); } else { targetPrivate->remoteAddressEndpoint = nw_endpoint_create_address(remoteAddress); } } targetPrivate->parameters = nw_parameters_create(); nw_parameters_set_local_address(targetPrivate->parameters, targetPrivate->localAddressEndpoint); SC_log(LOG_DEBUG, "%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 (_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->hostnameEndpoint = nw_endpoint_create_host(nodename, "0"); SC_log(LOG_DEBUG, "%s%s %@", targetPrivate->log_prefix, DEBUG_REACHABILITY_TYPE_NAME, targetPrivate); return (SCNetworkReachabilityRef)targetPrivate; } static SCNetworkReachabilityRef __SCNetworkReachabilityCreateWithPTR(CFAllocatorRef allocator, const struct sockaddr *ptrAddress) { SCNetworkReachabilityPrivateRef targetPrivate; ptrAddress = is_valid_address(ptrAddress); if (ptrAddress == NULL) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } targetPrivate = __SCNetworkReachabilityCreatePrivate(allocator); if (targetPrivate == NULL) { return NULL; } targetPrivate->type = reachabilityTypePTR; targetPrivate->remoteAddressEndpoint = nw_endpoint_create_address(ptrAddress); targetPrivate->parameters = nw_parameters_create(); nw_parameters_set_resolve_ptr(targetPrivate->parameters, TRUE); SC_log(LOG_DEBUG, "%s%s %@", targetPrivate->log_prefix, DEBUG_REACHABILITY_TYPE_PTR, targetPrivate); return (SCNetworkReachabilityRef)targetPrivate; } 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; Boolean haveOpt = FALSE; CFStringRef interface = NULL; CFStringRef nodename; CFBooleanRef resolverBypass; SCNetworkReachabilityRef target; SCNetworkReachabilityPrivateRef targetPrivate; unsigned int if_index = 0; char if_name[IFNAMSIZ]; CFDataRef sourceAppAuditToken = NULL; CFStringRef sourceAppBundleID = NULL; 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) || ((size_t)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) || ((size_t)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) || ((size_t)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; } resolverBypass = CFDictionaryGetValue(options, kSCNetworkReachabilityOptionResolverBypass); if ((resolverBypass != NULL) && !isA_CFBoolean(resolverBypass)) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } sourceAppAuditToken = CFDictionaryGetValue(options, kSCNetworkReachabilityOptionSourceAppAuditToken); if ((sourceAppAuditToken != NULL) && (!isA_CFData(sourceAppAuditToken) || (CFDataGetLength(sourceAppAuditToken) != sizeof(audit_token_t)))) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } sourceAppBundleID = CFDictionaryGetValue(options, kSCNetworkReachabilityOptionSourceAppBundleIdentifier); if ((sourceAppBundleID != NULL) && (!isA_CFString(sourceAppBundleID) || (CFStringGetLength(sourceAppBundleID) == 0))) { _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) { if ((addr_l != NULL) || // can't have PTR and target address (addr_r != NULL)) { _SCErrorSet(kSCStatusInvalidArgument); return NULL; } target = __SCNetworkReachabilityCreateWithPTR(NULL, 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, if_name, sizeof(if_name), kCFStringEncodingASCII) == NULL) || ((if_index = if_nametoindex(if_name)) == 0)) { CFRelease(targetPrivate); _SCErrorSet(kSCStatusInvalidArgument); return NULL; } } if (targetPrivate->parameters == NULL) { targetPrivate->parameters = nw_parameters_create(); } if (if_index != 0) { nw_interface_t interfaceObject = nw_interface_create_with_index(if_index); nw_parameters_require_interface(targetPrivate->parameters, interfaceObject); nw_release(interfaceObject); haveOpt = TRUE; } if (resolverBypass != NULL) { targetPrivate->resolverBypass = CFBooleanGetValue(resolverBypass); haveOpt = TRUE; } if (sourceAppAuditToken != NULL) { audit_token_t atoken; CFDataGetBytes(sourceAppAuditToken, CFRangeMake(0, CFDataGetLength(sourceAppAuditToken)), (UInt8 *)&atoken); nw_parameters_set_source_application(targetPrivate->parameters, atoken); haveOpt = TRUE; } else if (sourceAppBundleID != NULL) { char *cBundleID = _SC_cfstring_to_cstring(sourceAppBundleID, NULL, 0, kCFStringEncodingUTF8); if (cBundleID != NULL) { nw_parameters_set_source_application_by_bundle_id(targetPrivate->parameters, cBundleID); CFAllocatorDeallocate(NULL, (void *)cBundleID); } else { SC_log(LOG_WARNING, "failed to convert %@ to a C string", sourceAppBundleID); } haveOpt = TRUE; } if (haveOpt) { 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; } SC_log(LOG_DEBUG, "%s%s %@", targetPrivate->log_prefix, opt, targetPrivate); } return (SCNetworkReachabilityRef)targetPrivate; } 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) { CFMutableArrayRef array = NULL; 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 = 0; } MUTEX_LOCK(&targetPrivate->lock); if (nw_array_get_count(targetPrivate->lastResolvedEndpoints) > 0) { array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); nw_array_apply(targetPrivate->lastResolvedEndpoints, ^bool(size_t index, nw_object_t object) { #pragma unused(index) nw_endpoint_type_t endpoint_type = nw_endpoint_get_type((nw_endpoint_t)object); if (endpoint_type == nw_endpoint_type_address) { const struct sockaddr *address = nw_endpoint_get_address((nw_endpoint_t)object); if (address == NULL) { SC_log(LOG_ERR, "nw_endpoint_type_address w/no address"); return TRUE; } CFDataRef addressData = CFDataCreate(kCFAllocatorDefault, (const uint8_t *)address, address->sa_len); CFArrayAppendValue(array, addressData); CFRelease(addressData); } else if (endpoint_type == nw_endpoint_type_host) { const char *host = nw_endpoint_get_hostname((nw_endpoint_t)object); if (host == NULL) { SC_log(LOG_ERR, "nw_endpoint_type_host w/no host"); return TRUE; } CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, host, kCFStringEncodingASCII); if (string == NULL) { SC_log(LOG_ERR, "nw_endpoint_type_host w/non-ASCII host"); return TRUE; } if (CFStringHasPrefix(string, CFSTR(".")) || CFStringHasSuffix(string, CFSTR("."))) { CFMutableStringRef mutableString = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, string); CFRelease(string); string = mutableString; CFStringTrim(mutableString, CFSTR(".")); } CFArrayAppendValue(array, string); CFRelease(string); } else { SC_log(LOG_ERR, "unexpected nw_endpoint type: %d", endpoint_type); } return TRUE; }); } MUTEX_UNLOCK(&targetPrivate->lock); _SCErrorSet(kSCStatusOK); return array; } #pragma mark - #pragma mark Reachability Flags static void __SCNetworkReachabilityGetAgentVPNFlags(xpc_object_t dictionary, Boolean *vpn, Boolean *onDemand) { const struct netagent *agent = NULL; size_t length = 0; if (dictionary == NULL || vpn == NULL || onDemand == NULL) { return; } *vpn = FALSE; *onDemand = FALSE; agent = xpc_dictionary_get_data(dictionary, REACHABILITY_AGENT_DATA_KEY, &length); if (agent == NULL || length < sizeof(struct netagent) || length != (sizeof(struct netagent) + agent->netagent_data_size)) { return; } if (strncmp(REACHABILITY_NETWORK_EXTENSION_AGENT_DOMAIN, agent->netagent_domain, NETAGENT_DOMAINSIZE) == 0) { *vpn = TRUE; if ((agent->netagent_flags & NETAGENT_FLAG_VOLUNTARY) && !(agent->netagent_flags & NETAGENT_FLAG_ACTIVE)) { *onDemand = TRUE; } } } static bool nw_path_is_linklocal_direct(nw_path_t path) { bool is_linklocal_direct = false; nw_endpoint_t endpoint = nw_path_copy_endpoint(path); if (endpoint == NULL) { return false; } if (nw_endpoint_get_type(endpoint) == nw_endpoint_type_address) { const struct sockaddr_in *sin; sin = (const struct sockaddr_in *)(void *)nw_endpoint_get_address(endpoint);; if ((sin != NULL) && (sin->sin_family == AF_INET) && IN_LINKLOCAL(ntohl(sin->sin_addr.s_addr))) { nw_interface_t interface = nw_path_copy_interface(path); if (interface != NULL) { nw_interface_type_t type = nw_interface_get_type(interface); if ((type == nw_interface_type_wired) || ((type == nw_interface_type_wifi) && (nw_interface_get_subtype(interface) != nw_interface_subtype_wifi_awdl))) { is_linklocal_direct = true; } nw_release(interface); } } } nw_release(endpoint); return is_linklocal_direct; } static SCNetworkReachabilityFlags __SCNetworkReachabilityGetFlagsFromPath(nw_path_t path, ReachabilityAddressType type, nw_resolver_status_t resolverStatus, nw_array_t resolvedEndpoints, Boolean resolvedEndpointUseFlags, SCNetworkReachabilityFlags resolvedEndpointFlags) { __block SCNetworkReachabilityFlags flags = kSCNetworkReachabilityFlagsReachable; __block const char *why = "???"; if (path != NULL) { nw_path_status_t status = nw_path_get_status(path); if (status == nw_path_status_satisfied) { __block bool checkDNSFlags = TRUE; flags = kSCNetworkReachabilityFlagsReachable; why = "nw_path_status_satisfied"; #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR if (nw_path_uses_interface_type(path, nw_interface_type_cellular)) { flags |= (kSCNetworkReachabilityFlagsTransientConnection | kSCNetworkReachabilityFlagsIsWWAN); why = "nw_path_status_satisfied, cellular"; } #endif // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR xpc_object_t agent_dictionary = nw_path_copy_netagent_dictionary(path); if (agent_dictionary != NULL) { if (xpc_dictionary_get_count(agent_dictionary) > 0) { xpc_dictionary_apply(agent_dictionary, ^bool(const char *key, xpc_object_t value) { #pragma unused(key) Boolean vpn = FALSE; Boolean onDemand = FALSE; __SCNetworkReachabilityGetAgentVPNFlags(value, &vpn, &onDemand); if (vpn) { // VPN flows are transient flags |= kSCNetworkReachabilityFlagsTransientConnection; why = "nw_path_status_satisfied, VPN"; } if (onDemand && type == reachabilityTypeName && resolverStatus == nw_resolver_status_complete && nw_array_get_count(resolvedEndpoints) == 0) { // On Demand by hostname, when no address has been resolved flags |= (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsConnectionOnDemand); why = "nw_path_status_satisfied, OnDemand"; checkDNSFlags = FALSE; } return TRUE; }); } xpc_release(agent_dictionary); } if (isReachabilityTypeName(type)) { if (checkDNSFlags) { if (resolverStatus == nw_resolver_status_complete && nw_array_get_count(resolvedEndpoints) == 0) { // DNS didn't resolve, as a final answer for now. Not reachable! flags = 0; why = "nw_path_status_satisfied, DNS not reachable"; } else if (resolvedEndpointUseFlags) { flags = resolvedEndpointFlags; why = "nw_path_status_satisfied, resolved endpoint flags"; } } } else { if (nw_path_is_direct(path) || nw_path_is_linklocal_direct(path)) { flags |= kSCNetworkReachabilityFlagsIsDirect; why = "nw_path_status_satisfied, by address, direct"; } if (nw_path_is_local(path)) { flags |= kSCNetworkReachabilityFlagsIsLocalAddress; why = "nw_path_status_satisfied, by address, local"; } } } else if (status == nw_path_status_unsatisfied) { flags = 0; why = "nw_path_status_unsatisfied"; #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR if (nw_path_uses_interface_type(path, nw_interface_type_cellular)) { flags |= kSCNetworkReachabilityFlagsIsWWAN; why = "nw_path_status_unsatisfied, WWAN"; } #endif // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR } else if (status == nw_path_status_satisfiable) { flags = (kSCNetworkReachabilityFlagsReachable | kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection); why = "nw_path_status_satisfiable"; uuid_t vpn_uuid; if (nw_path_get_vpn_config_id(path, &vpn_uuid)) { flags |= kSCNetworkReachabilityFlagsConnectionOnDemand; why = "nw_path_status_satisfiable, OnDemand"; } #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR else if (nw_path_uses_interface_type(path, nw_interface_type_cellular)) { flags |= kSCNetworkReachabilityFlagsIsWWAN; why = "nw_path_status_satisfiable, WWAN"; } #endif // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR } } SC_log(LOG_DEBUG, "__SCNetworkReachabilityGetFlagsFromPath, flags = 0x%08x, %s", flags, why); return (flags & kSCNetworkReachabilityFlagsMask); } static nw_endpoint_t __SCNetworkReachabilityGetPrimaryEndpoint(SCNetworkReachabilityPrivateRef targetPrivate) { if (targetPrivate->type == reachabilityTypeName) { return targetPrivate->hostnameEndpoint; } else if (targetPrivate->type == reachabilityTypeAddress || targetPrivate->type == reachabilityTypeAddressPair || targetPrivate->type == reachabilityTypePTR) { return targetPrivate->remoteAddressEndpoint; } return NULL; } int SCNetworkReachabilityGetInterfaceIndex(SCNetworkReachabilityRef target) { int if_index = -1; Boolean ok = TRUE; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; SCNetworkReachabilityFlags flags = 0; if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return if_index; } MUTEX_LOCK(&targetPrivate->lock); flags = __SCNetworkReachabilityGetFlagsFromPath(targetPrivate->lastPath, targetPrivate->type, nw_resolver_status_invalid, NULL, targetPrivate->lastResolvedEndpointHasFlags, targetPrivate->lastResolvedEndpointFlags); /* Only return the if_index if the connection is reachable not for reachable connection * required etc ... */ if (ok && __SCNetworkReachabilityRank(flags) == ReachabilityRankReachable) { if (targetPrivate->lastResolvedEndpointHasFlags) { if_index = targetPrivate->lastResolvedEndpointInterfaceIndex; } else { if_index = nw_path_get_interface_index(targetPrivate->lastPath); } } MUTEX_UNLOCK(&targetPrivate->lock); return if_index; } // CrazyIvan46 is the feature that allows connections to IPv4 literals on IPv6-only (NAT64+DNS64) networks to work // This function replaces the path when the initial one isn't satisfied and our target is an IPv4 literal // It tries IPv6 reachability instead in case we could synthesize another address to connect to static OS_OBJECT_RETURNS_RETAINED nw_path_t __SCNetworkReachabilityCreateCrazyIvan46Path(nw_path_t path, nw_endpoint_t endpoint, nw_parameters_t parameters, Boolean allow_resolution) { nw_path_t retPath = NULL; const struct sockaddr *sa; if ((nw_path_get_status(path) != nw_path_status_unsatisfied) || (NULL == endpoint) || (nw_endpoint_get_type(endpoint) != nw_endpoint_type_address)) { return NULL; } sa = nw_endpoint_get_address(endpoint); if (sa->sa_family != AF_INET) { return NULL; } if (allow_resolution) { uint32_t ifIndex = 0; int32_t numPrefixes; nw_nat64_prefix_t *prefixes = NULL; struct sockaddr_in sin; memcpy(&sin, sa, MIN(sizeof(sin), sa->sa_len)); if (NULL != parameters) { ifIndex = nw_parameters_get_required_interface_index(parameters); } numPrefixes = nw_nat64_copy_prefixes(&ifIndex, &prefixes); if (numPrefixes > 0) { struct sockaddr_in6 synthesizedAddress = { .sin6_len = sizeof(struct sockaddr_in6), .sin6_family = AF_INET6, .sin6_port = htons(nw_endpoint_get_port(endpoint)), .sin6_flowinfo = 0, .sin6_scope_id = 0 }; nw_endpoint_t synthesizedEndpoint; nw_path_evaluator_t synthesizedEvaluator; nw_path_t synthesizedPath; nw_nat64_synthesize_v6(&prefixes[0], &sin.sin_addr, &synthesizedAddress.sin6_addr); synthesizedEndpoint = nw_endpoint_create_address((const struct sockaddr *)&synthesizedAddress); synthesizedEvaluator = nw_path_create_evaluator_for_endpoint(synthesizedEndpoint, parameters); synthesizedPath = nw_path_evaluator_copy_path(synthesizedEvaluator); if (nw_path_get_status(synthesizedPath) != nw_path_status_unsatisfied) { retPath = synthesizedPath; SC_log(LOG_INFO, "Using CrazyIvan46 synthesized reachability result"); } else { nw_release(synthesizedPath); } nw_release(synthesizedEvaluator); nw_release(synthesizedEndpoint); } } else { // Don't synthesize in non-scheduled mode to avoid generating DNS traffic nw_path_t v6Path; nw_path_evaluator_t v6PathEvaluator; nw_parameters_t v6Parameters; if (NULL != parameters) { v6Parameters = nw_parameters_copy(parameters); } else { v6Parameters = nw_parameters_create(); } nw_parameters_set_required_address_family(v6Parameters, AF_INET6); v6PathEvaluator = nw_path_create_evaluator_for_endpoint(NULL, v6Parameters); v6Path = nw_path_evaluator_copy_path(v6PathEvaluator); if (nw_path_get_status(v6Path) != nw_path_status_unsatisfied) { retPath = v6Path; SC_log(LOG_INFO, "Using CrazyIvan46 simple reachability result"); } else { nw_release(v6Path); } nw_release(v6PathEvaluator); nw_release(v6Parameters); } return retPath; } Boolean SCNetworkReachabilityGetFlags(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags *flags) { nw_path_t crazyIvanPath; nw_endpoint_t endpoint; Boolean ok = TRUE; nw_path_t path; nw_path_evaluator_t pathEvaluator; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } MUTEX_LOCK(&targetPrivate->lock); if (targetPrivate->scheduled) { // if being watched, return the last known (and what should be current) status *flags = __SCNetworkReachabilityGetFlagsFromPath(targetPrivate->lastPath, targetPrivate->type, targetPrivate->lastResolverStatus, targetPrivate->lastResolvedEndpoints, targetPrivate->lastResolvedEndpointHasFlags, targetPrivate->lastResolvedEndpointFlags); // because we have synchronously captured the current status, we no longer // need our by-name required callback targetPrivate->sentFirstUpdate = TRUE; goto done; } // Not being watched, so run a one-shot path evaluator // We don't care about DNS resolution in this case, since we only need to have the // DNS resolution to support clients watching reachability to get updates endpoint = __SCNetworkReachabilityGetPrimaryEndpoint(targetPrivate); pathEvaluator = nw_path_create_evaluator_for_endpoint(endpoint, targetPrivate->parameters); path = nw_path_evaluator_copy_path(pathEvaluator); if (isReachabilityTypeAddress(targetPrivate->type)) { crazyIvanPath = __SCNetworkReachabilityCreateCrazyIvan46Path(path, endpoint, targetPrivate->parameters, FALSE); if (NULL != crazyIvanPath) { nw_release(path); path = crazyIvanPath; } } *flags = __SCNetworkReachabilityGetFlagsFromPath(path, 0, nw_resolver_status_invalid, NULL, FALSE, 0); nw_release(path); nw_release(pathEvaluator); done : MUTEX_UNLOCK(&targetPrivate->lock); return ok; } #pragma mark - #pragma mark Notifications static void reachPerformAndUnlock(SCNetworkReachabilityPrivateRef targetPrivate) { void *context_info; void (*context_release)(const void *); SCNetworkReachabilityCallBack rlsFunction; SCNetworkReachabilityFlags flags = 0; if (!targetPrivate->scheduled) { // if no longer scheduled SC_log(LOG_INFO, "%sskipping SCNetworkReachability callback, no longer scheduled", targetPrivate->log_prefix); MUTEX_UNLOCK(&targetPrivate->lock); return; } // 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; } flags = __SCNetworkReachabilityGetFlagsFromPath(targetPrivate->lastPath, targetPrivate->type, targetPrivate->lastResolverStatus, targetPrivate->lastResolvedEndpoints, targetPrivate->lastResolvedEndpointHasFlags, targetPrivate->lastResolvedEndpointFlags); MUTEX_UNLOCK(&targetPrivate->lock); if (rlsFunction != NULL) { SC_log(LOG_DEBUG, "%sexec SCNetworkReachability callout w/flags = 0x%08x", targetPrivate->log_prefix, flags); (*rlsFunction)((SCNetworkReachabilityRef)targetPrivate, flags, context_info); } if (context_release != NULL) { (*context_release)(context_info); } return; } static void reachPerform(void *info) { SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)info; MUTEX_LOCK(&targetPrivate->lock); reachPerformAndUnlock(targetPrivate); } static void reachUpdateAndUnlock(SCNetworkReachabilityPrivateRef targetPrivate) { targetPrivate->sentFirstUpdate = TRUE; if (targetPrivate->rls != NULL) { if (targetPrivate->rlList != NULL) { CFRunLoopSourceSignal(targetPrivate->rls); _SC_signalRunLoop(targetPrivate, targetPrivate->rls, targetPrivate->rlList); } MUTEX_UNLOCK(&targetPrivate->lock); } else { reachPerformAndUnlock(targetPrivate); } } 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) { memcpy(&targetPrivate->rlsContext, context, 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); } Boolean SCNetworkReachabilityScheduleWithRunLoop(SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode) { Boolean success = FALSE; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; if (!isA_SCNetworkReachability(target) || (runLoop == NULL) || (runLoopMode == NULL)) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } MUTEX_LOCK(&targetPrivate->lock); if (targetPrivate->scheduled) { if (targetPrivate->rls != NULL && targetPrivate->rlList != NULL) { 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); MUTEX_UNLOCK(&targetPrivate->lock); return TRUE; } else { MUTEX_UNLOCK(&targetPrivate->lock); _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } } CFRunLoopSourceContext context = { 0 // version , (void *)target // info , CFRetain // retain , CFRelease // release , reachRLSCopyDescription // copyDescription , CFEqual // equal , CFHash // hash , NULL // schedule , NULL // cancel , reachPerform // perform }; targetPrivate->rls = CFRunLoopSourceCreate(NULL, 0, &context); targetPrivate->rlList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 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); success = __SCNetworkReachabilitySetDispatchQueue(targetPrivate, _callback_queue()); if (!success) { if (_SC_unschedule(target, runLoop, runLoopMode, targetPrivate->rlList, FALSE)) { CFIndex 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 CFRelease(targetPrivate->rlList); targetPrivate->rlList = NULL; CFRunLoopSourceInvalidate(targetPrivate->rls); CFRelease(targetPrivate->rls); targetPrivate->rls = NULL; } } } } MUTEX_UNLOCK(&targetPrivate->lock); return success; } Boolean SCNetworkReachabilityUnscheduleFromRunLoop(SCNetworkReachabilityRef target, CFRunLoopRef runLoop, CFStringRef runLoopMode) { Boolean success = FALSE; Boolean unscheduleDispatchQueue = FALSE; SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; if (!isA_SCNetworkReachability(target) || (runLoop == NULL) || (runLoopMode == NULL)) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } MUTEX_LOCK(&targetPrivate->lock); if (targetPrivate->rlList == NULL || targetPrivate->rls == NULL || !targetPrivate->scheduled) { MUTEX_UNLOCK(&targetPrivate->lock); _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } if (_SC_unschedule(target, runLoop, runLoopMode, targetPrivate->rlList, FALSE)) { CFIndex 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 unscheduleDispatchQueue = TRUE; CFRelease(targetPrivate->rlList); targetPrivate->rlList = NULL; CFRunLoopSourceInvalidate(targetPrivate->rls); CFRelease(targetPrivate->rls); targetPrivate->rls = NULL; } } } if (unscheduleDispatchQueue) { success = __SCNetworkReachabilitySetDispatchQueue(targetPrivate, NULL); } else { success = TRUE; } MUTEX_UNLOCK(&targetPrivate->lock); return success; } static __inline__ void __SCNetworkReachabilityCopyPathStatus(SCNetworkReachabilityPrivateRef targetPrivate, SCNetworkReachabilityFlags *flags, uint *ifIndex, size_t *endpointCount) { if (flags) { *flags = __SCNetworkReachabilityGetFlagsFromPath(targetPrivate->lastPath, targetPrivate->type, targetPrivate->lastResolverStatus, targetPrivate->lastResolvedEndpoints, targetPrivate->lastResolvedEndpointHasFlags, targetPrivate->lastResolvedEndpointFlags); } if (ifIndex) { *ifIndex = nw_path_get_interface_index(targetPrivate->lastPath); } if (endpointCount) { *endpointCount = nw_array_get_count(targetPrivate->lastResolvedEndpoints); } return; } static __inline__ Boolean __SCNetworkReachabilityShouldUpdateClient(SCNetworkReachabilityPrivateRef targetPrivate, SCNetworkReachabilityFlags oldFlags, uint oldIFIndex, size_t oldEndpointCount) { SCNetworkReachabilityFlags newFlags = 0; uint newIFIndex = 0; size_t newEndpointCount = 0; __SCNetworkReachabilityCopyPathStatus(targetPrivate, &newFlags, &newIFIndex, &newEndpointCount); return (!targetPrivate->sentFirstUpdate || oldFlags != newFlags || oldIFIndex != newIFIndex || oldEndpointCount != newEndpointCount); } static void __SCNetworkReachabilityRestartResolver(SCNetworkReachabilityPrivateRef targetPrivate) { if (targetPrivate && !targetPrivate->resolverBypass && isReachabilityTypeName(targetPrivate->type)) { nw_resolver_t resolver; CFRetain(targetPrivate); if (NULL != targetPrivate->resolver) { nw_resolver_cancel(targetPrivate->resolver); } if (targetPrivate->lastPath != NULL) { resolver = nw_resolver_create_with_path(targetPrivate->lastPath); } else { resolver = nw_resolver_create_with_endpoint(__SCNetworkReachabilityGetPrimaryEndpoint(targetPrivate), targetPrivate->lastPathParameters ? targetPrivate->lastPathParameters : targetPrivate->parameters); } if (resolver == NULL) { SC_log(LOG_ERR, "%sfailed to create a nw_resolver", targetPrivate->log_prefix); targetPrivate->resolver = NULL; CFRelease(targetPrivate); return; } targetPrivate->resolver = resolver; nw_resolver_set_cancel_handler(resolver, ^(void) { MUTEX_LOCK(&targetPrivate->lock); if (resolver == targetPrivate->resolver) { targetPrivate->resolver = NULL; } nw_release(resolver); MUTEX_UNLOCK(&targetPrivate->lock); CFRelease(targetPrivate); }); if (!nw_resolver_set_update_handler(resolver, targetPrivate->dispatchQueue, ^(nw_resolver_status_t status, nw_array_t resolved_endpoints) { MUTEX_LOCK(&targetPrivate->lock); if (targetPrivate->scheduled) { SCNetworkReachabilityFlags oldFlags = 0; uint oldIFIndex = 0; size_t oldEndpointCount = 0; __SCNetworkReachabilityCopyPathStatus(targetPrivate, &oldFlags, &oldIFIndex, &oldEndpointCount); targetPrivate->lastResolverStatus = status; nw_release(targetPrivate->lastResolvedEndpoints); targetPrivate->lastResolvedEndpoints = nw_retain(resolved_endpoints); // Run path evaluation on the resolved endpoints __block Boolean hasFlags = FALSE; targetPrivate->lastResolvedEndpointHasFlags = FALSE; targetPrivate->lastResolvedEndpointFlags = 0; targetPrivate->lastResolvedEndpointInterfaceIndex = 0; nw_array_apply(targetPrivate->lastResolvedEndpoints, ^bool(size_t index, nw_object_t object) { #pragma unused(index) SCNetworkReachabilityFlags flags = 0; uint interfaceIndex = 0; ReachabilityRankType rank; nw_endpoint_t resolvedEndpoint = (nw_endpoint_t)object; nw_path_evaluator_t pathEvaluator = nw_path_create_evaluator_for_endpoint(resolvedEndpoint, targetPrivate->lastPathParameters ? targetPrivate->lastPathParameters : targetPrivate->parameters); nw_path_t path = nw_path_evaluator_copy_path(pathEvaluator); if (path != NULL) { flags = __SCNetworkReachabilityGetFlagsFromPath(path, 0, nw_resolver_status_invalid, NULL, FALSE, 0); hasFlags = TRUE; } interfaceIndex = nw_path_get_interface_index(path); nw_release(path); nw_release(pathEvaluator); rank = __SCNetworkReachabilityRank(flags); if (rank > __SCNetworkReachabilityRank(targetPrivate->lastResolvedEndpointFlags)) { // Return the best case result targetPrivate->lastResolvedEndpointFlags = flags; targetPrivate->lastResolvedEndpointInterfaceIndex = interfaceIndex; if (rank == ReachabilityRankReachable) { // Can't get any better than REACHABLE return FALSE; } } return TRUE; }); targetPrivate->lastResolvedEndpointHasFlags = hasFlags; if (__SCNetworkReachabilityShouldUpdateClient(targetPrivate, oldFlags, oldIFIndex, oldEndpointCount)) { reachUpdateAndUnlock(targetPrivate); } else { MUTEX_UNLOCK(&targetPrivate->lock); } } else { MUTEX_UNLOCK(&targetPrivate->lock); } })) { nw_release(resolver); targetPrivate->resolver = NULL; CFRelease(targetPrivate); } } } static Boolean __SCNetworkReachabilitySetDispatchQueue(SCNetworkReachabilityPrivateRef targetPrivate, dispatch_queue_t queue) { Boolean ok = FALSE; if (queue != NULL) { nw_path_t crazyIvanPath; nw_endpoint_t endpoint; nw_path_evaluator_t pathEvaluator; 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; } SC_log(LOG_DEBUG, "%sscheduled", targetPrivate->log_prefix); // retain dispatch queue dispatch_retain(queue); nw_path_evaluator_cancel(targetPrivate->pathEvaluator); endpoint = __SCNetworkReachabilityGetPrimaryEndpoint(targetPrivate); pathEvaluator = nw_path_create_evaluator_for_endpoint(endpoint, targetPrivate->parameters); targetPrivate->pathEvaluator = pathEvaluator; targetPrivate->dispatchQueue = queue; targetPrivate->scheduled = TRUE; if (isReachabilityTypeName(targetPrivate->type)) { // we must have at least one callback for by-name queries targetPrivate->sentFirstUpdate = FALSE; } else { targetPrivate->sentFirstUpdate = TRUE; } nw_release(targetPrivate->lastPath); targetPrivate->lastPath = nw_path_evaluator_copy_path(pathEvaluator); if (isReachabilityTypeAddress(targetPrivate->type)) { crazyIvanPath = __SCNetworkReachabilityCreateCrazyIvan46Path(targetPrivate->lastPath, endpoint, targetPrivate->parameters, FALSE); if (NULL != crazyIvanPath) { nw_release(targetPrivate->lastPath); targetPrivate->lastPath = crazyIvanPath; } } nw_release(targetPrivate->lastPathParameters); targetPrivate->lastPathParameters = nw_path_copy_derived_parameters(targetPrivate->lastPath); targetPrivate->lastResolverStatus = nw_resolver_status_invalid; nw_release(targetPrivate->lastResolvedEndpoints); targetPrivate->lastResolvedEndpoints = NULL; __SCNetworkReachabilityRestartResolver(targetPrivate); CFRetain(targetPrivate); nw_path_evaluator_set_cancel_handler(pathEvaluator, ^(void) { MUTEX_LOCK(&targetPrivate->lock); if (pathEvaluator == targetPrivate->pathEvaluator) { targetPrivate->pathEvaluator = NULL; } nw_release(pathEvaluator); MUTEX_UNLOCK(&targetPrivate->lock); CFRelease(targetPrivate); }); if (!nw_path_evaluator_set_update_handler(pathEvaluator, targetPrivate->dispatchQueue, ^(nw_path_t path) { MUTEX_LOCK(&targetPrivate->lock); if (targetPrivate->scheduled) { nw_path_t crazyIvanPath; SCNetworkReachabilityFlags oldFlags = 0; uint oldIFIndex = 0; size_t oldEndpointCount = 0; __SCNetworkReachabilityCopyPathStatus(targetPrivate, &oldFlags, &oldIFIndex, &oldEndpointCount); nw_release(targetPrivate->lastPath); targetPrivate->lastPath = nw_retain(path); if (isReachabilityTypeAddress(targetPrivate->type)) { crazyIvanPath = __SCNetworkReachabilityCreateCrazyIvan46Path(targetPrivate->lastPath, endpoint, targetPrivate->parameters, TRUE); if (NULL != crazyIvanPath) { nw_release(targetPrivate->lastPath); targetPrivate->lastPath = crazyIvanPath; } } if (targetPrivate->lastResolverStatus == nw_resolver_status_complete) { targetPrivate->lastResolverStatus = nw_resolver_status_invalid; __SCNetworkReachabilityRestartResolver(targetPrivate); } if (__SCNetworkReachabilityShouldUpdateClient(targetPrivate, oldFlags, oldIFIndex, oldEndpointCount)) { reachUpdateAndUnlock(targetPrivate); } else { MUTEX_UNLOCK(&targetPrivate->lock); } } else { MUTEX_UNLOCK(&targetPrivate->lock); } })) { targetPrivate->pathEvaluator = NULL; nw_release(pathEvaluator); CFRelease(targetPrivate); } } else { if (targetPrivate->dispatchQueue == NULL) { // if we should be scheduled on a dispatch queue (but are not) _SCErrorSet(kSCStatusInvalidArgument); goto done; } if (!targetPrivate->scheduled) { // if not currently scheduled _SCErrorSet(kSCStatusInvalidArgument); goto done; } targetPrivate->scheduled = FALSE; targetPrivate->sentFirstUpdate = FALSE; nw_path_evaluator_cancel(targetPrivate->pathEvaluator); targetPrivate->pathEvaluator = NULL; nw_release(targetPrivate->lastPath); targetPrivate->lastPath = NULL; nw_release(targetPrivate->lastPathParameters); targetPrivate->lastPathParameters = NULL; nw_release(targetPrivate->lastResolvedEndpoints); targetPrivate->lastResolvedEndpoints = NULL; if (NULL != targetPrivate->resolver) { nw_resolver_cancel(targetPrivate->resolver); targetPrivate->resolver = NULL; } if (targetPrivate->dispatchQueue != NULL) { dispatch_release(targetPrivate->dispatchQueue); targetPrivate->dispatchQueue = NULL; } SC_log(LOG_DEBUG, "%sunscheduled", targetPrivate->log_prefix); } ok = TRUE; done: return ok; } Boolean SCNetworkReachabilitySetDispatchQueue(SCNetworkReachabilityRef target, dispatch_queue_t queue) { if (!isA_SCNetworkReachability(target)) { _SCErrorSet(kSCStatusInvalidArgument); return FALSE; } SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target; MUTEX_LOCK(&targetPrivate->lock); Boolean success = __SCNetworkReachabilitySetDispatchQueue(targetPrivate, queue); MUTEX_UNLOCK(&targetPrivate->lock); return success; } /* * _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) { #pragma unused(storeP) #pragma unused(sa) nw_path_evaluator_t evaluator = nw_path_create_default_evaluator(); nw_path_t path = nw_path_evaluator_copy_path(evaluator); if (nw_path_get_status(path) == nw_path_status_unsatisfied_network) { if (flags) { *flags = 0; } if (haveDNS) { *haveDNS = FALSE; } } else { if (flags) { *flags = kSCNetworkReachabilityFlagsReachable; } if (haveDNS) { *haveDNS = TRUE; } } nw_release(evaluator); nw_release(path); return TRUE; }