SCNetworkReachabilityServer_client.c [plain text]
#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCPrivate.h>
#include "SCNetworkReachabilityInternal.h"
#include <xpc/xpc.h>
#include <xpc/private.h>
#include <sys/rbtree.h>
#pragma mark -
#pragma mark Globals
static Boolean serverAvailable = TRUE;
#pragma mark -
#pragma mark Support functions
static void
log_xpc_object(const char *msg, xpc_object_t obj)
{
char *desc;
desc = xpc_copy_description(obj);
SCLog(TRUE, LOG_DEBUG, CFSTR("%s = %s"), msg, desc);
free(desc);
}
#pragma mark -
#pragma mark Reachability [RBT] client support
typedef struct {
rb_node_t rbn;
SCNetworkReachabilityRef target;
} reach_request_t;
static int
_rbt_compare_transaction_nodes(void *context, const void *n1, const void *n2)
{
uint64_t a = (uintptr_t)(((reach_request_t *)n1)->target);
uint64_t b = (uintptr_t)(((reach_request_t *)n2)->target);
if (a == b) {
return 0;
} else if (a < b) {
return -1;
} else {
return 1;
}
}
static int
_rbt_compare_transaction_key(void *context, const void *n1, const void *key)
{
uint64_t a = (uintptr_t)(((reach_request_t *)n1)->target);
uint64_t b = *(uint64_t *)key;
if (a == b) {
return 0;
} else if (a < b) {
return -1;
} else {
return 1;
}
}
static rb_tree_t *
_reach_requests_rbt()
{
static dispatch_once_t once;
static const rb_tree_ops_t ops = {
.rbto_compare_nodes = _rbt_compare_transaction_nodes,
.rbto_compare_key = _rbt_compare_transaction_key,
.rbto_node_offset = offsetof(reach_request_t, rbn),
.rbto_context = NULL
};
static rb_tree_t rbt;
dispatch_once(&once, ^{
rb_tree_init(&rbt, &ops);
});
return &rbt;
}
static dispatch_queue_t
_reach_requests_rbt_queue()
{
static dispatch_once_t once;
static dispatch_queue_t q;
dispatch_once(&once, ^{
q = dispatch_queue_create(REACH_SERVICE_NAME ".requests.rbt", NULL);
});
return q;
}
static reach_request_t *
_reach_request_create(SCNetworkReachabilityRef target)
{
reach_request_t *request;
request = calloc(1, sizeof(*request));
request->target = CFRetain(target);
return request;
}
static void
_reach_request_release(reach_request_t *request)
{
SCNetworkReachabilityRef target = request->target;
CFRelease(target);
free(request);
return;
}
static void
_reach_request_add(SCNetworkReachabilityRef target)
{
uint64_t target_id = (uintptr_t)target;
dispatch_sync(_reach_requests_rbt_queue(), ^{
rb_tree_t *rbt = _reach_requests_rbt();
reach_request_t *request;
request = rb_tree_find_node(rbt, &target_id);
if (request == NULL) {
request = _reach_request_create(target);
rb_tree_insert_node(rbt, request);
}
});
return;
}
static void
_reach_request_remove(SCNetworkReachabilityRef target)
{
uint64_t target_id = (uintptr_t)target;
dispatch_sync(_reach_requests_rbt_queue(), ^{ rb_tree_t *rbt = _reach_requests_rbt();
reach_request_t *request;
request = rb_tree_find_node(rbt, &target_id);
if (request != NULL) {
rb_tree_remove_node(rbt, request);
_reach_request_release(request);
}
});
return;
}
static SCNetworkReachabilityRef
_reach_request_copy_target(uint64_t target_id)
{
__block SCNetworkReachabilityRef target = NULL;
dispatch_sync(_reach_requests_rbt_queue(), ^{
rb_tree_t *rbt = _reach_requests_rbt();
reach_request_t *request;
request = rb_tree_find_node(rbt, &target_id);
if (request != NULL) {
target = request->target;
CFRetain(target);
}
});
return target;
}
#pragma mark -
#pragma mark Reachability [XPC] client support
static void
handle_reachability_status(SCNetworkReachabilityRef target, xpc_object_t dict)
{
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
if (_sc_debug) {
SCLog(TRUE, LOG_INFO, CFSTR("%sgot [async] notification"),
targetPrivate->log_prefix);
}
__SCNetworkReachabilityUpdateConcurrent(target);
return;
}
static void
handle_async_notification(SCNetworkReachabilityRef target, xpc_object_t dict)
{
int64_t op;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
op = xpc_dictionary_get_int64(dict, MESSAGE_NOTIFY);
switch (op) {
case MESSAGE_REACHABILITY_STATUS :
handle_reachability_status(target, dict);
break;
default :
SCLog(TRUE, LOG_ERR, CFSTR("%sgot [async] unknown reply : %lld"),
targetPrivate->log_prefix,
op);
log_xpc_object(" reply", dict);
break;
}
return;
}
static dispatch_queue_t
_reach_xpc_queue()
{
static dispatch_once_t once;
static dispatch_queue_t q;
dispatch_once(&once, ^{
q = dispatch_queue_create(REACH_SERVICE_NAME ".xpc", NULL);
});
return q;
}
static void
_reach_connection_reconnect(xpc_connection_t connection);
static xpc_connection_t
_reach_connection_create()
{
xpc_connection_t c;
#if !TARGET_IPHONE_SIMULATOR
const uint64_t flags = XPC_CONNECTION_MACH_SERVICE_PRIVILEGED;
#else // !TARGET_IPHONE_SIMULATOR
const uint64_t flags = 0;
#endif // !TARGET_IPHONE_SIMULATOR
const char *name;
dispatch_queue_t q = _reach_xpc_queue();
name = getenv("REACH_SERVER");
if ((name == NULL) || (issetugid() != 0)) {
name = REACH_SERVICE_NAME;
}
c = xpc_connection_create_mach_service(name, q, flags);
xpc_connection_set_event_handler(c, ^(xpc_object_t xobj) {
xpc_type_t type;
type = xpc_get_type(xobj);
if (type == XPC_TYPE_DICTIONARY) {
SCNetworkReachabilityRef target;
uint64_t target_id;
target_id = xpc_dictionary_get_uint64(xobj, REACH_CLIENT_TARGET_ID);
if (target_id == 0) {
SCLog(TRUE, LOG_ERR,
CFSTR("reach client %p: async reply with no target [ID]"),
c);
log_xpc_object(" reply", xobj);
return;
}
target = _reach_request_copy_target(target_id);
if (target == NULL) {
return;
}
xpc_retain(xobj);
dispatch_async(__SCNetworkReachability_concurrent_queue(), ^{
handle_async_notification(target, xobj);
CFRelease(target);
xpc_release(xobj);
});
} else if (type == XPC_TYPE_ERROR) {
if (xobj == XPC_ERROR_CONNECTION_INVALID) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCNetworkReachability server not available"));
serverAvailable = FALSE;
} else if (xobj == XPC_ERROR_CONNECTION_INTERRUPTED) {
SCLog(TRUE, LOG_DEBUG,
CFSTR("SCNetworkReachability server failure, reconnecting"));
_reach_connection_reconnect(c);
} else {
const char *desc;
desc = xpc_dictionary_get_string(xobj, XPC_ERROR_KEY_DESCRIPTION);
SCLog(TRUE, LOG_ERR,
CFSTR("reach client %p: Connection error: %s"),
c,
desc);
}
} else {
SCLog(TRUE, LOG_ERR,
CFSTR("reach client %p: unknown event type : %p"),
c,
type);
}
});
xpc_connection_resume(c);
return c;
}
static xpc_connection_t
_reach_connection()
{
static xpc_connection_t c;
static dispatch_once_t once;
static dispatch_queue_t q;
if (!serverAvailable) {
return NULL;
}
dispatch_once(&once, ^{
q = dispatch_queue_create(REACH_SERVICE_NAME ".connection", NULL);
});
dispatch_sync(q, ^{
if (c == NULL) {
c = _reach_connection_create();
}
});
return c;
}
typedef void (^reach_server_reply_handler_t)(xpc_object_t reply);
static void
add_proc_name(xpc_object_t reqdict)
{
static const char *name = NULL;
static dispatch_once_t once;
dispatch_once(&once, ^{
name = getprogname();
});
xpc_dictionary_set_string(reqdict, REACH_CLIENT_PROC_NAME, name);
return;
}
static void
_reach_server_target_reconnect(xpc_connection_t connection, SCNetworkReachabilityRef target, Boolean disconnect);
static Boolean
_reach_server_target_add(xpc_connection_t connection, SCNetworkReachabilityRef target)
{
Boolean ok = FALSE;
xpc_object_t reply;
xpc_object_t reqdict;
Boolean retry = FALSE;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
reqdict = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_CREATE);
switch (targetPrivate->type) {
case reachabilityTypeName :
xpc_dictionary_set_string(reqdict,
REACH_TARGET_NAME,
targetPrivate->name);
break;
case reachabilityTypeAddress :
case reachabilityTypeAddressPair :
if (targetPrivate->localAddress != NULL) {
xpc_dictionary_set_data(reqdict,
REACH_TARGET_LOCAL_ADDR,
targetPrivate->localAddress,
targetPrivate->localAddress->sa_len);
}
if (targetPrivate->remoteAddress != NULL) {
xpc_dictionary_set_data(reqdict,
REACH_TARGET_REMOTE_ADDR,
targetPrivate->remoteAddress,
targetPrivate->remoteAddress->sa_len);
}
break;
case reachabilityTypePTR :
xpc_dictionary_set_data(reqdict,
REACH_TARGET_PTR_ADDR,
targetPrivate->remoteAddress,
targetPrivate->remoteAddress->sa_len);
break;
}
if (targetPrivate->if_index != 0) {
xpc_dictionary_set_int64(reqdict,
REACH_TARGET_IF_INDEX,
targetPrivate->if_index);
xpc_dictionary_set_string(reqdict,
REACH_TARGET_IF_NAME,
targetPrivate->if_name);
}
if (targetPrivate->onDemandBypass) {
xpc_dictionary_set_bool(reqdict,
REACH_TARGET_ONDEMAND_BYPASS,
TRUE);
}
if (targetPrivate->resolverBypass) {
xpc_dictionary_set_bool(reqdict,
REACH_TARGET_RESOLVER_BYPASS,
TRUE);
}
xpc_dictionary_set_uint64(reqdict, REACH_CLIENT_TARGET_ID, (uintptr_t)target);
add_proc_name(reqdict);
retry :
reply = xpc_connection_send_message_with_reply_sync(connection, reqdict);
if (reply != NULL) {
xpc_type_t type;
type = xpc_get_type(reply);
if (type == XPC_TYPE_DICTIONARY) {
int64_t status;
status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
ok = (status == REACH_REQUEST_REPLY_OK);
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCNetworkReachability server not available"));
serverAvailable = FALSE;
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
SCLog(TRUE, LOG_DEBUG,
CFSTR("reach target %p: SCNetworkReachability server failure, retrying"),
target);
retry = TRUE;
} else {
SCLog(TRUE, LOG_ERR,
CFSTR("reach target %p: _targetAdd with unexpected reply"),
target);
log_xpc_object(" reply", reply);
}
xpc_release(reply);
}
if (retry) {
retry = FALSE;
goto retry;
}
xpc_release(reqdict);
return ok;
}
static Boolean
_reach_server_target_remove(xpc_connection_t connection, SCNetworkReachabilityRef target)
{
Boolean ok = FALSE;
xpc_object_t reply;
xpc_object_t reqdict;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
reqdict = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_REMOVE);
xpc_dictionary_set_uint64(reqdict, REACH_CLIENT_TARGET_ID, (uintptr_t)target);
reply = xpc_connection_send_message_with_reply_sync(connection, reqdict);
if (reply != NULL) {
xpc_type_t type;
type = xpc_get_type(reply);
if (type == XPC_TYPE_DICTIONARY) {
int64_t status;
status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
switch (status) {
case REACH_REQUEST_REPLY_OK :
ok = TRUE;
break;
case REACH_REQUEST_REPLY_UNKNOWN :
ok = TRUE;
break;
default : {
SCLog(TRUE, LOG_ERR, CFSTR("%s target remove failed"),
targetPrivate->log_prefix);
log_xpc_object(" reply", reply);
}
}
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCNetworkReachability server not available"));
serverAvailable = FALSE;
ok = TRUE;
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
SCLog(TRUE, LOG_DEBUG,
CFSTR("reach target %p: SCNetworkReachability server failure, no need to remove"),
target);
ok = TRUE;
} else {
SCLog(TRUE, LOG_ERR,
CFSTR("reach target %p: _targetRemove with unexpected reply"),
target);
log_xpc_object(" reply", reply);
}
xpc_release(reply);
}
xpc_release(reqdict);
return ok;
}
static Boolean
_reach_server_target_schedule(xpc_connection_t connection, SCNetworkReachabilityRef target)
{
Boolean ok = FALSE;
xpc_object_t reply;
xpc_object_t reqdict;
Boolean retry = FALSE;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
reqdict = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_SCHEDULE);
xpc_dictionary_set_uint64(reqdict, REACH_CLIENT_TARGET_ID, (uintptr_t)target);
retry :
reply = xpc_connection_send_message_with_reply_sync(connection, reqdict);
if (reply != NULL) {
xpc_type_t type;
type = xpc_get_type(reply);
if (type == XPC_TYPE_DICTIONARY) {
int64_t status;
status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
switch (status) {
case REACH_REQUEST_REPLY_OK :
ok = TRUE;
break;
case REACH_REQUEST_REPLY_UNKNOWN :
retry = TRUE;
break;
default : {
SCLog(TRUE, LOG_ERR, CFSTR("%s target schedule failed"),
targetPrivate->log_prefix);
log_xpc_object(" reply", reply);
}
}
if (ok) {
CFRetain(target);
}
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCNetworkReachability server not available"));
serverAvailable = FALSE;
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
SCLog(TRUE, LOG_DEBUG,
CFSTR("reach target %p: SCNetworkReachability server failure, retry schedule"),
target);
retry = TRUE;
} else {
SCLog(TRUE, LOG_ERR,
CFSTR("reach target %p: _targetSchedule with unexpected reply"),
target);
log_xpc_object(" reply", reply);
}
xpc_release(reply);
}
if (retry) {
_reach_server_target_reconnect(connection, target, FALSE);
retry = FALSE;
goto retry;
}
xpc_release(reqdict);
return ok;
}
static void
_reach_reply_set_reachability(SCNetworkReachabilityRef target,
xpc_object_t reply)
{
char *if_name;
size_t len = 0;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
targetPrivate->serverInfo.cycle = xpc_dictionary_get_uint64(reply,
REACH_STATUS_CYCLE);
targetPrivate->serverInfo.flags = (SCNetworkReachabilityFlags)xpc_dictionary_get_uint64(reply,
REACH_STATUS_FLAGS);
targetPrivate->serverInfo.if_index = (unsigned int)xpc_dictionary_get_uint64(reply,
REACH_STATUS_IF_INDEX);
bzero(&targetPrivate->serverInfo.if_name, sizeof(targetPrivate->serverInfo.if_name));
if_name = (void *)xpc_dictionary_get_data(reply,
REACH_STATUS_IF_NAME,
&len);
if ((if_name != NULL) && (len > 0)) {
if (len > sizeof(targetPrivate->serverInfo.if_name)) {
len = sizeof(targetPrivate->serverInfo.if_name);
}
bcopy(if_name, targetPrivate->serverInfo.if_name, len);
}
targetPrivate->serverInfo.sleeping = xpc_dictionary_get_bool(reply,
REACH_STATUS_SLEEPING);
if (isReachabilityTypeName(targetPrivate->type)) {
xpc_object_t addresses;
if (targetPrivate->resolvedAddresses != NULL) {
CFRelease(targetPrivate->resolvedAddresses);
targetPrivate->resolvedAddresses = NULL;
}
targetPrivate->resolvedError = (int)xpc_dictionary_get_int64(reply,
REACH_STATUS_RESOLVED_ERROR);
addresses = xpc_dictionary_get_value(reply, REACH_STATUS_RESOLVED_ADDRESSES);
if ((addresses != NULL) && (xpc_get_type(addresses) != XPC_TYPE_ARRAY)) {
addresses = NULL;
}
if ((targetPrivate->resolvedError == NETDB_SUCCESS) && (addresses != NULL)) {
int i;
size_t n;
CFMutableArrayRef newAddresses;
newAddresses = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
n = xpc_array_get_count(addresses);
for (i = 0; i < n; i++) {
if (targetPrivate->type == reachabilityTypeName) {
struct sockaddr *sa;
size_t len;
CFDataRef newAddress;
sa = (struct sockaddr *)xpc_array_get_data(addresses, i, &len);
newAddress = CFDataCreate(NULL, (const UInt8 *)sa, len);
CFArrayAppendValue(newAddresses, newAddress);
CFRelease(newAddress);
} else if (targetPrivate->type == reachabilityTypePTR) {
const char *str;
CFStringRef newName;
str = xpc_array_get_string(addresses, i);
newName = CFStringCreateWithCString(NULL, str, kCFStringEncodingUTF8);
CFArrayAppendValue(newAddresses, newName);
CFRelease(newName);
}
}
targetPrivate->resolvedAddresses = newAddresses;
} else {
targetPrivate->resolvedAddresses = CFRetain(kCFNull);
}
targetPrivate->dnsFlags = (uint32_t)xpc_dictionary_get_uint64(reply,
REACH_STATUS_DNS_FLAGS);
targetPrivate->needResolve = FALSE;
}
return;
}
static Boolean
_reach_server_target_status(xpc_connection_t connection, SCNetworkReachabilityRef target)
{
Boolean ok = FALSE;
xpc_object_t reply;
xpc_object_t reqdict;
Boolean retry = FALSE;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
if (_sc_debug) {
CFStringRef str;
str = _SCNetworkReachabilityCopyTargetDescription(target);
SCLog(TRUE, LOG_INFO, CFSTR("%scheckReachability(%@)"),
targetPrivate->log_prefix,
str);
CFRelease(str);
}
reqdict = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_STATUS);
xpc_dictionary_set_uint64(reqdict, REACH_CLIENT_TARGET_ID, (uintptr_t)target);
retry :
reply = xpc_connection_send_message_with_reply_sync(connection, reqdict);
if (reply != NULL) {
xpc_type_t type;
type = xpc_get_type(reply);
if (type == XPC_TYPE_DICTIONARY) {
int64_t status;
status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
switch (status) {
case REACH_REQUEST_REPLY_OK :
ok = TRUE;
break;
case REACH_REQUEST_REPLY_UNKNOWN :
retry = TRUE;
break;
default :
SCLog(TRUE, LOG_INFO, CFSTR("%s target status failed"),
targetPrivate->log_prefix);
log_xpc_object(" reply", reply);
}
if (ok) {
_reach_reply_set_reachability(target, reply);
if (_sc_debug) {
SCLog(TRUE, LOG_INFO, CFSTR("%s flags = 0x%08x"),
targetPrivate->log_prefix,
targetPrivate->serverInfo.flags);
if (targetPrivate->serverInfo.if_index != 0) {
SCLog(TRUE, LOG_INFO, CFSTR("%s device = %s (%u%s)"),
targetPrivate->log_prefix,
targetPrivate->serverInfo.if_name,
targetPrivate->serverInfo.if_index,
targetPrivate->serverInfo.sleeping ? ", z" : "");
}
if (targetPrivate->serverInfo.cycle != targetPrivate->cycle) {
SCLog(TRUE, LOG_INFO, CFSTR("%s forced"),
targetPrivate->log_prefix);
}
}
}
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCNetworkReachability server not available"));
serverAvailable = FALSE;
ok = TRUE;
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
SCLog(TRUE, LOG_DEBUG,
CFSTR("reach target %p: SCNetworkReachability server failure, retry status"),
target);
retry = TRUE;
} else {
SCLog(TRUE, LOG_ERR,
CFSTR("reach target %p: _targetStatus with unexpected reply"),
target);
log_xpc_object(" reply", reply);
}
xpc_release(reply);
}
if (retry) {
_reach_server_target_reconnect(connection, target, FALSE);
retry = FALSE;
goto retry;
}
xpc_release(reqdict);
return ok;
}
static Boolean
_reach_server_target_unschedule(xpc_connection_t connection, SCNetworkReachabilityRef target)
{
Boolean ok = FALSE;
xpc_object_t reply;
xpc_object_t reqdict;
Boolean retry = FALSE;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
reqdict = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_UNSCHEDULE);
xpc_dictionary_set_uint64(reqdict, REACH_CLIENT_TARGET_ID, (uintptr_t)target);
reply = xpc_connection_send_message_with_reply_sync(connection, reqdict);
if (reply != NULL) {
xpc_type_t type;
type = xpc_get_type(reply);
if (type == XPC_TYPE_DICTIONARY) {
int64_t status;
status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
switch (status) {
case REACH_REQUEST_REPLY_OK :
ok = TRUE;
break;
case REACH_REQUEST_REPLY_UNKNOWN :
retry = TRUE;
break;
default :
SCLog(TRUE, LOG_INFO, CFSTR("%s target unschedule failed"),
targetPrivate->log_prefix);
log_xpc_object(" reply", reply);
}
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCNetworkReachability server not available"));
serverAvailable = FALSE;
ok = TRUE;
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
SCLog(TRUE, LOG_DEBUG,
CFSTR("reach target %p: SCNetworkReachability server failure, re-establish (but do not re-schedule)"),
target);
retry = TRUE;
} else {
SCLog(TRUE, LOG_ERR,
CFSTR("reach target %p: _targetUnschedule with unexpected reply"),
target);
log_xpc_object(" reply", reply);
}
xpc_release(reply);
}
if (retry) {
targetPrivate->serverScheduled = FALSE;
_reach_server_target_reconnect(connection, target, FALSE);
ok = TRUE;
}
if (ok) {
CFRelease(target);
}
xpc_release(reqdict);
return ok;
}
#pragma mark -
#pragma mark Reconnect
static void
_reach_server_target_reconnect(xpc_connection_t connection, SCNetworkReachabilityRef target, Boolean disconnect)
{
Boolean ok;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
if (!targetPrivate->serverActive) {
return;
}
if (disconnect) {
if (targetPrivate->serverScheduled) {
(void) _reach_server_target_unschedule(connection, target);
}
(void) _reach_server_target_remove(connection, target);
} else {
targetPrivate->cycle = 0;
}
ok = _reach_server_target_add(connection, target);
if (!ok) {
return;
}
if (!targetPrivate->serverScheduled) {
return;
}
ok = _reach_server_target_schedule(connection, target);
if (!ok) {
return;
}
if (isReachabilityTypeAddress(targetPrivate->type)) {
__SCNetworkReachabilityUpdate(target);
}
return;
}
static void
_reach_connection_reconnect(xpc_connection_t connection)
{
dispatch_sync(_reach_requests_rbt_queue(), ^{
rb_tree_t *rbt = _reach_requests_rbt();
reach_request_t *request;
RB_TREE_FOREACH(request, rbt) {
SCNetworkReachabilityRef target;
xpc_retain(connection);
target = request->target;
CFRetain(target);
dispatch_async(__SCNetworkReachability_concurrent_queue(), ^{
_reach_server_target_reconnect(connection, target, FALSE);
CFRelease(target);
xpc_release(connection);
});
}
});
return;
}
#pragma mark -
#pragma mark SPI (exposed)
Boolean
_SCNetworkReachabilityServer_snapshot(void)
{
xpc_connection_t c;
Boolean ok = FALSE;
xpc_object_t reply;
xpc_object_t reqdict;
c = _reach_connection();
if (c == NULL) {
return FALSE;
}
reqdict = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_SNAPSHOT);
add_proc_name(reqdict);
retry :
reply = xpc_connection_send_message_with_reply_sync(c, reqdict);
if (reply != NULL) {
xpc_type_t type;
type = xpc_get_type(reply);
if (type == XPC_TYPE_DICTIONARY) {
int64_t status;
status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
ok = (status == REACH_REQUEST_REPLY_OK);
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCNetworkReachability server not available"));
serverAvailable = FALSE;
} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
SCLog(TRUE, LOG_DEBUG,
CFSTR("SCNetworkReachability server failure, retrying"));
xpc_release(reply);
goto retry;
} else {
SCLog(TRUE, LOG_ERR,
CFSTR("_snapshot with unexpected reply"));
log_xpc_object(" reply", reply);
}
xpc_release(reply);
}
xpc_release(reqdict);
return ok;
}
__private_extern__
Boolean
__SCNetworkReachabilityServer_targetAdd(SCNetworkReachabilityRef target)
{
xpc_connection_t c;
Boolean ok;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
c = _reach_connection();
if (c == NULL) {
return FALSE;
}
ok = _reach_server_target_add(c, target);
if (ok) {
_SC_ATOMIC_CMPXCHG(&targetPrivate->serverActive, FALSE, TRUE);
}
return ok;
}
__private_extern__
void
__SCNetworkReachabilityServer_targetRemove(SCNetworkReachabilityRef target)
{
xpc_connection_t c;
Boolean ok;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
if (!targetPrivate->serverActive) {
return;
}
c = _reach_connection();
if (c == NULL) {
return;
}
ok = _reach_server_target_remove(c, target);
if (ok) {
_SC_ATOMIC_CMPXCHG(&targetPrivate->serverActive, TRUE, FALSE);
}
return;
}
__private_extern__
Boolean
__SCNetworkReachabilityServer_targetSchedule(SCNetworkReachabilityRef target)
{
xpc_connection_t c;
Boolean ok;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
c = _reach_connection();
if (c == NULL) {
return FALSE;
}
_reach_request_add(target);
ok = _reach_server_target_schedule(c, target);
if (ok) {
_SC_ATOMIC_CMPXCHG(&targetPrivate->serverScheduled, FALSE, TRUE);
} else {
_reach_request_remove(target);
}
return ok;
}
__private_extern__
Boolean
__SCNetworkReachabilityServer_targetStatus(SCNetworkReachabilityRef target)
{
xpc_connection_t c;
Boolean ok;
c = _reach_connection();
if (c == NULL) {
return FALSE;
}
ok = _reach_server_target_status(c, target);
return ok;
}
__private_extern__
Boolean
__SCNetworkReachabilityServer_targetUnschedule(SCNetworkReachabilityRef target)
{
xpc_connection_t c;
Boolean ok;
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
if (!targetPrivate->serverScheduled) {
return TRUE;
}
c = _reach_connection();
if (c == NULL) {
return FALSE;
}
ok = _reach_server_target_unschedule(c, target);
if (ok) {
_SC_ATOMIC_CMPXCHG(&targetPrivate->serverScheduled, TRUE, FALSE);
_reach_request_remove(target);
} else {
}
return ok;
}