SCNetworkReachability.c [plain text]
#include <TargetConditionals.h>
#include <sys/cdefs.h>
#include <dispatch/dispatch.h>
#include <dispatch/private.h>
#include <pthread.h>
#include <libkern/OSAtomic.h>
#include <notify.h>
#include <dnsinfo.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <resolv.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/network_agent.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFRuntime.h>
#define SC_LOG_HANDLE __log_SCNetworkReachability
#define SC_LOG_HANDLE_TYPE static
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/SCPrivate.h>
#include "SCD.h"
#include "SCNetworkReachabilityInternal.h"
#include <nw/private.h>
#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, "SCNetworkReachability", NULL, NULL, __SCNetworkReachabilityDeallocate, NULL, NULL, NULL, __SCNetworkReachabilityCopyDescription };
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("<SCNetworkReachability %p [%p]> {"), cf, allocator);
str = _SCNetworkReachabilityCopyTargetDescription(target);
CFStringAppend(result, str);
CFRelease(str);
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"));
}
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);
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;
pthread_once(&initialized, __SCNetworkReachabilityInitialize);
size = sizeof(SCNetworkReachabilityPrivate) - sizeof(CFRuntimeBase);
targetPrivate = (SCNetworkReachabilityPrivateRef)_CFRuntimeCreateInstance(allocator,
__kSCNetworkReachabilityTypeID,
size,
NULL);
if (targetPrivate == NULL) {
return NULL;
}
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;
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
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 <loopback>");
SC_log(LOG_WARNING, "address and remote <non-loopback> 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) {
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)) {
_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) || (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);
return __kSCNetworkReachabilityTypeID;
}
CFArrayRef
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) {
flags |= kSCNetworkReachabilityFlagsTransientConnection;
why = "nw_path_status_satisfied, VPN";
}
if (onDemand &&
type == reachabilityTypeName &&
resolverStatus == nw_resolver_status_complete &&
nw_array_get_count(resolvedEndpoints) == 0) {
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) {
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);
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;
}
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 {
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) {
*flags = __SCNetworkReachabilityGetFlagsFromPath(targetPrivate->lastPath,
targetPrivate->type,
targetPrivate->lastResolverStatus,
targetPrivate->lastResolvedEndpoints,
targetPrivate->lastResolvedEndpointHasFlags,
targetPrivate->lastResolvedEndpointFlags);
targetPrivate->sentFirstUpdate = TRUE;
goto done;
}
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) {
SC_log(LOG_INFO, "%sskipping SCNetworkReachability callback, no longer scheduled",
targetPrivate->log_prefix);
MUTEX_UNLOCK(&targetPrivate->lock);
return;
}
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) {
(*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("<SCNetworkReachability RLS> {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)) {
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 , (void *)target , CFRetain , CFRelease , reachRLSCopyDescription , CFEqual , CFHash , NULL , NULL , reachPerform };
targetPrivate->rls = CFRunLoopSourceCreate(NULL, 0, &context);
targetPrivate->rlList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (!_SC_isScheduled(NULL, runLoop, runLoopMode, targetPrivate->rlList)) {
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)) {
CFRunLoopRemoveSource(runLoop, targetPrivate->rls, runLoopMode);
if (n == 0) {
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)) {
CFRunLoopRemoveSource(runLoop, targetPrivate->rls, runLoopMode);
if (n == 0) {
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);
__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)) {
targetPrivate->lastResolvedEndpointFlags = flags;
targetPrivate->lastResolvedEndpointInterfaceIndex = interfaceIndex;
if (rank == ReachabilityRankReachable) {
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) || ((queue != NULL) && targetPrivate->scheduled)) { _SCErrorSet(kSCStatusInvalidArgument);
goto done;
}
SC_log(LOG_DEBUG, "%sscheduled", targetPrivate->log_prefix);
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)) {
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) { _SCErrorSet(kSCStatusInvalidArgument);
goto done;
}
if (!targetPrivate->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;
}
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;
}