SCNetworkReachabilityServer_server.c [plain text]
#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
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 -
static void
_target_reference_add(SCNetworkReachabilityRef target, CFDataRef digest, xpc_connection_t connection)
{
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
CFRetain(target);
if (targetPrivate->serverGroup == NULL) {
targetPrivate->serverGroup = dispatch_group_create();
}
if (targetPrivate->serverQueue == NULL) {
char qname[256];
snprintf(qname, sizeof(qname), "com.apple.SCNetworkReachability.%p.server", target);
targetPrivate->serverQueue = dispatch_queue_create(qname, NULL);
}
if (_SC_ATOMIC_INC(&targetPrivate->serverReferences) == 0) {
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;
}
static void
_target_reference_remove(SCNetworkReachabilityRef target, xpc_connection_t connection)
{
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
if (_SC_ATOMIC_DEC(&targetPrivate->serverReferences) == 0) {
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);
}
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) {
return;
}
key = _target_watcher_key_create(connection, target_id);
val = CFDictionaryGetValue(targetPrivate->serverWatchers, key);
CFRelease(key);
if (val == NULL) {
return;
}
scheduled = TRUE;
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, target_id, 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;
}
(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; } 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;
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];
watcher_key = (reach_watcher_key_t *)(void *)CFDataGetBytePtr(key);
if (watcher_key->connection == connection) {
_target_watcher_remove(target,
watcher_key->connection,
watcher_key->target_id);
}
}
if (watchers != watchers_q) {
CFAllocatorDeallocate(NULL, watchers);
}
}
}
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);
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
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) {
return;
}
n = CFDictionaryGetCount(targetPrivate->serverWatchers);
if (n == 0) {
return;
}
if (_SC_ATOMIC_CMPXCHG(&targetPrivate->serverInfoValid, FALSE, TRUE)) {
if (S_debug) {
SCLog(TRUE, LOG_INFO, CFSTR(" flags are now \"valid\""));
}
}
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];
watcher_val = (reach_watcher_val_t *)(void *)CFDataGetBytePtr(val);
if (_SC_ATOMIC_INC(&watcher_val->n_changes) > 0) {
continue;
}
key = (CFDataRef)watcher_keys[i];
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;
reply = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(reply,
MESSAGE_NOTIFY,
MESSAGE_REACHABILITY_STATUS);
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 : {
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 : {
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;
bool resolverBypass = 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;
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);
}
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;
}
CC_SHA1_Init(&ctx);
name = xpc_dictionary_get_string(request, REACH_TARGET_NAME);
if (name != NULL) {
CC_SHA1_Update(&ctx, REACH_TARGET_NAME, sizeof(REACH_TARGET_NAME));
CC_SHA1_Update(&ctx, name, strlen(name));
}
serv = xpc_dictionary_get_string(request, REACH_TARGET_SERV);
if (serv != NULL) {
CC_SHA1_Update(&ctx, REACH_TARGET_SERV, sizeof(REACH_TARGET_SERV));
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, REACH_TARGET_LOCAL_ADDR, sizeof(REACH_TARGET_LOCAL_ADDR));
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, REACH_TARGET_REMOTE_ADDR, sizeof(REACH_TARGET_REMOTE_ADDR));
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, REACH_TARGET_HINTS, sizeof(REACH_TARGET_HINTS));
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, REACH_TARGET_IF_NAME, sizeof(REACH_TARGET_IF_NAME));
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, REACH_TARGET_ONDEMAND_BYPASS, sizeof(REACH_TARGET_ONDEMAND_BYPASS));
CC_SHA1_Update(&ctx, &onDemandBypass, sizeof(onDemandBypass));
}
resolverBypass = xpc_dictionary_get_bool(request, REACH_TARGET_RESOLVER_BYPASS);
if (resolverBypass) {
CC_SHA1_Update(&ctx, REACH_TARGET_RESOLVER_BYPASS, sizeof(REACH_TARGET_RESOLVER_BYPASS));
CC_SHA1_Update(&ctx, &resolverBypass, sizeof(resolverBypass));
}
CC_SHA1_Final(bytes, &ctx);
digest = CFDataCreate(NULL, bytes, sizeof(bytes));
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;
}
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;
}
}
_target_reference_add(target, digest, client->connection);
});
if (!ok) {
goto done;
}
added = _client_target_set(client, target_id, target);
if (!added) {
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);
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);
}
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;
}
_client_target_remove(client, target_id);
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);
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);
}
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;
}
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);
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);
}
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;
}
scheduled = _target_watcher_checkin(target, client->connection, target_id);
dispatch_sync(_target_queue(target), ^{
SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
if (scheduled) {
_target_reply_add_reachability(target, reply);
if (S_debug) {
CFStringRef str;
str = _SCNetworkReachabilityCopyTargetFlags(target);
SCLog(TRUE, LOG_INFO,
CFSTR("<%p> reply [scheduled], %@"),
client->connection,
str);
CFRelease(str);
}
} else {
if (targetPrivate->scheduled && targetPrivate->serverInfoValid) {
_target_reply_add_reachability(target, 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;
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;
ok = SCNetworkReachabilityGetFlags(target, &flags);
flags = targetPrivate->info.flags; if (!ok) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCNetworkReachabilityGetFlags() [sync query] failed"
"\n target = %@"
"\n status = %s"),
target,
SCErrorString(SCError()));
}
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), ^{
_target_reply_add_reachability(target, reply);
xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, REACH_REQUEST_REPLY_OK);
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) {
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);
}
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;
}
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);
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;
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;
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);
}
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;
}
(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;
}
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"));
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);
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 (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;
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;
reach_digest_map = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
reach_server_q = dispatch_queue_create(REACH_SERVICE_NAME, NULL);
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_verbose = (argc > 1) ? TRUE : FALSE;
_sc_debug = TRUE;
load_SCNetworkReachability(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
CFRunLoopRun();
exit(0);
return 0;
}
#endif
#endif // HAVE_REACHABILITY_SERVER