SCNetworkReachabilityServer_client.c   [plain text]


/*
 * Copyright (c) 2011-2014 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 <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(), ^{		// 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);
	}

	__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();

	// 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 : %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) {
		// if SCNetworkReachability [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
	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);
	}



	// 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 = (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 {
			/* save the error associated with the attempt to resolve the name */
			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);
	}

	// 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 (%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) {
		// 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 (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;

	// 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;
}