/* * Copyright (c) 2011-2013 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@ */ #include #include #include #include "SCNetworkReachabilityInternal.h" #ifdef HAVE_REACHABILITY_SERVER #include #include #include #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); return (a - b); } 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; return (a - b); } 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); if (request == NULL || !rb_tree_insert_node(rbt, request)) { __builtin_trap(); } } }); return; } static void _reach_request_remove(SCNetworkReachabilityRef target) { uint64_t target_id = (uintptr_t)target; dispatch_sync(_reach_requests_rbt_queue(), ^{ // FIXME ?? use dispatch_async? 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); // log_xpc_object(" status", dict); } __SCNetworkReachabilityPerformConcurrent(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 : %d"), 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(); // create XPC connection 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) { // SCLog(TRUE, LOG_ERR, // CFSTR("received unexpected target [ID] from SCNetworkReachability server")); // log_xpc_object(" reply", xobj); 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 : %x"), 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) { // if SCNetworkReachabilty [XPC] server not available 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; // add the process name 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; // create message reqdict = xpc_dictionary_create(NULL, NULL, 0); // set request xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_CREATE); // add reachability target info if (targetPrivate->name != NULL) { xpc_dictionary_set_string(reqdict, REACH_TARGET_NAME, targetPrivate->name); } 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); } 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); } // add the target [ID] xpc_dictionary_set_uint64(reqdict, REACH_CLIENT_TARGET_ID, (uintptr_t)target); // add the process name (for debugging) add_proc_name(reqdict); retry : // send request to the SCNetworkReachability server 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; // create message reqdict = xpc_dictionary_create(NULL, NULL, 0); // set request xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_REMOVE); // add the target [ID] 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 : // target not known by the server (most likely due to a // SCNetworkReachability server failure), no need to // remove. 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; // create message reqdict = xpc_dictionary_create(NULL, NULL, 0); // set request xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_SCHEDULE); // add the target [ID] 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 : // target not known by the server (most likely due to a // SCNetworkReachability server failure), re-establish // and retry scheduling. 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) { // reconnect _reach_server_target_reconnect(connection, target, FALSE); // and retry 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 = xpc_dictionary_get_uint64(reply, REACH_STATUS_FLAGS); targetPrivate->serverInfo.if_index = 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 (targetPrivate->type == reachabilityTypeName) { xpc_object_t addresses; if (targetPrivate->resolvedAddresses != NULL) { CFRelease(targetPrivate->resolvedAddresses); targetPrivate->resolvedAddresses = NULL; } targetPrivate->resolvedError = 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; int n; CFMutableArrayRef newAddresses; newAddresses = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); n = xpc_array_get_count(addresses); for (i = 0; i < n; i++) { struct addrinfo *sa; size_t len; CFDataRef newAddress; sa = (struct addrinfo *)xpc_array_get_data(addresses, i, &len); newAddress = CFDataCreate(NULL, (const UInt8 *)sa, len); CFArrayAppendValue(newAddresses, newAddress); CFRelease(newAddress); } targetPrivate->resolvedAddresses = newAddresses; } else { /* save the error associated with the attempt to resolve the name */ targetPrivate->resolvedAddresses = CFRetain(kCFNull); } 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); } // create message reqdict = xpc_dictionary_create(NULL, NULL, 0); // set request xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_STATUS); // add the target [ID] 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 : // target not known by the server (most likely due to a // SCNetworkReachability server failure), re-establish // and retry status. 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 (%hu%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) { // reconnect _reach_server_target_reconnect(connection, target, FALSE); // and retry 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; // create message reqdict = xpc_dictionary_create(NULL, NULL, 0); // set request xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_UNSCHEDULE); // add the target [ID] 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 : // target not known by the server (most likely due to a // SCNetworkReachability server failure), re-establish // but no need to unschedule. 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) { // reconnect 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) { // if target already removed return; } if (disconnect) { // if we should first disconnect (unschedule, remove) if (targetPrivate->serverScheduled) { (void) _reach_server_target_unschedule(connection, target); } (void) _reach_server_target_remove(connection, target); } else { // server has been restarted targetPrivate->cycle = 0; } // re-associate with server ok = _reach_server_target_add(connection, target); if (!ok) { // if we could not add the target return; } if (!targetPrivate->serverScheduled) { // if not scheduled return; } // ... and re-schedule with server ok = _reach_server_target_schedule(connection, target); if (!ok) { // if we could not reschedule the target return; } // For addresses, update our status now. For names, queries will // be updated with a callback if (targetPrivate->type != reachabilityTypeName) { __SCNetworkReachabilityPerform(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; // initialize connection with SCNetworkReachability server c = _reach_connection(); if (c == NULL) { return FALSE; } // create message reqdict = xpc_dictionary_create(NULL, NULL, 0); // set request xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_SNAPSHOT); // add the process name (for debugging) add_proc_name(reqdict); retry : // send request 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) { // if not active 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) { // if not scheduled 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 { // if unschedule failed } return ok; } #endif // HAVE_REACHABILITY_SERVER