+/*
+ * Copyright (c) 2011, 2012 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"
+
+#ifdef HAVE_REACHABILITY_SERVER
+
+#include <fcntl.h>
+#include <paths.h>
+#include <CommonCrypto/CommonDigest.h>
+#include <dispatch/dispatch.h>
+#include <dispatch/private.h>
+#include <xpc/xpc.h>
+#include <xpc/private.h>
+
+#include "rb.h"
+
+
+#pragma mark -
+#pragma mark Globals
+
+
+/*
+ * S_debug
+ * A boolean that enables additional logging.
+ */
+static boolean_t S_debug = FALSE;
+
+
+#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(S_debug, LOG_INFO, CFSTR("%s = %s"), msg, desc);
+ free(desc);
+}
+
+
+static __inline__ void
+my_CFDictionaryApplyFunction(CFDictionaryRef theDict,
+ CFDictionaryApplierFunction applier,
+ void *context)
+{
+ CFAllocatorRef myAllocator;
+ CFDictionaryRef myDict;
+
+ myAllocator = CFGetAllocator(theDict);
+ myDict = CFDictionaryCreateCopy(myAllocator, theDict);
+ CFDictionaryApplyFunction(myDict, applier, context);
+ CFRelease(myDict);
+ return;
+}
+
+
+#pragma mark -
+#pragma mark SCNetworkReachability target support
+
+
+static CFMutableDictionaryRef reach_digest_map;
+
+
+static dispatch_queue_t
+_server_concurrent_queue()
+{
+ static dispatch_once_t once;
+ static dispatch_queue_t q;
+
+ dispatch_once(&once, ^{
+ q = dispatch_queue_create(REACH_SERVICE_NAME ".concurrent",
+ DISPATCH_QUEUE_CONCURRENT);
+ dispatch_queue_set_width(q, 32);
+ });
+
+ return q;
+}
+
+
+static dispatch_queue_t
+_server_digest_queue()
+{
+ static dispatch_once_t once;
+ static dispatch_queue_t q;
+
+ dispatch_once(&once, ^{
+ q = dispatch_queue_create(REACH_SERVICE_NAME ".digest", NULL);
+ });
+
+ return q;
+}
+
+
+static dispatch_group_t
+_target_group(SCNetworkReachabilityRef target)
+{
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ return targetPrivate->serverGroup;
+}
+
+
+static dispatch_queue_t
+_target_queue(SCNetworkReachabilityRef target)
+{
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ return targetPrivate->serverQueue;
+}
+
+
+#pragma mark -
+
+
+/*
+ * _target_reference_add
+ *
+ * Note: use dispatch_sync(_server_digest_queue(), ^{ ... });
+ */
+static void
+_target_reference_add(SCNetworkReachabilityRef target, CFDataRef digest, xpc_connection_t connection)
+{
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ // take a reference to the target
+ CFRetain(target);
+
+ // ensure that we have a dispatch group
+ if (targetPrivate->serverGroup == NULL) {
+ targetPrivate->serverGroup = dispatch_group_create();
+ }
+
+ // ensure that we have a dispatch queue
+ if (targetPrivate->serverQueue == NULL) {
+ char qname[256];
+
+ snprintf(qname, sizeof(qname), "com.apple.SCNetworkReachability.%p.server", target);
+ targetPrivate->serverQueue = dispatch_queue_create(qname, NULL);
+ }
+
+ // bump the reference count
+ if (_SC_ATOMIC_INC(&targetPrivate->serverReferences) == 0) {
+ // and maintain a digest-->target mapping
+ targetPrivate->serverDigest = CFRetain(digest);
+ CFDictionarySetValue(reach_digest_map, digest, target);
+ }
+
+ if (S_debug) {
+ CFStringRef str;
+
+ str = _SCNetworkReachabilityCopyTargetDescription(target);
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> target %p: reference added (%@, %d)"),
+ connection,
+ target,
+ str,
+ targetPrivate->serverReferences);
+ CFRelease(str);
+ }
+
+ return;
+}
+
+
+/*
+ * _target_reference_remove
+ *
+ * Note: use dispatch_sync(_server_digest_queue(), ^{ ... });
+ */
+static void
+_target_reference_remove(SCNetworkReachabilityRef target, xpc_connection_t connection)
+{
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ // drop the reference count
+ if (_SC_ATOMIC_DEC(&targetPrivate->serverReferences) == 0) {
+ /*
+ * if that was the last reference, we no longer need to
+ * keep the digest-->target mapping
+ */
+ CFDictionaryRemoveValue(reach_digest_map, targetPrivate->serverDigest);
+ CFRelease(targetPrivate->serverDigest);
+ targetPrivate->serverDigest = NULL;
+ }
+
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> target %p: reference removed (%d)"),
+ connection,
+ target,
+ targetPrivate->serverReferences);
+ }
+
+ // release a reference to the target
+ CFRelease(target);
+
+ return;
+}
+
+
+#pragma mark -
+
+
+#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_); \
+}
+
+
+static void
+_target_reply_add_reachability(SCNetworkReachabilityRef target,
+ xpc_object_t reply)
+{
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ MUTEX_LOCK(&targetPrivate->lock);
+
+ xpc_dictionary_set_uint64(reply,
+ REACH_STATUS_CYCLE,
+ targetPrivate->info.cycle);
+ xpc_dictionary_set_uint64(reply,
+ REACH_STATUS_FLAGS,
+ targetPrivate->info.flags);
+ xpc_dictionary_set_uint64(reply,
+ REACH_STATUS_IF_INDEX,
+ targetPrivate->info.if_index);
+ xpc_dictionary_set_data (reply,
+ REACH_STATUS_IF_NAME,
+ targetPrivate->info.if_name,
+ sizeof(targetPrivate->info.if_name));
+ xpc_dictionary_set_bool (reply,
+ REACH_STATUS_SLEEPING,
+ targetPrivate->info.sleeping);
+ if (targetPrivate->type == reachabilityTypeName) {
+ if (isA_CFArray(targetPrivate->resolvedAddress)) {
+ xpc_object_t addresses;
+ CFIndex i;
+ CFIndex n;
+
+ addresses = xpc_array_create(NULL, 0);
+
+ n = CFArrayGetCount(targetPrivate->resolvedAddress);
+ for (i = 0; i < n; i++) {
+ CFDataRef address;
+
+ address = CFArrayGetValueAtIndex(targetPrivate->resolvedAddress, i);
+ xpc_array_set_data(addresses,
+ XPC_ARRAY_APPEND,
+ CFDataGetBytePtr(address),
+ CFDataGetLength(address));
+ }
+
+ xpc_dictionary_set_value(reply,
+ REACH_STATUS_RESOLVED_ADDRESS,
+ addresses);
+ xpc_release(addresses);
+ }
+ xpc_dictionary_set_int64(reply,
+ REACH_STATUS_RESOLVED_ADDRESS_ERROR,
+ targetPrivate->resolvedAddressError);
+ }
+
+ MUTEX_UNLOCK(&targetPrivate->lock);
+
+ return;
+}
+
+
+#pragma mark -
+
+
+typedef struct {
+ xpc_connection_t connection;
+ uint64_t target_id;
+} reach_watcher_key_t;
+
+typedef struct {
+ unsigned int n_changes;
+} reach_watcher_val_t;
+
+
+static CFDataRef
+_target_watcher_key_create(xpc_connection_t connection,
+ uint64_t target_id)
+{
+ CFDataRef key;
+ reach_watcher_key_t watcher_key;
+
+ watcher_key.connection = connection;
+ watcher_key.target_id = target_id;
+
+ key = CFDataCreate(NULL, (UInt8 *)&watcher_key, sizeof(watcher_key));
+ return key;
+}
+
+
+static Boolean
+_target_watcher_add(SCNetworkReachabilityRef target,
+ xpc_connection_t connection,
+ uint64_t target_id)
+{
+ __block Boolean ok = TRUE;
+ dispatch_queue_t q;
+
+ q = _target_queue(target);
+ dispatch_sync(q, ^{
+ CFDataRef key;
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ if (targetPrivate->serverWatchers == NULL) {
+ ok = SCNetworkReachabilitySetDispatchQueue(target, q);
+ if (!ok) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target %p: _watcher_add SCNetworkReachabilitySetDispatchQueue() failed: %s"),
+ connection,
+ target,
+ SCErrorString(SCError()));
+ return;
+ }
+
+ targetPrivate->serverWatchers = CFDictionaryCreateMutable(NULL,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ }
+
+ xpc_retain(connection);
+
+ key = _target_watcher_key_create(connection, target_id);
+ if (CFDictionaryContainsKey(targetPrivate->serverWatchers, key)) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target %p: watcher not added, c=0x%0llx, \"serverWatchers\" key exists"),
+ connection,
+ target,
+ target_id);
+ } else {
+ CFDataRef val;
+ static const reach_watcher_val_t watcher_val0 = { 0 };
+
+ val = CFDataCreate(NULL, (UInt8 *)&watcher_val0, sizeof(watcher_val0));
+ CFDictionaryAddValue(targetPrivate->serverWatchers, key, val);
+ CFRelease(val);
+
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> target %p: watcher added, c=0x%0llx, n=%d"),
+ connection,
+ target,
+ target_id,
+ CFDictionaryGetCount(targetPrivate->serverWatchers));
+ }
+ }
+ CFRelease(key);
+ });
+
+ return ok;
+}
+
+
+static Boolean
+_target_watcher_checkin(SCNetworkReachabilityRef target,
+ xpc_connection_t connection,
+ uint64_t target_id)
+{
+ __block Boolean scheduled = FALSE;
+
+ dispatch_sync(_target_queue(target), ^{
+ CFDataRef key;
+ unsigned int n;
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+ CFDataRef val;
+ reach_watcher_val_t *watcher_val;
+
+ if (targetPrivate->serverWatchers == NULL) {
+ // if no watchers
+ return;
+ }
+
+ key = _target_watcher_key_create(connection, target_id);
+ val = CFDictionaryGetValue(targetPrivate->serverWatchers, key);
+ CFRelease(key);
+ if (val == NULL) {
+ // if the target [for this client] was not scheduled
+ return;
+ }
+
+ // indicate that the target was scheduled
+ scheduled = TRUE;
+
+ /*
+ * and note that the reachability flags for this target have
+ * been picked up by the client
+ */
+ /* ALIGN: CF aligns to at least >8 byte boundries */
+ watcher_val = (reach_watcher_val_t *)(void *)CFDataGetBytePtr(val);
+ n = _SC_ATOMIC_ZERO(&watcher_val->n_changes);
+ if (S_debug && (n > 0)) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> target %p: SCNetworkReachabilityGetFlags() after %d notification%s"),
+ connection,
+ target,
+ n,
+ (n == 1) ? "" : "s");
+ }
+ });
+
+ return scheduled;
+}
+
+
+static Boolean
+_target_watcher_remove(SCNetworkReachabilityRef target,
+ xpc_connection_t connection,
+ uint64_t target_id)
+{
+ __block Boolean ok = TRUE;
+
+ dispatch_sync(_target_queue(target), ^{
+ CFDataRef key;
+ CFIndex n;
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ if (targetPrivate->serverWatchers == NULL) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target %p: watcher not removed, c=0x%0llx, no \"serverWatchers\""),
+ connection,
+ target,
+ target_id);
+ return;
+ }
+
+ key = _target_watcher_key_create(connection, target_id);
+ if (!CFDictionaryContainsKey(targetPrivate->serverWatchers, key)) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target %p: watcher not removed, c=0x%0llx, no \"serverWatchers\" key"),
+ connection,
+ target,
+ target_id);
+ CFRelease(key);
+ return;
+ }
+
+ CFDictionaryRemoveValue(targetPrivate->serverWatchers, key);
+ xpc_release(connection);
+ CFRelease(key);
+
+ n = CFDictionaryGetCount(targetPrivate->serverWatchers);
+
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> target %p: watcher removed, c=0x%0llx, n=%d"),
+ connection,
+ target, // server
+ target_id, // client
+ n);
+ }
+
+ if (n == 0) {
+ CFRelease(targetPrivate->serverWatchers);
+ targetPrivate->serverWatchers = NULL;
+
+ ok = SCNetworkReachabilitySetDispatchQueue(target, NULL);
+ if (!ok) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target %p: _watcher_remove SCNetworkReachabilitySetDispatchQueue() failed: %s"),
+ connection,
+ target,
+ SCErrorString(SCError()));
+ return;
+ }
+
+ // no more watchers, flags are no longer valid
+ (void) _SC_ATOMIC_CMPXCHG(&targetPrivate->serverInfoValid, TRUE, FALSE);
+ }
+ });
+
+ return ok;
+}
+
+
+#pragma mark -
+#pragma mark Reachability [RBT] client support
+
+
+typedef struct {
+ struct rb_node rbn;
+ xpc_connection_t connection;
+ pid_t pid;
+ const char *proc_name;
+ CFMutableDictionaryRef targets; // target_id --> SCNetworkReachabilityRef
+} reach_client_t;
+
+
+#define RBNODE_TO_REACH_CLIENT(node) \
+ ((reach_client_t *)((uintptr_t)node - offsetof(reach_client_t, rbn)))
+
+
+static int
+_rbt_compare_transaction_nodes(const struct rb_node *n1, const struct rb_node *n2)
+{
+ uint64_t a = (uintptr_t)RBNODE_TO_REACH_CLIENT(n1)->connection;
+ uint64_t b = (uintptr_t)RBNODE_TO_REACH_CLIENT(n2)->connection;
+
+ return (a - b);
+}
+
+
+static int
+_rbt_compare_transaction_key(const struct rb_node *n1, const void *key)
+{
+ uint64_t a = (uintptr_t)RBNODE_TO_REACH_CLIENT(n1)->connection;
+ uint64_t b = *(uintptr_t *)key;
+
+ return (a - b);
+}
+
+
+static struct rb_tree *
+_reach_clients_rbt()
+{
+ static dispatch_once_t once;
+ static const struct rb_tree_ops ops = {
+ .rbto_compare_nodes = _rbt_compare_transaction_nodes,
+ .rbto_compare_key = _rbt_compare_transaction_key,
+ };
+ static struct rb_tree rbtree;
+
+ dispatch_once(&once, ^{
+ rb_tree_init(&rbtree, &ops);
+ });
+
+ return &rbtree;
+}
+
+
+static dispatch_queue_t
+_reach_connection_queue()
+{
+ static dispatch_once_t once;
+ static dispatch_queue_t q;
+
+ dispatch_once(&once, ^{
+ q = dispatch_queue_create(REACH_SERVICE_NAME ".connection", NULL);
+ });
+
+ return q;
+}
+
+
+static reach_client_t *
+_reach_client_create(xpc_connection_t connection)
+{
+ reach_client_t *client;
+
+ client = calloc(1, sizeof(*client));
+ client->connection = connection;
+ client->pid = xpc_connection_get_pid(connection);
+ client->proc_name = NULL;
+ client->targets = CFDictionaryCreateMutable(NULL,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ return client;
+}
+
+
+static void
+_reach_client_release(reach_client_t *client)
+{
+ if (client->proc_name != NULL) {
+ free((void *)client->proc_name);
+ }
+ CFRelease(client->targets);
+ free(client);
+ return;
+}
+
+
+static void
+_reach_client_remove_target(const void *key, const void *value, void *context)
+{
+ xpc_connection_t connection = (xpc_connection_t)context;
+ SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)value;
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ // check if we have anyone watching this target
+ if (targetPrivate->serverWatchers != NULL) {
+ CFIndex n;
+
+ n = CFDictionaryGetCount(targetPrivate->serverWatchers);
+ if (n > 0) {
+ CFIndex i;
+ const void * watchers_q[32];
+ const void ** watchers = watchers_q;
+
+ if (n > sizeof(watchers_q)/sizeof(watchers[0])) {
+ watchers = CFAllocatorAllocate(NULL, n * sizeof(CFDataRef), 0);
+ }
+ CFDictionaryGetKeysAndValues(targetPrivate->serverWatchers, watchers, NULL);
+
+ for (i = 0; i < n; i++) {
+ CFDataRef key;
+ reach_watcher_key_t *watcher_key;
+
+ key = (CFDataRef)watchers[i];
+ /* ALIGN: CF aligns to >8 byte boundries */
+ watcher_key = (reach_watcher_key_t *)(void *)CFDataGetBytePtr(key);
+ if (watcher_key->connection == connection) {
+ // remove watcher references for THIS connection
+ _target_watcher_remove(target,
+ watcher_key->connection,
+ watcher_key->target_id);
+ }
+ }
+
+ if (watchers != watchers_q) {
+ CFAllocatorDeallocate(NULL, watchers);
+ }
+ }
+ }
+
+ // remove our reference to this target
+ dispatch_sync(_server_digest_queue(), ^{
+ _target_reference_remove(target, connection);
+ });
+
+ return;
+}
+
+
+static void
+_reach_client_remove(xpc_connection_t connection)
+{
+ struct rb_tree *rbtree = _reach_clients_rbt();
+ struct rb_node *rbn;
+
+ rbn = rb_tree_find_node(rbtree, &connection);
+ if (rbn != NULL) {
+ reach_client_t *client;
+
+ client = RBNODE_TO_REACH_CLIENT(rbn);
+
+ // remove any remaining target references (for this client)
+ my_CFDictionaryApplyFunction(client->targets,
+ _reach_client_remove_target,
+ (void *)connection);
+
+ rb_tree_remove_node(rbtree, rbn);
+ _reach_client_release(client);
+ } else {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> _reach_client_remove: unexpected client"),
+ connection);
+ }
+
+ return;
+}
+
+
+static __inline__ CFDataRef
+_client_target_key_create(uint64_t target_id)
+{
+ CFDataRef target_key;
+
+ target_key = CFDataCreate(NULL, (UInt8 *)&target_id, sizeof(target_id));
+ return target_key;
+}
+
+
+static SCNetworkReachabilityRef
+_client_target_copy(reach_client_t *client, uint64_t target_id)
+{
+ SCNetworkReachabilityRef target;
+ CFDataRef target_key;
+
+ target_key = _client_target_key_create(target_id);
+ target = CFDictionaryGetValue(client->targets, target_key);
+ CFRelease(target_key);
+
+ if (target != NULL) {
+ CFRetain(target);
+ }
+
+ return target;
+}
+
+
+static Boolean
+_client_target_set(reach_client_t *client, uint64_t target_id, SCNetworkReachabilityRef target)
+{
+ Boolean added;
+ CFDataRef target_key;
+
+ target_key = _client_target_key_create(target_id);
+ added = !CFDictionaryContainsKey(client->targets, target_key);
+ if (added) {
+ CFDictionarySetValue(client->targets, target_key, target);
+ }
+ CFRelease(target_key);
+
+ return added;
+}
+
+
+static void
+_client_target_remove(reach_client_t *client, uint64_t target_id)
+{
+ CFDataRef target_key;
+
+ target_key = _client_target_key_create(target_id);
+ CFDictionaryRemoveValue(client->targets, target_key);
+ CFRelease(target_key);
+
+ return;
+}
+
+
+#pragma mark -
+#pragma mark Reachability [XPC] server functions
+
+/*
+ * _reach_changed
+ *
+ * Note: should be exec'd on the target queue
+ */
+static void
+_reach_changed(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
+{
+ CFIndex i;
+ CFIndex n;
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+ const void * watcher_keys_q[32];
+ const void ** watcher_keys = watcher_keys_q;
+ const void * watcher_vals_q[32];
+ const void ** watcher_vals = watcher_vals_q;
+
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("%sprocess reachability changed, flags = 0x%08x"),
+ targetPrivate->log_prefix,
+ flags);
+ }
+
+ if (targetPrivate->serverWatchers == NULL) {
+ // if no watchers
+ return;
+ }
+
+ n = CFDictionaryGetCount(targetPrivate->serverWatchers);
+ if (n == 0) {
+ // if no watchers
+ return;
+ }
+
+ /*
+ * Because we are actively watching for additional changes
+ * we mark the flags as "valid"
+ */
+ if (_SC_ATOMIC_CMPXCHG(&targetPrivate->serverInfoValid, FALSE, TRUE)) {
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO, CFSTR(" flags are now \"valid\""));
+ }
+ }
+
+ // notify all of the watchers
+ if (n > sizeof(watcher_keys_q)/sizeof(watcher_keys[0])) {
+ watcher_keys = CFAllocatorAllocate(NULL, n * sizeof(CFDataRef), 0);
+ watcher_vals = CFAllocatorAllocate(NULL, n * sizeof(CFDataRef), 0);
+ }
+
+ CFDictionaryGetKeysAndValues(targetPrivate->serverWatchers,
+ watcher_keys,
+ watcher_vals);
+
+ for (i = 0; i < n; i++) {
+ xpc_connection_t connection;
+ CFDataRef key;
+ uint64_t target_id;
+ CFDataRef val;
+ reach_watcher_key_t *watcher_key;
+ reach_watcher_val_t *watcher_val;
+
+ val = (CFDataRef)watcher_vals[i];
+ /* ALIGN: CF aligns to >8 byte boundries */
+ watcher_val = (reach_watcher_val_t *)(void *)CFDataGetBytePtr(val);
+
+ if (_SC_ATOMIC_INC(&watcher_val->n_changes) > 0) {
+ // if we've already sent a notification
+ continue;
+ }
+
+ key = (CFDataRef)watcher_keys[i];
+ /* ALIGN: CF aligns to >8 byte boundries */
+ watcher_key = (reach_watcher_key_t *)(void *)CFDataGetBytePtr(key);
+
+ connection = xpc_retain(watcher_key->connection);
+ target_id = watcher_key->target_id;
+ dispatch_async(_reach_connection_queue(), ^{
+ xpc_object_t reply;
+
+ // create our [async] notification
+ reply = xpc_dictionary_create(NULL, NULL, 0);
+
+ // set notification
+ xpc_dictionary_set_int64(reply,
+ MESSAGE_NOTIFY,
+ MESSAGE_REACHABILITY_STATUS);
+
+ // set target ID
+ xpc_dictionary_set_uint64(reply,
+ REACH_CLIENT_TARGET_ID,
+ target_id);
+
+ log_xpc_object(" reply [async]", reply);
+ xpc_connection_send_message(connection, reply);
+
+ xpc_release(reply);
+ xpc_release(connection);
+ });
+ }
+
+ if (n > sizeof(watcher_keys_q)/sizeof(watcher_keys[0])) {
+ CFAllocatorDeallocate(NULL, watcher_keys);
+ CFAllocatorDeallocate(NULL, watcher_vals);
+ }
+
+ return;
+}
+
+
+static void
+sanitize_address(const struct sockaddr *from, struct sockaddr *to)
+{
+ switch (from->sa_family) {
+ case AF_INET : {
+ /* ALIGN: cast okay, alignment not assumed. */
+ struct sockaddr_in *from4 = (struct sockaddr_in *)(void *)from;
+ struct sockaddr_in *to4 = (struct sockaddr_in *)(void *)to;
+
+ bzero(to4, sizeof(*to4));
+ to4->sin_len = sizeof(*to4);
+ to4->sin_family = AF_INET;
+ bcopy(&from4->sin_addr, &to4->sin_addr, sizeof(to4->sin_addr));
+ break;
+ }
+
+ case AF_INET6 : {
+ /* ALIGN: cast okay, alignment not assumed. */
+ struct sockaddr_in6 *from6 = (struct sockaddr_in6 *)(void *)from;
+ struct sockaddr_in6 *to6 = (struct sockaddr_in6 *)(void *)to;
+
+ bzero(to6, sizeof(*to6));
+ to6->sin6_len = sizeof(*to6);
+ to6->sin6_family = AF_INET6;
+ bcopy(&from6->sin6_addr, &to6->sin6_addr, sizeof(to6->sin6_addr));
+ to6->sin6_scope_id = from6->sin6_scope_id;
+ break;
+ }
+
+ default:
+ bcopy(from, to, from->sa_len);
+ break;
+ }
+
+ return;
+}
+
+
+static void
+target_add(reach_client_t *client, xpc_object_t request)
+{
+ const char *name;
+ const char *serv;
+ const struct sockaddr *localAddress;
+ struct sockaddr_storage localAddress0;
+ const struct sockaddr *remoteAddress;
+ struct sockaddr_storage remoteAddress0;
+ const struct addrinfo *hints;
+ int64_t if_index;
+ const char *if_name = NULL;
+ bool onDemandBypass = FALSE;
+ uint64_t target_id;
+
+
+ unsigned char bytes[CC_SHA1_DIGEST_LENGTH];
+ CC_SHA1_CTX ctx;
+ CFDataRef digest = NULL;
+ size_t len;
+ xpc_connection_t remote;
+ xpc_object_t reply;
+ bool resolverBypass = FALSE;
+ uint64_t status = REACH_REQUEST_REPLY_FAILED;
+
+ Boolean added;
+ __block Boolean ok = TRUE;
+ __block SCNetworkReachabilityRef target = NULL;
+
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> create reachability target"),
+ client->connection);
+// log_xpc_object(" create", request);
+ }
+
+ remote = xpc_dictionary_get_remote_connection(request);
+ reply = xpc_dictionary_create_reply(request);
+ if (reply == NULL) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target_add: xpc_dictionary_create_reply: failed"),
+ client->connection);
+ return;
+ }
+
+ target_id = xpc_dictionary_get_uint64(request, REACH_CLIENT_TARGET_ID);
+ if (target_id == 0) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "no target ID");
+ goto done;
+ }
+
+ // create a "digest" of the [new] target
+
+ CC_SHA1_Init(&ctx);
+
+ name = xpc_dictionary_get_string(request, REACH_TARGET_NAME);
+ if (name != NULL) {
+ CC_SHA1_Update(&ctx, name, strlen(name));
+ }
+
+ serv = xpc_dictionary_get_string(request, REACH_TARGET_SERV);
+ if (serv != NULL) {
+ CC_SHA1_Update(&ctx, serv, strlen(serv));
+ }
+
+ localAddress = xpc_dictionary_get_data(request, REACH_TARGET_LOCAL_ADDR, &len);
+ if (localAddress != NULL) {
+ if ((len == localAddress->sa_len) && (len <= sizeof(struct sockaddr_storage))) {
+ sanitize_address(localAddress, (struct sockaddr *)&localAddress0);
+ CC_SHA1_Update(&ctx, &localAddress0, len);
+ } else {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "local address: size error");
+ goto done;
+ }
+ }
+
+ remoteAddress = xpc_dictionary_get_data(request, REACH_TARGET_REMOTE_ADDR, &len);
+ if (remoteAddress != NULL) {
+ if ((len == remoteAddress->sa_len) && (len <= sizeof(struct sockaddr_storage))) {
+ sanitize_address(remoteAddress, (struct sockaddr *)&remoteAddress0);
+ CC_SHA1_Update(&ctx, &remoteAddress0, len);
+ } else {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "remote address: size error");
+ goto done;
+ }
+ }
+
+ hints = xpc_dictionary_get_data(request, REACH_TARGET_HINTS, &len);
+ if (hints != NULL) {
+ if (len == sizeof(struct addrinfo)) {
+ CC_SHA1_Update(&ctx, hints, len);
+ } else {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "hints: size error");
+ goto done;
+ }
+ }
+
+ if_index = xpc_dictionary_get_int64(request, REACH_TARGET_IF_INDEX);
+ if (if_index != 0) {
+ if_name = xpc_dictionary_get_string(request, REACH_TARGET_IF_NAME);
+ if (if_name != NULL) {
+ CC_SHA1_Update(&ctx, if_name, strlen(if_name));
+ }
+ }
+
+ onDemandBypass = xpc_dictionary_get_bool(request, REACH_TARGET_ONDEMAND_BYPASS);
+ if (onDemandBypass) {
+ CC_SHA1_Update(&ctx, &onDemandBypass, sizeof(onDemandBypass));
+ }
+
+ resolverBypass = xpc_dictionary_get_bool(request, REACH_TARGET_RESOLVER_BYPASS);
+ if (resolverBypass) {
+ CC_SHA1_Update(&ctx, &resolverBypass, sizeof(resolverBypass));
+ }
+
+
+ CC_SHA1_Final(bytes, &ctx);
+ digest = CFDataCreate(NULL, bytes, sizeof(bytes));
+
+ /*
+ * Check to see if we already have a SCNetworkReachability object
+ * for this digest. If so, we'll share the existing target. If not,
+ * create a new [shared] target.
+ */
+ dispatch_sync(_server_digest_queue(), ^{
+ target = CFDictionaryGetValue(reach_digest_map, digest);
+ if (target != NULL) {
+ CFRetain(target);
+ } else {
+ CFDataRef data;
+ CFMutableDictionaryRef options;
+ CFStringRef str;
+
+ options = CFDictionaryCreateMutable(NULL,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (name != NULL) {
+ str = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ CFDictionarySetValue(options, kSCNetworkReachabilityOptionNodeName, str);
+ CFRelease(str);
+ }
+ if (serv != NULL) {
+ str = CFStringCreateWithCString(NULL, serv, kCFStringEncodingUTF8);
+ CFDictionarySetValue(options, kSCNetworkReachabilityOptionServName, str);
+ CFRelease(str);
+ }
+ if (localAddress != NULL) {
+ data = CFDataCreate(NULL, (const UInt8 *)&localAddress0, localAddress0.ss_len);
+ CFDictionarySetValue(options, kSCNetworkReachabilityOptionLocalAddress, data);
+ CFRelease(data);
+ }
+ if (remoteAddress != NULL) {
+ data = CFDataCreate(NULL, (const UInt8 *)&remoteAddress0, remoteAddress0.ss_len);
+ CFDictionarySetValue(options, kSCNetworkReachabilityOptionRemoteAddress, data);
+ CFRelease(data);
+ }
+ if (hints != NULL) {
+ data = CFDataCreate(NULL, (const UInt8 *)hints, sizeof(struct addrinfo));
+ CFDictionarySetValue(options, kSCNetworkReachabilityOptionHints, data);
+ CFRelease(data);
+ }
+ if (onDemandBypass) {
+ CFDictionarySetValue(options,
+ kSCNetworkReachabilityOptionConnectionOnDemandBypass,
+ kCFBooleanTrue);
+ }
+ if (resolverBypass) {
+ CFDictionarySetValue(options,
+ kSCNetworkReachabilityOptionResolverBypass,
+ kCFBooleanTrue);
+ }
+ CFDictionarySetValue(options,
+ kSCNetworkReachabilityOptionServerBypass,
+ kCFBooleanTrue);
+ target = SCNetworkReachabilityCreateWithOptions(NULL, options);
+ CFRelease(options);
+ if (target == NULL) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "SCNetworkReachabilityCreateWithOptions failed");
+ ok = FALSE;
+ return;
+ }
+
+ // because the interface name may not (no longer) be valid we set
+ // this after we've created the SCNetworkReachabilty object
+ if ((if_index != 0) && (if_name != NULL)) {
+ SCNetworkReachabilityPrivateRef targetPrivate;
+
+ targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+ targetPrivate->if_index = if_index;
+ strlcpy(targetPrivate->if_name, if_name, sizeof(targetPrivate->if_name));
+ }
+
+
+ ok = SCNetworkReachabilitySetCallback(target, _reach_changed, NULL);
+ if (!ok) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "SCNetworkReachabilitySetCallback failed");
+ CFRelease(target);
+ target = NULL;
+ return;
+ }
+ }
+
+ // bump the number of references to this target
+ _target_reference_add(target, digest, client->connection);
+ });
+
+ if (!ok) {
+ goto done;
+ }
+
+ /*
+ * add an association for the client's target_id to the [shared]
+ * SCNetworkReachability object.
+ */
+ added = _client_target_set(client, target_id, target);
+ if (!added) {
+ // if we already had a reference to the target (e.g. reconnect)
+ dispatch_sync(_server_digest_queue(), ^{
+ _target_reference_remove(target, client->connection);
+ });
+ }
+
+ status = REACH_REQUEST_REPLY_OK;
+
+ done :
+
+ xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, status);
+// log_xpc_object(" reply", reply);
+ xpc_connection_send_message(remote, reply);
+ xpc_release(reply);
+
+ if (digest != NULL) CFRelease(digest);
+ if (target != NULL) CFRelease(target);
+ return;
+}
+
+
+static void
+target_remove(reach_client_t *client, xpc_object_t request)
+{
+ xpc_connection_t remote;
+ xpc_object_t reply;
+ uint64_t status = REACH_REQUEST_REPLY_FAILED;
+ SCNetworkReachabilityRef target = NULL;
+ uint64_t target_id;
+
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> remove reachability target"),
+ client->connection);
+// log_xpc_object(" remove", request);
+ }
+
+ remote = xpc_dictionary_get_remote_connection(request);
+ reply = xpc_dictionary_create_reply(request);
+ if (reply == NULL) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target_remove: xpc_dictionary_create_reply: failed"),
+ client->connection);
+ return;
+ }
+
+ target_id = xpc_dictionary_get_uint64(request, REACH_CLIENT_TARGET_ID);
+ if (target_id == 0) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "no target ID");
+ goto done;
+ }
+
+ target = _client_target_copy(client, target_id);
+ if (target == NULL) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "no target");
+ status = REACH_REQUEST_REPLY_UNKNOWN;
+ goto done;
+ }
+
+ /*
+ * remove the association from the client's target_id to the [shared]
+ * SCNetworkReachability object.
+ */
+ _client_target_remove(client, target_id);
+
+ // drop the number of references to this target
+ dispatch_sync(_server_digest_queue(), ^{
+ _target_reference_remove(target, client->connection);
+ });
+
+ status = REACH_REQUEST_REPLY_OK;
+
+ done :
+
+ xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, status);
+// log_xpc_object(" reply", reply);
+ xpc_connection_send_message(remote, reply);
+ xpc_release(reply);
+
+ if (target != NULL) CFRelease(target);
+ return;
+}
+
+
+static void
+target_schedule(reach_client_t *client, xpc_object_t request)
+{
+ Boolean ok;
+ xpc_connection_t remote;
+ xpc_object_t reply;
+ uint64_t status = REACH_REQUEST_REPLY_FAILED;
+ SCNetworkReachabilityRef target = NULL;
+ uint64_t target_id;
+
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> schedule reachability target"),
+ client->connection);
+// log_xpc_object(" schedule", request);
+ }
+
+ remote = xpc_dictionary_get_remote_connection(request);
+ reply = xpc_dictionary_create_reply(request);
+ if (reply == NULL) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target_schedule: xpc_dictionary_create_reply: failed"),
+ client->connection);
+ return;
+ }
+
+ target_id = xpc_dictionary_get_uint64(request, REACH_CLIENT_TARGET_ID);
+ if (target_id == 0) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "no target ID");
+ goto done;
+ }
+
+ target = _client_target_copy(client, target_id);
+ if (target == NULL) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "no target");
+ status = REACH_REQUEST_REPLY_UNKNOWN;
+ goto done;
+ }
+
+ // enable monitoring
+ ok = _target_watcher_add(target, client->connection, target_id);
+ if (ok) {
+ status = REACH_REQUEST_REPLY_OK;
+ }
+
+ done :
+
+ xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, status);
+// log_xpc_object(" reply", reply);
+ xpc_connection_send_message(remote, reply);
+ xpc_release(reply);
+
+ if (target != NULL) CFRelease(target);
+ return;
+}
+
+
+static void
+target_status(reach_client_t *client, xpc_object_t request)
+{
+ xpc_connection_t remote;
+ xpc_object_t reply;
+ __block Boolean reply_now = TRUE;
+ Boolean scheduled;
+ uint64_t status = REACH_REQUEST_REPLY_FAILED;
+ SCNetworkReachabilityRef target = NULL;
+ uint64_t target_id;
+
+ if(S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> get status of reachability target"),
+ client->connection);
+// log_xpc_object(" status", request);
+ }
+
+ remote = xpc_dictionary_get_remote_connection(request);
+ reply = xpc_dictionary_create_reply(request);
+ if (reply == NULL) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target_status: xpc_dictionary_create_reply: failed"),
+ client->connection);
+ return;
+ }
+
+ target_id = xpc_dictionary_get_uint64(request, REACH_CLIENT_TARGET_ID);
+ if (target_id == 0) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target_status: no target"),
+ client->connection);
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "no target ID");
+ goto done;
+ }
+
+ target = _client_target_copy(client, target_id);
+ if (target == NULL) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target_status: no target (0x%0llx)"),
+ client->connection,
+ target_id);
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "no target");
+ status = REACH_REQUEST_REPLY_UNKNOWN;
+ goto done;
+ }
+
+ /*
+ * Check to see if the target [for this client] had been "scheduled".
+ *
+ * If so, also mark that we've picked up the current reachability
+ * flags and that any pending notifications have been processed.
+ */
+ scheduled = _target_watcher_checkin(target, client->connection, target_id);
+
+ /*
+ * return current reachability information to the caller
+ */
+ dispatch_sync(_target_queue(target), ^{
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ if (scheduled) {
+ /*
+ * The client "scheduled" this target. As such, we
+ * know that this an async query and that we only
+ * need to return the "last known" flags.
+ */
+ _target_reply_add_reachability(target, reply);
+// log_xpc_object(" reply [scheduled]", reply);
+
+ if (S_debug) {
+ CFStringRef str;
+
+ str = _SCNetworkReachabilityCopyTargetFlags(target);
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> reply [scheduled], %@"),
+ client->connection,
+ str);
+ CFRelease(str);
+ }
+ } else {
+ /*
+ * The client has NOT "scheduled" this target. As
+ * such, we know that this is a sync query and that
+ * must return "current" flags.
+ */
+ if (targetPrivate->scheduled && targetPrivate->serverInfoValid) {
+ /*
+ * The server target has been "scheduled" and we
+ * have flags that are "current".
+ */
+ _target_reply_add_reachability(target, reply);
+// log_xpc_object(" reply [scheduled/valid]", reply);
+
+ if (S_debug) {
+ CFStringRef str;
+
+ str = _SCNetworkReachabilityCopyTargetFlags(target);
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> reply [scheduled/valid], %@"),
+ client->connection,
+ str);
+ CFRelease(str);
+ }
+ } else {
+ dispatch_group_t group;
+
+ /*
+ * The server target has NOT been "scheduled" (or
+ * we do not have "current" flags. This means that
+ * we must query for the current information and
+ * return the flags to the client when they are
+ * available.
+ */
+
+ reply_now = FALSE;
+
+ group = _target_group(target);
+ if (_SC_ATOMIC_INC(&targetPrivate->serverQueryActive) == 0) {
+ CFRetain(target);
+ dispatch_group_async(group, _server_concurrent_queue(), ^{
+ SCNetworkReachabilityFlags flags;
+ unsigned int n;
+ Boolean ok;
+
+ // query for the flags
+ ok = SCNetworkReachabilityGetFlags(target, &flags);
+ flags = targetPrivate->info.flags; // get the "raw" flags
+ if (!ok) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("SCNetworkReachabilityGetFlags() [sync query] failed"
+ "\n target = %@"
+ "\n status = %s"),
+ target,
+ SCErrorString(SCError()));
+ }
+
+ // flags are now available
+ n = _SC_ATOMIC_ZERO(&targetPrivate->serverQueryActive);
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("%sSCNetworkReachabilityGetFlags() [sync query] complete, n = %d"),
+ targetPrivate->log_prefix,
+ n);
+ }
+
+ CFRelease(target);
+ });
+ }
+
+ CFRetain(target);
+ dispatch_group_notify(group, _target_queue(target), ^{
+ // flags are now available
+ _target_reply_add_reachability(target, reply);
+ xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, REACH_REQUEST_REPLY_OK);
+// log_xpc_object(" reply [delayed]", reply);
+
+ if (S_debug) {
+ CFStringRef str;
+
+ str = _SCNetworkReachabilityCopyTargetFlags(target);
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> reply [delayed], %@"),
+ client->connection,
+ str);
+ CFRelease(str);
+ }
+
+ xpc_connection_send_message(remote, reply);
+ xpc_release(reply);
+
+ CFRelease(target);
+ });
+ }
+ }
+ });
+
+ status = REACH_REQUEST_REPLY_OK;
+
+ done :
+
+ if (reply_now) {
+ xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, status);
+
+ if (status != REACH_REQUEST_REPLY_OK) {
+// log_xpc_object(" reply [!]", reply);
+
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> reply [!]"),
+ client->connection);
+ }
+ }
+
+ xpc_connection_send_message(remote, reply);
+ xpc_release(reply);
+ } else if (S_debug) {
+ CFStringRef str;
+
+ str = _SCNetworkReachabilityCopyTargetFlags(target);
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> no reply [yet], %@"),
+ client->connection,
+ str);
+ CFRelease(str);
+ }
+
+ if (target != NULL) CFRelease(target);
+ return;
+}
+
+
+static void
+target_unschedule(reach_client_t *client, xpc_object_t request)
+{
+ Boolean ok;
+ xpc_connection_t remote;
+ xpc_object_t reply;
+ uint64_t status = REACH_REQUEST_REPLY_FAILED;
+ SCNetworkReachabilityRef target = NULL;
+ uint64_t target_id;
+
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> unschedule reachability target"),
+ client->connection);
+// log_xpc_object(" unschedule", request);
+ }
+
+ remote = xpc_dictionary_get_remote_connection(request);
+ reply = xpc_dictionary_create_reply(request);
+ if (reply == NULL) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> target_unschedule: xpc_dictionary_create_reply: failed"),
+ client->connection);
+ return;
+ }
+
+ target_id = xpc_dictionary_get_uint64(request, REACH_CLIENT_TARGET_ID);
+ if (target_id == 0) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "no target ID");
+ goto done;
+ }
+
+ target = _client_target_copy(client, target_id);
+ if (target == NULL) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "no target");
+ status = REACH_REQUEST_REPLY_UNKNOWN;
+ goto done;
+ }
+
+ // disable monitoring
+ ok = _target_watcher_remove(target, client->connection, target_id);
+ if (ok) {
+ status = REACH_REQUEST_REPLY_OK;
+ }
+
+ done :
+
+ xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, status);
+// log_xpc_object(" reply", reply);
+ xpc_connection_send_message(remote, reply);
+ xpc_release(reply);
+
+ if (target != NULL) CFRelease(target);
+ return;
+}
+
+
+#define SNAPSHOT_PATH_STATE _PATH_VARTMP "configd-reachability"
+
+
+static void
+_snapshot_digest_watcher(const void *key, const void *value, void *context)
+{
+ FILE *f = (FILE *)context;
+ static reach_client_t no_client = {
+ .pid = 0,
+ .proc_name = "?",
+ };
+ struct rb_node *rbn;
+ reach_client_t *rbt_client;
+ reach_watcher_key_t *watcher_key;
+ reach_watcher_val_t *watcher_val;
+
+ /* ALIGN: CF aligns to >8 byte boundries */
+ watcher_key = (reach_watcher_key_t *)(void *)CFDataGetBytePtr(key);
+ watcher_val = (reach_watcher_val_t *)(void *)CFDataGetBytePtr(value);
+
+ rbn = rb_tree_find_node(_reach_clients_rbt(), &watcher_key->connection);
+ if (rbn == NULL) {
+ rbn = &no_client.rbn;
+ }
+
+ rbt_client = RBNODE_TO_REACH_CLIENT(rbn);
+
+ SCPrint(TRUE, f,
+ CFSTR(" connection = %p, target(c) = 0x%0llx, command = %s, pid = %d, changes = %u\n"),
+ watcher_key->connection,
+ watcher_key->target_id,
+ rbt_client->proc_name,
+ rbt_client->pid,
+ watcher_val->n_changes);
+
+ return;
+}
+
+
+static void
+_snapshot_digest(const void *key, const void *value, void *context)
+{
+ FILE *f = (FILE *)context;
+ CFStringRef digest = (CFStringRef)key;
+ dispatch_queue_t q;
+ SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)value;
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ q = _target_queue(target);
+ dispatch_sync(q, ^{
+ SCPrint(TRUE, f, CFSTR("\n digest : %@\n"), digest);
+ SCPrint(TRUE, f, CFSTR(" %@\n"), target);
+ SCPrint(TRUE, f, CFSTR(" valid = %s, active = %u, refs = %u\n"),
+ targetPrivate->serverInfoValid ? "Y" : "N",
+ targetPrivate->serverQueryActive,
+ targetPrivate->serverReferences);
+
+ SCPrint(TRUE, f, CFSTR(" network %d.%3.3d"),
+ targetPrivate->last_network.tv_sec,
+ targetPrivate->last_network.tv_usec / 1000);
+#if !TARGET_OS_IPHONE
+ SCPrint(TRUE, f, CFSTR(", power %d.%3.3d"),
+ targetPrivate->last_power.tv_sec,
+ targetPrivate->last_power.tv_usec / 1000);
+#endif // !TARGET_OS_IPHONE
+ if (targetPrivate->type == reachabilityTypeName) {
+ SCPrint(TRUE, f, CFSTR(", DNS %d.%3.3d"),
+ targetPrivate->last_dns.tv_sec,
+ targetPrivate->last_dns.tv_usec / 1000);
+ if (timerisset(&targetPrivate->dnsQueryEnd)) {
+ struct timeval dnsQueryElapsed;
+
+ timersub(&targetPrivate->dnsQueryEnd,
+ &targetPrivate->dnsQueryStart,
+ &dnsQueryElapsed);
+ SCPrint(TRUE, f, CFSTR(" (query %d.%3.3d / reply %d.%3.3d)"),
+ targetPrivate->dnsQueryStart.tv_sec,
+ targetPrivate->dnsQueryStart.tv_usec / 1000,
+ dnsQueryElapsed.tv_sec,
+ dnsQueryElapsed.tv_usec / 1000);
+ }
+ }
+ if (timerisset(&targetPrivate->last_push)) {
+ SCPrint(TRUE, f, CFSTR(", last notify %d.%3.3d"),
+ targetPrivate->last_push.tv_sec,
+ targetPrivate->last_push.tv_usec / 1000);
+ }
+ SCPrint(TRUE, f, CFSTR("\n"));
+
+ if (targetPrivate->serverWatchers != NULL) {
+ CFDictionaryApplyFunction(targetPrivate->serverWatchers,
+ _snapshot_digest_watcher,
+ f);
+ }
+ });
+
+ return;
+}
+
+
+static void
+_snapshot_target(const void *key, const void *value, void *context)
+{
+ FILE *f = (FILE *)context;
+ SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)value;
+ uint64_t target_id;
+ CFDataRef target_key = (CFDataRef)key;
+
+ /* ALIGN: CF aligns > 8 byte boundries */
+ target_id = *(uint64_t *)(void *)CFDataGetBytePtr(target_key);
+
+ SCPrint(TRUE, f,
+ CFSTR(" target(c) = 0x%0llx, target(s) = %@\n"),
+ target_id,
+ target);
+
+ return;
+}
+
+
+static void
+_snapshot(reach_client_t *client, xpc_object_t request)
+{
+ uid_t euid;
+ FILE *f;
+ int fd;
+ Boolean ok = FALSE;
+ struct rb_node *rbn;
+ struct rb_tree *rbt;
+ xpc_connection_t remote;
+ xpc_object_t reply;
+
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p> snapshot"),
+ client->connection);
+// log_xpc_object(" create", request);
+ }
+
+ remote = xpc_dictionary_get_remote_connection(request);
+ reply = xpc_dictionary_create_reply(request);
+ if (reply == NULL) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> _snapshot: xpc_dictionary_create_reply: failed"),
+ client->connection);
+ return;
+ }
+
+ euid = xpc_connection_get_euid(remote);
+ if (euid != 0) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "Permission denied.");
+ goto done;
+ }
+
+ // Save a snapshot of the SCNetworkReachability server "state"
+
+ (void) unlink(SNAPSHOT_PATH_STATE);
+ fd = open(SNAPSHOT_PATH_STATE, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644);
+ if (fd == -1) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "open: failed");
+ goto done;
+ }
+ f = fdopen(fd, "w");
+ if (f == NULL) {
+ xpc_dictionary_set_string(reply,
+ REACH_REQUEST_REPLY_DETAIL,
+ "fdopen: failed");
+ goto done;
+ }
+
+ // provide connection/client info
+
+ SCPrint(TRUE, f, CFSTR("Clients :\n"));
+ rbt = _reach_clients_rbt();
+ rbn = rb_tree_iterate(rbt, NULL, RB_DIR_RIGHT);
+ if (rbn != NULL) {
+ while (rbn != NULL) {
+ reach_client_t *rbt_client;
+
+ rbt_client = RBNODE_TO_REACH_CLIENT(rbn);
+ SCPrint(TRUE, f,
+ CFSTR("\n connection = %p, client = %p, command = %s, pid = %d\n"),
+ rbt_client->connection,
+ rbt_client,
+ rbt_client->proc_name != NULL ? rbt_client->proc_name : "?",
+ rbt_client->pid);
+ my_CFDictionaryApplyFunction(rbt_client->targets,
+ _snapshot_target,
+ f);
+
+ rbn = rb_tree_iterate(rbt, rbn, RB_DIR_LEFT);
+ }
+ } else {
+ SCPrint(TRUE, f, CFSTR(" None.\n"));
+ }
+ SCPrint(TRUE, f, CFSTR("\n"));
+
+ // provide "digest" info
+
+ SCPrint(TRUE, f, CFSTR("Digests :\n"));
+ dispatch_sync(_server_digest_queue(), ^{
+ if (reach_digest_map != NULL) {
+ CFDictionaryApplyFunction(reach_digest_map,
+ _snapshot_digest,
+ f);
+ }
+ });
+
+ (void) fclose(f);
+
+ ok = TRUE;
+
+ done :
+
+ xpc_dictionary_set_int64(reply,
+ REACH_REQUEST_REPLY,
+ ok ? REACH_REQUEST_REPLY_OK : REACH_REQUEST_REPLY_FAILED);
+// log_xpc_object(" reply", reply);
+ xpc_connection_send_message(remote, reply);
+ xpc_release(reply);
+
+ return;
+}
+
+
+static __inline__ void
+_extract_client_info(reach_client_t *client, xpc_object_t request)
+{
+ // if available/needed, save the process name
+ if (client->proc_name == NULL) {
+ const char *proc_name;
+
+ proc_name = xpc_dictionary_get_string(request, REACH_CLIENT_PROC_NAME);
+ if (proc_name != NULL) {
+ client->proc_name = strdup(proc_name);
+ }
+ }
+
+ return;
+}
+
+
+static void
+process_request(reach_client_t *client, xpc_object_t request)
+{
+ int64_t op;
+
+ op = xpc_dictionary_get_int64(request, REACH_REQUEST);
+ switch (op) {
+ case REACH_REQUEST_CREATE :
+ _extract_client_info(client, request);
+ target_add(client, request);
+ break;
+ case REACH_REQUEST_REMOVE :
+ target_remove(client, request);
+ break;
+ case REACH_REQUEST_STATUS :
+ target_status(client, request);
+ break;
+ case REACH_REQUEST_SCHEDULE :
+ target_schedule(client, request);
+ break;
+ case REACH_REQUEST_UNSCHEDULE :
+ target_unschedule(client, request);
+ break;
+ case REACH_REQUEST_SNAPSHOT :
+ _extract_client_info(client, request);
+ _snapshot(client, request);
+ break;
+ default :
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p> unknown request : %d"),
+ client->connection,
+ op);
+ break;
+ }
+
+ return;
+}
+
+
+static void
+process_new_connection(xpc_connection_t connection)
+{
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO, CFSTR("<%p> new reach client, pid=%d"),
+ connection,
+ xpc_connection_get_pid(connection));
+ }
+
+ dispatch_sync(_reach_connection_queue(), ^{
+ reach_client_t *client;
+
+ client = _reach_client_create(connection);
+ if (client == NULL || !rb_tree_insert_node(_reach_clients_rbt(), &client->rbn)) {
+ __builtin_trap();
+ }
+ });
+
+ xpc_connection_set_event_handler(connection, ^(xpc_object_t xobj) {
+ xpc_type_t type;
+
+ type = xpc_get_type(xobj);
+ if (type == XPC_TYPE_DICTIONARY) {
+ dispatch_sync(_reach_connection_queue(), ^{
+ struct rb_node *rbn;
+
+ rbn = rb_tree_find_node(_reach_clients_rbt(), &connection);
+ if (rbn != NULL) {
+ reach_client_t *client;
+
+ // process the request
+ client = RBNODE_TO_REACH_CLIENT(rbn);
+ process_request(client, xobj);
+ } else {
+ char *desc;
+
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p:%d> unexpected SCNetworkReachability request"),
+ connection,
+ xpc_connection_get_pid(connection));
+
+ desc = xpc_copy_description(xobj);
+ SCLog(TRUE, LOG_ERR,
+ CFSTR(" request = %s"),
+ desc);
+ free(desc);
+
+ xpc_connection_cancel(connection);
+ }
+ });
+
+ } else if (type == XPC_TYPE_ERROR) {
+ const char *desc;
+
+ desc = xpc_dictionary_get_string(xobj, XPC_ERROR_KEY_DESCRIPTION);
+ if (xobj == XPC_ERROR_CONNECTION_INVALID) {
+ if (S_debug) {
+ SCLog(TRUE, LOG_INFO,
+ CFSTR("<%p:%d> %s"),
+ connection,
+ xpc_connection_get_pid(connection),
+ desc);
+ }
+
+ xpc_retain(connection);
+ dispatch_async(_reach_connection_queue(), ^{
+ _reach_client_remove(connection);
+ xpc_release(connection);
+ });
+
+ } else if (xobj == XPC_ERROR_CONNECTION_INTERRUPTED) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p:%d> %s"),
+ connection,
+ xpc_connection_get_pid(connection),
+ desc);
+
+ } else {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p:%d> Connection error: %d : %s"),
+ connection,
+ xpc_connection_get_pid(connection),
+ xobj,
+ desc);
+ }
+
+ } else {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("<%p:%d> unknown event type : %x"),
+ connection,
+ xpc_connection_get_pid(connection),
+ type);
+ }
+ });
+
+ xpc_connection_resume(connection);
+
+ return;
+}
+
+
+#pragma mark -
+#pragma mark Reachability server "main"
+
+
+__private_extern__
+void
+load_SCNetworkReachability(CFBundleRef bundle, Boolean bundleVerbose)
+{
+ xpc_connection_t connection;
+ const char *name;
+ dispatch_queue_t reach_server_q;
+
+ S_debug = bundleVerbose;
+
+ /*
+ * create a dictionary mapping SCNetworkReachability [CFData] digests
+ * to SCNetworkReachability objects.
+ */
+ reach_digest_map = CFDictionaryCreateMutable(NULL,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ /*
+ * create dispatch queue for processing SCNetworkReachability
+ * service requests
+ */
+ reach_server_q = dispatch_queue_create(REACH_SERVICE_NAME, NULL);
+
+ // create XPC listener
+ name = getenv("REACH_SERVER");
+ if (name == NULL) {
+ name = REACH_SERVICE_NAME;
+ }
+ connection = xpc_connection_create_mach_service(name,
+ reach_server_q,
+ XPC_CONNECTION_MACH_SERVICE_LISTENER);
+
+ xpc_connection_set_event_handler(connection, ^(xpc_object_t event) {
+ xpc_type_t type;
+
+ type = xpc_get_type(event);
+ if (type == XPC_TYPE_CONNECTION) {
+ process_new_connection(event);
+
+ } else if (type == XPC_TYPE_ERROR) {
+ const char *desc;
+
+ desc = xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION);
+ if (event == XPC_ERROR_CONNECTION_INVALID) {
+ SCLog(TRUE, LOG_ERR, CFSTR("reach server: %s"), desc);
+ xpc_release(connection);
+ } else if (event == XPC_ERROR_CONNECTION_INTERRUPTED) {
+ SCLog(TRUE, LOG_ERR, CFSTR("reach server: %s"), desc);
+ } else {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("reach server: Connection error: %d : %s"),
+ event,
+ desc);
+ }
+
+ } else {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("reach server: unknown event type : %x"),
+ type);
+ }
+ });
+ xpc_connection_resume(connection);
+
+ return;
+}
+
+#ifdef MAIN
+
+int
+main(int argc, char **argv)
+{
+// _sc_log = FALSE;
+ _sc_verbose = (argc > 1) ? TRUE : FALSE;
+ _sc_debug = TRUE;
+
+ load_SCNetworkReachability(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
+ CFRunLoopRun();
+ /* not reached */
+ exit(0);
+ return 0;
+}
+
+#endif /* MAIN */
+
+#endif // HAVE_REACHABILITY_SERVER