1/*
2 * Copyright (c) 2011-2013 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <CoreFoundation/CoreFoundation.h>
25#include <SystemConfiguration/SystemConfiguration.h>
26#include <SystemConfiguration/SCPrivate.h>
27#include "SCNetworkReachabilityInternal.h"
28
29#ifdef	HAVE_REACHABILITY_SERVER
30
31#include <fcntl.h>
32#include <paths.h>
33#include <CommonCrypto/CommonDigest.h>
34#include <dispatch/dispatch.h>
35#include <dispatch/private.h>
36#include <xpc/xpc.h>
37#include <xpc/private.h>
38#include <sys/rbtree.h>
39
40
41#pragma mark -
42#pragma mark Globals
43
44
45/*
46 * S_debug
47 *   A boolean that enables additional logging.
48 */
49static boolean_t	S_debug		= FALSE;
50
51
52#pragma mark -
53#pragma mark Support functions
54
55
56static void
57log_xpc_object(const char *msg, xpc_object_t obj)
58{
59	char		*desc;
60
61	desc = xpc_copy_description(obj);
62	SCLog(S_debug, LOG_INFO, CFSTR("%s = %s"), msg, desc);
63	free(desc);
64}
65
66
67static __inline__ void
68my_CFDictionaryApplyFunction(CFDictionaryRef			theDict,
69			     CFDictionaryApplierFunction	applier,
70			     void				*context)
71{
72	CFAllocatorRef	myAllocator;
73	CFDictionaryRef	myDict;
74
75	myAllocator = CFGetAllocator(theDict);
76	myDict      = CFDictionaryCreateCopy(myAllocator, theDict);
77	CFDictionaryApplyFunction(myDict, applier, context);
78	CFRelease(myDict);
79	return;
80}
81
82
83#pragma mark -
84#pragma mark SCNetworkReachability target support
85
86
87static CFMutableDictionaryRef	reach_digest_map;
88
89
90static dispatch_queue_t
91_server_concurrent_queue()
92{
93	static dispatch_once_t	once;
94	static dispatch_queue_t	q;
95
96	dispatch_once(&once, ^{
97		q = dispatch_queue_create(REACH_SERVICE_NAME ".concurrent",
98					  DISPATCH_QUEUE_CONCURRENT);
99		dispatch_queue_set_width(q, 32);
100	});
101
102	return q;
103}
104
105
106static dispatch_queue_t
107_server_digest_queue()
108{
109	static dispatch_once_t	once;
110	static dispatch_queue_t	q;
111
112	dispatch_once(&once, ^{
113		q = dispatch_queue_create(REACH_SERVICE_NAME ".digest", NULL);
114	});
115
116	return q;
117}
118
119
120static dispatch_group_t
121_target_group(SCNetworkReachabilityRef target)
122{
123	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
124
125	return targetPrivate->serverGroup;
126}
127
128
129static dispatch_queue_t
130_target_queue(SCNetworkReachabilityRef target)
131{
132	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
133
134	return targetPrivate->serverQueue;
135}
136
137
138#pragma mark -
139
140
141/*
142 * _target_reference_add
143 *
144 * Note: use dispatch_sync(_server_digest_queue(), ^{ ... });
145 */
146static void
147_target_reference_add(SCNetworkReachabilityRef target, CFDataRef digest, xpc_connection_t connection)
148{
149	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
150
151	// take a reference to the target
152	CFRetain(target);
153
154	// ensure that we have a dispatch group
155	if (targetPrivate->serverGroup == NULL) {
156		targetPrivate->serverGroup = dispatch_group_create();
157	}
158
159	// ensure that we have a dispatch queue
160	if (targetPrivate->serverQueue == NULL) {
161		char	qname[256];
162
163		snprintf(qname, sizeof(qname), "com.apple.SCNetworkReachability.%p.server", target);
164		targetPrivate->serverQueue = dispatch_queue_create(qname, NULL);
165	}
166
167	// bump the reference count
168	if (_SC_ATOMIC_INC(&targetPrivate->serverReferences) == 0) {
169		// and maintain a digest-->target mapping
170		targetPrivate->serverDigest = CFRetain(digest);
171		CFDictionarySetValue(reach_digest_map, digest, target);
172	}
173
174	if (S_debug) {
175		CFStringRef	str;
176
177		str = _SCNetworkReachabilityCopyTargetDescription(target);
178		SCLog(TRUE, LOG_INFO,
179		      CFSTR("<%p>   target %p: reference added (%@, %d)"),
180		      connection,
181		      target,
182		      str,
183		      targetPrivate->serverReferences);
184		CFRelease(str);
185	}
186
187	return;
188}
189
190
191/*
192 * _target_reference_remove
193 *
194 * Note: use dispatch_sync(_server_digest_queue(), ^{ ... });
195 */
196static void
197_target_reference_remove(SCNetworkReachabilityRef target, xpc_connection_t connection)
198{
199	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
200
201	// drop the reference count
202	if (_SC_ATOMIC_DEC(&targetPrivate->serverReferences) == 0) {
203		/*
204		 * if that was the last reference, we no longer need to
205		 * keep the digest-->target mapping
206		 */
207		CFDictionaryRemoveValue(reach_digest_map, targetPrivate->serverDigest);
208		CFRelease(targetPrivate->serverDigest);
209		targetPrivate->serverDigest = NULL;
210	}
211
212	if (S_debug) {
213		SCLog(TRUE, LOG_INFO,
214		      CFSTR("<%p>   target %p: reference removed (%d)"),
215		      connection,
216		      target,
217		      targetPrivate->serverReferences);
218	}
219
220	// release a reference to the target
221	CFRelease(target);
222
223	return;
224}
225
226
227#pragma mark -
228
229
230#define	MUTEX_LOCK(m) {							\
231	int _lock_ = (pthread_mutex_lock(m) == 0);			\
232	assert(_lock_);							\
233}
234
235#define	MUTEX_UNLOCK(m) {						\
236	int _unlock_ = (pthread_mutex_unlock(m) == 0);			\
237	assert(_unlock_);						\
238}
239
240
241static void
242_target_reply_add_reachability(SCNetworkReachabilityRef target,
243			       xpc_object_t		reply)
244{
245	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
246
247	MUTEX_LOCK(&targetPrivate->lock);
248
249	xpc_dictionary_set_uint64(reply,
250				  REACH_STATUS_CYCLE,
251				  targetPrivate->info.cycle);
252	xpc_dictionary_set_uint64(reply,
253				  REACH_STATUS_FLAGS,
254				  targetPrivate->info.flags);
255	xpc_dictionary_set_uint64(reply,
256				  REACH_STATUS_IF_INDEX,
257				  targetPrivate->info.if_index);
258	xpc_dictionary_set_data  (reply,
259				  REACH_STATUS_IF_NAME,
260				  targetPrivate->info.if_name,
261				  sizeof(targetPrivate->info.if_name));
262	xpc_dictionary_set_bool	 (reply,
263				  REACH_STATUS_SLEEPING,
264				  targetPrivate->info.sleeping);
265	if (targetPrivate->type == reachabilityTypeName) {
266		if (isA_CFArray(targetPrivate->resolvedAddresses)) {
267			xpc_object_t	addresses;
268			CFIndex		i;
269			CFIndex		n;
270
271			addresses = xpc_array_create(NULL, 0);
272
273			n = CFArrayGetCount(targetPrivate->resolvedAddresses);
274			for (i = 0; i < n; i++) {
275				CFDataRef	address;
276
277				address = CFArrayGetValueAtIndex(targetPrivate->resolvedAddresses, i);
278				xpc_array_set_data(addresses,
279						   XPC_ARRAY_APPEND,
280						   CFDataGetBytePtr(address),
281						   CFDataGetLength(address));
282			}
283
284			xpc_dictionary_set_value(reply,
285						 REACH_STATUS_RESOLVED_ADDRESSES,
286						 addresses);
287			xpc_release(addresses);
288		}
289		xpc_dictionary_set_int64(reply,
290					 REACH_STATUS_RESOLVED_ERROR,
291					 targetPrivate->resolvedError);
292	}
293
294	MUTEX_UNLOCK(&targetPrivate->lock);
295
296	return;
297}
298
299
300#pragma mark -
301
302
303typedef struct {
304	xpc_connection_t	connection;
305	uint64_t		target_id;
306} reach_watcher_key_t;
307
308typedef struct {
309	unsigned int		n_changes;
310} reach_watcher_val_t;
311
312
313static CFDataRef
314_target_watcher_key_create(xpc_connection_t	connection,
315			   uint64_t		target_id)
316{
317	CFDataRef		key;
318	reach_watcher_key_t	watcher_key;
319
320	watcher_key.connection = connection;
321	watcher_key.target_id  = target_id;
322
323	key = CFDataCreate(NULL, (UInt8 *)&watcher_key, sizeof(watcher_key));
324	return key;
325}
326
327
328static Boolean
329_target_watcher_add(SCNetworkReachabilityRef	target,
330		    xpc_connection_t		connection,
331		    uint64_t			target_id)
332{
333	__block Boolean		ok	= TRUE;
334	dispatch_queue_t	q;
335
336	q = _target_queue(target);
337	dispatch_sync(q, ^{
338		CFDataRef			key;
339		SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
340
341		if (targetPrivate->serverWatchers == NULL) {
342			ok = SCNetworkReachabilitySetDispatchQueue(target, q);
343			if (!ok) {
344				SCLog(TRUE, LOG_ERR,
345				      CFSTR("<%p> target %p: _watcher_add SCNetworkReachabilitySetDispatchQueue() failed: %s"),
346				      connection,
347				      target,
348				      SCErrorString(SCError()));
349				return;
350			}
351
352			targetPrivate->serverWatchers = CFDictionaryCreateMutable(NULL,
353										  0,
354										  &kCFTypeDictionaryKeyCallBacks,
355										  &kCFTypeDictionaryValueCallBacks);
356		}
357
358		xpc_retain(connection);
359
360		key = _target_watcher_key_create(connection, target_id);
361		if (CFDictionaryContainsKey(targetPrivate->serverWatchers, key)) {
362			SCLog(TRUE, LOG_ERR,
363			      CFSTR("<%p>   target %p: watcher not added, c=0x%0llx, \"serverWatchers\" key exists"),
364			      connection,
365			      target,
366			      target_id);
367		} else {
368			CFDataRef				val;
369			static const reach_watcher_val_t	watcher_val0	= { 0 };
370
371			val = CFDataCreate(NULL, (UInt8 *)&watcher_val0, sizeof(watcher_val0));
372			CFDictionaryAddValue(targetPrivate->serverWatchers, key, val);
373			CFRelease(val);
374
375			if (S_debug) {
376				SCLog(TRUE, LOG_INFO,
377				      CFSTR("<%p>   target %p: watcher added, c=0x%0llx, n=%d"),
378				      connection,
379				      target,
380				      target_id,
381				      CFDictionaryGetCount(targetPrivate->serverWatchers));
382			}
383		}
384		CFRelease(key);
385	});
386
387	return ok;
388}
389
390
391static Boolean
392_target_watcher_checkin(SCNetworkReachabilityRef	target,
393			xpc_connection_t		connection,
394			uint64_t			target_id)
395{
396	__block Boolean		scheduled	= FALSE;
397
398	dispatch_sync(_target_queue(target), ^{
399		CFDataRef			key;
400		unsigned int			n;
401		SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
402		CFDataRef			val;
403		reach_watcher_val_t		*watcher_val;
404
405		if (targetPrivate->serverWatchers == NULL) {
406			// if no watchers
407			return;
408		}
409
410		key = _target_watcher_key_create(connection, target_id);
411		val = CFDictionaryGetValue(targetPrivate->serverWatchers, key);
412		CFRelease(key);
413		if (val == NULL) {
414			// if the target [for this client] was not scheduled
415			return;
416		}
417
418		// indicate that the target was scheduled
419		scheduled = TRUE;
420
421		/*
422		 * and note that the reachability flags for this target have
423		 * been picked up by the client
424		 */
425		/* ALIGN: CF aligns to at least >8 byte boundries */
426		watcher_val = (reach_watcher_val_t *)(void *)CFDataGetBytePtr(val);
427		n = _SC_ATOMIC_ZERO(&watcher_val->n_changes);
428		if (S_debug && (n > 0)) {
429			SCLog(TRUE, LOG_INFO,
430			      CFSTR("<%p> target %p: SCNetworkReachabilityGetFlags() after %d notification%s"),
431			      connection,
432			      target,
433			      n,
434			      (n == 1) ? "" : "s");
435		}
436	});
437
438	return scheduled;
439}
440
441
442static Boolean
443_target_watcher_remove(SCNetworkReachabilityRef	target,
444		       xpc_connection_t		connection,
445		       uint64_t			target_id)
446{
447	__block Boolean		ok	= TRUE;
448
449	dispatch_sync(_target_queue(target), ^{
450		CFDataRef			key;
451		CFIndex				n;
452		SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
453
454		if (targetPrivate->serverWatchers == NULL) {
455			SCLog(TRUE, LOG_ERR,
456			      CFSTR("<%p>   target %p: watcher not removed, c=0x%0llx, no \"serverWatchers\""),
457			      connection,
458			      target,
459			      target_id);
460			ok = FALSE;
461			return;
462		}
463
464		key = _target_watcher_key_create(connection, target_id);
465		if (!CFDictionaryContainsKey(targetPrivate->serverWatchers, key)) {
466			SCLog(TRUE, LOG_ERR,
467			      CFSTR("<%p>   target %p: watcher not removed, c=0x%0llx, no \"serverWatchers\" key"),
468			      connection,
469			      target,
470			      target_id);
471			CFRelease(key);
472			ok = FALSE;
473			return;
474		}
475
476		CFDictionaryRemoveValue(targetPrivate->serverWatchers, key);
477		CFRelease(key);
478
479		n = CFDictionaryGetCount(targetPrivate->serverWatchers);
480
481		if (S_debug) {
482			SCLog(TRUE, LOG_INFO,
483			      CFSTR("<%p>   target %p: watcher removed, c=0x%0llx, n=%d"),
484			      connection,
485			      target,		// server
486			      target_id,	// client
487			      n);
488		}
489
490		if (n == 0) {
491			CFRelease(targetPrivate->serverWatchers);
492			targetPrivate->serverWatchers = NULL;
493
494			ok = SCNetworkReachabilitySetDispatchQueue(target, NULL);
495			if (!ok) {
496				SCLog(TRUE, LOG_ERR,
497				      CFSTR("<%p> target %p: _watcher_remove SCNetworkReachabilitySetDispatchQueue() failed: %s"),
498				      connection,
499				      target,
500				      SCErrorString(SCError()));
501				return;
502			}
503
504			// no more watchers, flags are no longer valid
505			if (_SC_ATOMIC_CMPXCHG(&targetPrivate->serverInfoValid, TRUE, FALSE)) {
506				if (S_debug) {
507					SCLog(TRUE, LOG_INFO, CFSTR("%s  flags are no longer \"valid\""),
508					      targetPrivate->log_prefix);
509				}
510			}
511		}
512
513		xpc_release(connection);
514	});
515
516	return ok;
517}
518
519
520#pragma mark -
521#pragma mark Reachability [RBT] client support
522
523
524typedef struct {
525	rb_node_t		rbn;
526	xpc_connection_t	connection;
527	pid_t			pid;
528	const char		*proc_name;
529	CFMutableDictionaryRef	targets;	// target_id --> SCNetworkReachabilityRef
530} reach_client_t;
531
532
533static int
534_rbt_compare_transaction_nodes(void *context, const void *n1, const void *n2)
535{
536	uint64_t	a = (uintptr_t)((reach_client_t *)n1)->connection;
537	uint64_t	b = (uintptr_t)((reach_client_t *)n2)->connection;
538
539	return (a - b);
540}
541
542
543static int
544_rbt_compare_transaction_key(void *context, const void *n1, const void *key)
545{
546	uint64_t	a = (uintptr_t)((reach_client_t *)n1)->connection;
547	uint64_t	b = *(uintptr_t *)key;
548
549	return (a - b);
550}
551
552
553static rb_tree_t *
554_reach_clients_rbt()
555{
556	static dispatch_once_t		once;
557	static const rb_tree_ops_t	ops = {
558		.rbto_compare_nodes	= _rbt_compare_transaction_nodes,
559		.rbto_compare_key	= _rbt_compare_transaction_key,
560		.rbto_node_offset	= offsetof(reach_client_t, rbn),
561		.rbto_context		= NULL
562	};
563	static rb_tree_t		rbt;
564
565	dispatch_once(&once, ^{
566		rb_tree_init(&rbt, &ops);
567	});
568
569	return &rbt;
570}
571
572
573static dispatch_queue_t
574_reach_clients_rbt_queue()
575{
576	static dispatch_once_t	once;
577	static dispatch_queue_t	q;
578
579	dispatch_once(&once, ^{
580		q = dispatch_queue_create(REACH_SERVICE_NAME ".clients.rbt", NULL);
581	});
582
583	return q;
584}
585
586
587static reach_client_t *
588_reach_client_create(xpc_connection_t connection)
589{
590	reach_client_t	*client;
591
592	client = calloc(1, sizeof(*client));
593	client->connection = connection;
594	client->pid = xpc_connection_get_pid(connection);
595	client->proc_name = NULL;
596	client->targets = CFDictionaryCreateMutable(NULL,
597						    0,
598						    &kCFTypeDictionaryKeyCallBacks,
599						    &kCFTypeDictionaryValueCallBacks);
600
601	return client;
602}
603
604
605static void
606_reach_client_release(reach_client_t *client)
607{
608	if (client->proc_name != NULL) {
609		free((void *)client->proc_name);
610	}
611	CFRelease(client->targets);
612	free(client);
613	return;
614}
615
616
617static void
618_reach_client_remove_target(const void *key, const void *value, void *context)
619{
620	xpc_connection_t		connection	= (xpc_connection_t)context;
621	SCNetworkReachabilityRef	target		= (SCNetworkReachabilityRef)value;
622	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
623
624	// check if we have anyone watching this target
625	if (targetPrivate->serverWatchers != NULL) {
626		CFIndex		n;
627
628		n = CFDictionaryGetCount(targetPrivate->serverWatchers);
629		if (n > 0) {
630			CFIndex		i;
631			CFDictionaryRef	serverWatchers;
632			const void *	watchers_q[32];
633			const void **	watchers	= watchers_q;
634
635			serverWatchers = CFDictionaryCreateCopy(NULL, targetPrivate->serverWatchers);
636
637			if (n > sizeof(watchers_q)/sizeof(watchers[0])) {
638				watchers = CFAllocatorAllocate(NULL, n * sizeof(CFDataRef), 0);
639			}
640			CFDictionaryGetKeysAndValues(serverWatchers, watchers, NULL);
641
642			for (i = 0; i < n; i++) {
643				CFDataRef		key;
644				reach_watcher_key_t	*watcher_key;
645
646				key = (CFDataRef)watchers[i];
647				/* ALIGN: CF aligns to >8 byte boundries */
648				watcher_key = (reach_watcher_key_t *)(void *)CFDataGetBytePtr(key);
649				if (watcher_key->connection == connection) {
650					// remove watcher references for THIS connection
651					_target_watcher_remove(target,
652							       watcher_key->connection,
653							       watcher_key->target_id);
654				}
655			}
656
657			if (watchers != watchers_q) {
658				CFAllocatorDeallocate(NULL, watchers);
659			}
660
661			CFRelease(serverWatchers);
662		}
663	}
664
665	// remove our reference to this target
666	dispatch_sync(_server_digest_queue(), ^{
667		_target_reference_remove(target, connection);
668	});
669
670	return;
671}
672
673
674static void
675_reach_client_remove(xpc_connection_t connection)
676{
677	uint64_t	connection_id	= (uintptr_t)connection;
678
679	dispatch_sync(_reach_clients_rbt_queue(), ^{
680		reach_client_t	*client;
681		rb_tree_t	*rbt	= _reach_clients_rbt();
682
683		client = rb_tree_find_node(rbt, &connection_id);
684		if (client != NULL) {
685			// remove any remaining target references (for this client)
686			my_CFDictionaryApplyFunction(client->targets,
687						     _reach_client_remove_target,
688						     (void *)connection);
689
690			rb_tree_remove_node(rbt, client);
691			_reach_client_release(client);
692		} else {
693			SCLog(TRUE, LOG_ERR,
694			      CFSTR("<%p> _reach_client_remove: unexpected client"),
695			      connection);
696		}
697	});
698
699	return;
700}
701
702
703static __inline__ CFDataRef
704_client_target_key_create(uint64_t target_id)
705{
706	CFDataRef	target_key;
707
708	target_key = CFDataCreate(NULL, (UInt8 *)&target_id, sizeof(target_id));
709	return target_key;
710}
711
712
713static SCNetworkReachabilityRef
714_client_target_copy(reach_client_t *client, uint64_t target_id)
715{
716	SCNetworkReachabilityRef	target;
717	CFDataRef			target_key;
718
719	target_key = _client_target_key_create(target_id);
720	target = CFDictionaryGetValue(client->targets, target_key);
721	CFRelease(target_key);
722
723	if (target != NULL) {
724		CFRetain(target);
725	}
726
727	return target;
728}
729
730
731static Boolean
732_client_target_set(reach_client_t *client, uint64_t target_id, SCNetworkReachabilityRef target)
733{
734	Boolean		added;
735	CFDataRef	target_key;
736
737	target_key = _client_target_key_create(target_id);
738	added = !CFDictionaryContainsKey(client->targets, target_key);
739	if (added) {
740		CFDictionarySetValue(client->targets, target_key, target);
741	}
742	CFRelease(target_key);
743
744	return added;
745}
746
747
748static void
749_client_target_remove(reach_client_t *client, uint64_t target_id)
750{
751	CFDataRef	target_key;
752
753	target_key = _client_target_key_create(target_id);
754	CFDictionaryRemoveValue(client->targets, target_key);
755	CFRelease(target_key);
756
757	return;
758}
759
760
761#pragma mark -
762#pragma mark Reachability [XPC] server functions
763
764
765static dispatch_queue_t
766_reach_server_queue()
767{
768	static dispatch_once_t	once;
769	static dispatch_queue_t	q;
770
771	dispatch_once(&once, ^{
772		q = dispatch_queue_create(REACH_SERVICE_NAME, NULL);
773	});
774
775	return q;
776}
777
778
779/*
780 * _reach_changed
781 *
782 * Note: should be exec'd on the target queue
783 */
784static void
785_reach_changed(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
786{
787	CFIndex				i;
788	CFIndex				n;
789	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
790	const void *			watcher_keys_q[32];
791	const void **			watcher_keys	= watcher_keys_q;
792	const void *			watcher_vals_q[32];
793	const void **			watcher_vals	= watcher_vals_q;
794
795	if (S_debug) {
796		SCLog(TRUE, LOG_INFO,
797		      CFSTR("%sprocess reachability changed, flags = 0x%08x"),
798		      targetPrivate->log_prefix,
799		      flags);
800	}
801
802	if (targetPrivate->serverWatchers == NULL) {
803		// if no watchers
804		return;
805	}
806
807	n = CFDictionaryGetCount(targetPrivate->serverWatchers);
808	if (n == 0) {
809		// if no watchers
810		return;
811	}
812
813	/*
814	 * Because we are actively watching for additional changes
815	 * we mark the flags as "valid"
816	 */
817	if (_SC_ATOMIC_CMPXCHG(&targetPrivate->serverInfoValid, FALSE, TRUE)) {
818		if (S_debug) {
819			SCLog(TRUE, LOG_INFO, CFSTR("%s  flags are now \"valid\""),
820			      targetPrivate->log_prefix);
821		}
822	}
823
824	// notify all of the watchers
825	if (n > sizeof(watcher_keys_q)/sizeof(watcher_keys[0])) {
826		watcher_keys = CFAllocatorAllocate(NULL, n * sizeof(CFDataRef), 0);
827		watcher_vals = CFAllocatorAllocate(NULL, n * sizeof(CFDataRef), 0);
828	}
829
830	CFDictionaryGetKeysAndValues(targetPrivate->serverWatchers,
831				     watcher_keys,
832				     watcher_vals);
833
834	for (i = 0; i < n; i++) {
835		xpc_connection_t	connection;
836		CFDataRef		key;
837		uint64_t		target_id;
838		CFDataRef		val;
839		reach_watcher_key_t	*watcher_key;
840		reach_watcher_val_t	*watcher_val;
841
842		val = (CFDataRef)watcher_vals[i];
843		/* ALIGN: CF aligns to >8 byte boundries */
844		watcher_val = (reach_watcher_val_t *)(void *)CFDataGetBytePtr(val);
845
846		if (_SC_ATOMIC_INC(&watcher_val->n_changes) > 0) {
847			// if we've already sent a notification
848			continue;
849		}
850
851		key = (CFDataRef)watcher_keys[i];
852		/* ALIGN: CF aligns to >8 byte boundries */
853		watcher_key = (reach_watcher_key_t *)(void *)CFDataGetBytePtr(key);
854
855		connection = xpc_retain(watcher_key->connection);
856		target_id  = watcher_key->target_id;
857		dispatch_async(_reach_server_queue(), ^{
858			xpc_object_t	reply;
859
860			// create our [async] notification
861			reply = xpc_dictionary_create(NULL, NULL, 0);
862
863			// set notification
864			xpc_dictionary_set_int64(reply,
865						 MESSAGE_NOTIFY,
866						 MESSAGE_REACHABILITY_STATUS);
867
868			// set target ID
869			xpc_dictionary_set_uint64(reply,
870						  REACH_CLIENT_TARGET_ID,
871						  target_id);
872
873			log_xpc_object("  reply [async]", reply);
874			xpc_connection_send_message(connection, reply);
875
876			xpc_release(reply);
877			xpc_release(connection);
878		});
879	}
880
881	if (n > sizeof(watcher_keys_q)/sizeof(watcher_keys[0])) {
882		CFAllocatorDeallocate(NULL, watcher_keys);
883		CFAllocatorDeallocate(NULL, watcher_vals);
884	}
885
886	return;
887}
888
889
890static void
891sanitize_address(const struct sockaddr *from, struct sockaddr *to)
892{
893	switch (from->sa_family) {
894		case AF_INET : {
895			/* ALIGN: cast okay, alignment not assumed. */
896			struct sockaddr_in *from4	= (struct sockaddr_in *)(void *)from;
897			struct sockaddr_in *to4		= (struct sockaddr_in *)(void *)to;
898
899			bzero(to4, sizeof(*to4));
900			to4->sin_len = sizeof(*to4);
901			to4->sin_family = AF_INET;
902			bcopy(&from4->sin_addr, &to4->sin_addr, sizeof(to4->sin_addr));
903			break;
904		}
905
906		case AF_INET6 : {
907			/* ALIGN: cast okay, alignment not assumed. */
908			struct sockaddr_in6 *from6	= (struct sockaddr_in6 *)(void *)from;
909			struct sockaddr_in6 *to6	= (struct sockaddr_in6 *)(void *)to;
910
911			bzero(to6, sizeof(*to6));
912			to6->sin6_len = sizeof(*to6);
913			to6->sin6_family = AF_INET6;
914			bcopy(&from6->sin6_addr, &to6->sin6_addr, sizeof(to6->sin6_addr));
915			to6->sin6_scope_id = from6->sin6_scope_id;
916			break;
917		}
918
919		default:
920			bcopy(from, to, from->sa_len);
921			break;
922	}
923
924	return;
925}
926
927
928static void
929target_add(reach_client_t *client, xpc_object_t request)
930{
931	const char				*name;
932	const struct sockaddr			*localAddress;
933	struct sockaddr_storage			localAddress0;
934	const struct sockaddr			*remoteAddress;
935	struct sockaddr_storage			remoteAddress0;
936	int64_t					if_index;
937	const char				*if_name	= NULL;
938	bool					onDemandBypass	= FALSE;
939	bool					resolverBypass	= FALSE;
940	uint64_t				target_id;
941
942
943	unsigned char				bytes[CC_SHA1_DIGEST_LENGTH];
944	CC_SHA1_CTX				ctx;
945	CFDataRef				digest		= NULL;
946	size_t					len;
947	xpc_connection_t			remote;
948	xpc_object_t				reply;
949	uint64_t				status		= REACH_REQUEST_REPLY_FAILED;
950
951	Boolean					added;
952	__block Boolean				ok		= TRUE;
953	__block SCNetworkReachabilityRef	target		= NULL;
954
955	if (S_debug) {
956		SCLog(TRUE, LOG_INFO,
957		      CFSTR("<%p> create reachability target"),
958		      client->connection);
959//		log_xpc_object("  create", request);
960	}
961
962	remote = xpc_dictionary_get_remote_connection(request);
963	reply = xpc_dictionary_create_reply(request);
964	if (reply == NULL) {
965		SCLog(TRUE, LOG_ERR,
966		      CFSTR("<%p> target_add: xpc_dictionary_create_reply: failed"),
967		      client->connection);
968		return;
969	}
970
971	target_id = xpc_dictionary_get_uint64(request, REACH_CLIENT_TARGET_ID);
972	if (target_id == 0) {
973		xpc_dictionary_set_string(reply,
974					  REACH_REQUEST_REPLY_DETAIL,
975					  "no target ID");
976		goto done;
977	}
978
979	// create a "digest" of the [new] target
980
981	CC_SHA1_Init(&ctx);
982
983	name = xpc_dictionary_get_string(request, REACH_TARGET_NAME);
984	if (name != NULL) {
985		CC_SHA1_Update(&ctx, REACH_TARGET_NAME, sizeof(REACH_TARGET_NAME));
986		CC_SHA1_Update(&ctx, name, strlen(name));
987	}
988
989	localAddress = xpc_dictionary_get_data(request, REACH_TARGET_LOCAL_ADDR, &len);
990	if (localAddress != NULL) {
991		if ((len == localAddress->sa_len) && (len <= sizeof(struct sockaddr_storage))) {
992			sanitize_address(localAddress, (struct sockaddr *)&localAddress0);
993			CC_SHA1_Update(&ctx, REACH_TARGET_LOCAL_ADDR, sizeof(REACH_TARGET_LOCAL_ADDR));
994			CC_SHA1_Update(&ctx, &localAddress0, len);
995		} else {
996			xpc_dictionary_set_string(reply,
997						  REACH_REQUEST_REPLY_DETAIL,
998						  "local address: size error");
999			goto done;
1000		}
1001	}
1002
1003	remoteAddress = xpc_dictionary_get_data(request, REACH_TARGET_REMOTE_ADDR, &len);
1004	if (remoteAddress != NULL) {
1005		if ((len == remoteAddress->sa_len) && (len <= sizeof(struct sockaddr_storage))) {
1006			sanitize_address(remoteAddress, (struct sockaddr *)&remoteAddress0);
1007			CC_SHA1_Update(&ctx, REACH_TARGET_REMOTE_ADDR, sizeof(REACH_TARGET_REMOTE_ADDR));
1008			CC_SHA1_Update(&ctx, &remoteAddress0, len);
1009		} else {
1010			xpc_dictionary_set_string(reply,
1011						  REACH_REQUEST_REPLY_DETAIL,
1012						  "remote address: size error");
1013			goto done;
1014		}
1015	}
1016
1017	if_index = xpc_dictionary_get_int64(request, REACH_TARGET_IF_INDEX);
1018	if (if_index != 0) {
1019		if_name = xpc_dictionary_get_string(request, REACH_TARGET_IF_NAME);
1020		if (if_name != NULL) {
1021			CC_SHA1_Update(&ctx, REACH_TARGET_IF_NAME, sizeof(REACH_TARGET_IF_NAME));
1022			CC_SHA1_Update(&ctx, if_name, strlen(if_name));
1023		}
1024	}
1025
1026
1027	onDemandBypass = xpc_dictionary_get_bool(request, REACH_TARGET_ONDEMAND_BYPASS);
1028	if (onDemandBypass) {
1029		CC_SHA1_Update(&ctx, REACH_TARGET_ONDEMAND_BYPASS, sizeof(REACH_TARGET_ONDEMAND_BYPASS));
1030		CC_SHA1_Update(&ctx, &onDemandBypass, sizeof(onDemandBypass));
1031	}
1032
1033	resolverBypass = xpc_dictionary_get_bool(request, REACH_TARGET_RESOLVER_BYPASS);
1034	if (resolverBypass) {
1035		CC_SHA1_Update(&ctx, REACH_TARGET_RESOLVER_BYPASS, sizeof(REACH_TARGET_RESOLVER_BYPASS));
1036		CC_SHA1_Update(&ctx, &resolverBypass, sizeof(resolverBypass));
1037	}
1038
1039
1040	CC_SHA1_Final(bytes, &ctx);
1041	digest = CFDataCreate(NULL, bytes, sizeof(bytes));
1042
1043	/*
1044	 * Check to see if we already have a SCNetworkReachability object
1045	 * for this digest. If so, we'll share the existing target. If not,
1046	 * create a new [shared] target.
1047	 */
1048	dispatch_sync(_server_digest_queue(), ^{
1049		target = CFDictionaryGetValue(reach_digest_map, digest);
1050		if (target != NULL) {
1051			CFRetain(target);
1052		} else {
1053			CFDataRef			data;
1054			CFMutableDictionaryRef		options;
1055			CFStringRef			str;
1056
1057			options = CFDictionaryCreateMutable(NULL,
1058							    0,
1059							    &kCFTypeDictionaryKeyCallBacks,
1060							    &kCFTypeDictionaryValueCallBacks);
1061			if (name != NULL) {
1062				str = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
1063				CFDictionarySetValue(options, kSCNetworkReachabilityOptionNodeName, str);
1064				CFRelease(str);
1065			}
1066			if (localAddress != NULL) {
1067				data = CFDataCreate(NULL, (const UInt8 *)&localAddress0, localAddress0.ss_len);
1068				CFDictionarySetValue(options, kSCNetworkReachabilityOptionLocalAddress, data);
1069				CFRelease(data);
1070			}
1071			if (remoteAddress != NULL) {
1072				data = CFDataCreate(NULL, (const UInt8 *)&remoteAddress0, remoteAddress0.ss_len);
1073				CFDictionarySetValue(options, kSCNetworkReachabilityOptionRemoteAddress, data);
1074				CFRelease(data);
1075			}
1076			if (onDemandBypass) {
1077				CFDictionarySetValue(options,
1078						     kSCNetworkReachabilityOptionConnectionOnDemandBypass,
1079						     kCFBooleanTrue);
1080			}
1081			if (resolverBypass) {
1082				CFDictionarySetValue(options,
1083						     kSCNetworkReachabilityOptionResolverBypass,
1084						     kCFBooleanTrue);
1085			}
1086			CFDictionarySetValue(options,
1087					     kSCNetworkReachabilityOptionServerBypass,
1088					     kCFBooleanTrue);
1089			target = SCNetworkReachabilityCreateWithOptions(NULL, options);
1090			CFRelease(options);
1091			if (target == NULL) {
1092				xpc_dictionary_set_string(reply,
1093							  REACH_REQUEST_REPLY_DETAIL,
1094							  "SCNetworkReachabilityCreateWithOptions failed");
1095				ok = FALSE;
1096				return;
1097			}
1098
1099			// because the interface name may not (no longer) be valid we set
1100			// this after we've created the SCNetworkReachabilty object
1101			if ((if_index != 0) && (if_name != NULL)) {
1102				SCNetworkReachabilityPrivateRef	targetPrivate;
1103
1104				targetPrivate = (SCNetworkReachabilityPrivateRef)target;
1105				targetPrivate->if_index = if_index;
1106				strlcpy(targetPrivate->if_name, if_name, sizeof(targetPrivate->if_name));
1107			}
1108
1109
1110			ok = SCNetworkReachabilitySetCallback(target, _reach_changed, NULL);
1111			if (!ok) {
1112				xpc_dictionary_set_string(reply,
1113							  REACH_REQUEST_REPLY_DETAIL,
1114							  "SCNetworkReachabilitySetCallback failed");
1115				CFRelease(target);
1116				target = NULL;
1117				return;
1118			}
1119		}
1120
1121		// bump the number of references to this target
1122		_target_reference_add(target, digest, client->connection);
1123	});
1124
1125	if (!ok) {
1126		goto done;
1127	}
1128
1129	/*
1130	 * add an association for the client's target_id to the [shared]
1131	 * SCNetworkReachability object.
1132	 */
1133	added = _client_target_set(client, target_id, target);
1134	if (!added) {
1135		// if we already had a reference to the target (e.g. reconnect)
1136		dispatch_sync(_server_digest_queue(), ^{
1137			_target_reference_remove(target, client->connection);
1138		});
1139	}
1140
1141	status = REACH_REQUEST_REPLY_OK;
1142
1143    done :
1144
1145	xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, status);
1146//	log_xpc_object("  reply", reply);
1147	xpc_connection_send_message(remote, reply);
1148	xpc_release(reply);
1149
1150	if (digest != NULL) CFRelease(digest);
1151	if (target != NULL) CFRelease(target);
1152	return;
1153}
1154
1155
1156static void
1157target_remove(reach_client_t *client, xpc_object_t request)
1158{
1159	xpc_connection_t		remote;
1160	xpc_object_t			reply;
1161	uint64_t			status		= REACH_REQUEST_REPLY_FAILED;
1162	SCNetworkReachabilityRef	target		= NULL;
1163	uint64_t			target_id;
1164
1165	if (S_debug) {
1166		SCLog(TRUE, LOG_INFO,
1167		      CFSTR("<%p> remove reachability target"),
1168		      client->connection);
1169//		log_xpc_object("  remove", request);
1170	}
1171
1172	remote = xpc_dictionary_get_remote_connection(request);
1173	reply = xpc_dictionary_create_reply(request);
1174	if (reply == NULL) {
1175		SCLog(TRUE, LOG_ERR,
1176		      CFSTR("<%p> target_remove: xpc_dictionary_create_reply: failed"),
1177		      client->connection);
1178		return;
1179	}
1180
1181	target_id = xpc_dictionary_get_uint64(request, REACH_CLIENT_TARGET_ID);
1182	if (target_id == 0) {
1183		xpc_dictionary_set_string(reply,
1184					  REACH_REQUEST_REPLY_DETAIL,
1185					  "no target ID");
1186		goto done;
1187	}
1188
1189	target = _client_target_copy(client, target_id);
1190	if (target == NULL) {
1191		xpc_dictionary_set_string(reply,
1192					  REACH_REQUEST_REPLY_DETAIL,
1193					  "no target");
1194		status = REACH_REQUEST_REPLY_UNKNOWN;
1195		goto done;
1196	}
1197
1198	/*
1199	 * remove the association from the client's target_id to the [shared]
1200	 * SCNetworkReachability object.
1201	 */
1202	_client_target_remove(client, target_id);
1203
1204	// drop the number of references to this target
1205	dispatch_sync(_server_digest_queue(), ^{
1206		_target_reference_remove(target, client->connection);
1207	});
1208
1209	status = REACH_REQUEST_REPLY_OK;
1210
1211    done :
1212
1213	xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, status);
1214//	log_xpc_object("  reply", reply);
1215	xpc_connection_send_message(remote, reply);
1216	xpc_release(reply);
1217
1218	if (target != NULL) CFRelease(target);
1219	return;
1220}
1221
1222
1223static void
1224target_schedule(reach_client_t *client, xpc_object_t request)
1225{
1226	Boolean				ok;
1227	xpc_connection_t		remote;
1228	xpc_object_t			reply;
1229	uint64_t			status		= REACH_REQUEST_REPLY_FAILED;
1230	SCNetworkReachabilityRef	target		= NULL;
1231	uint64_t			target_id;
1232
1233	if (S_debug) {
1234		SCLog(TRUE, LOG_INFO,
1235		      CFSTR("<%p> schedule reachability target"),
1236		      client->connection);
1237//		log_xpc_object("  schedule", request);
1238	}
1239
1240	remote = xpc_dictionary_get_remote_connection(request);
1241	reply = xpc_dictionary_create_reply(request);
1242	if (reply == NULL) {
1243		SCLog(TRUE, LOG_ERR,
1244		      CFSTR("<%p> target_schedule: xpc_dictionary_create_reply: failed"),
1245		      client->connection);
1246		return;
1247	}
1248
1249	target_id = xpc_dictionary_get_uint64(request, REACH_CLIENT_TARGET_ID);
1250	if (target_id == 0) {
1251		xpc_dictionary_set_string(reply,
1252					  REACH_REQUEST_REPLY_DETAIL,
1253					  "no target ID");
1254		goto done;
1255	}
1256
1257	target = _client_target_copy(client, target_id);
1258	if (target == NULL) {
1259		xpc_dictionary_set_string(reply,
1260					  REACH_REQUEST_REPLY_DETAIL,
1261					  "no target");
1262		status = REACH_REQUEST_REPLY_UNKNOWN;
1263		goto done;
1264	}
1265
1266	// enable monitoring
1267	ok = _target_watcher_add(target, client->connection, target_id);
1268	if (!ok) {
1269		xpc_dictionary_set_string(reply,
1270					  REACH_REQUEST_REPLY_DETAIL,
1271					  "could not add watcher");
1272		goto done;
1273	}
1274
1275	status = REACH_REQUEST_REPLY_OK;
1276
1277    done :
1278
1279	xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, status);
1280//	log_xpc_object("  reply", reply);
1281	xpc_connection_send_message(remote, reply);
1282	xpc_release(reply);
1283
1284	if (target != NULL) CFRelease(target);
1285	return;
1286}
1287
1288
1289static void
1290target_status(reach_client_t *client, xpc_object_t request)
1291{
1292	xpc_connection_t		remote;
1293	xpc_object_t			reply;
1294	__block Boolean			reply_now	= TRUE;
1295	Boolean				scheduled;
1296	uint64_t			status		= REACH_REQUEST_REPLY_FAILED;
1297	SCNetworkReachabilityRef	target		= NULL;
1298	uint64_t			target_id;
1299
1300	if(S_debug) {
1301		SCLog(TRUE, LOG_INFO,
1302		      CFSTR("<%p> get status of reachability target"),
1303		      client->connection);
1304//		log_xpc_object("  status", request);
1305	}
1306
1307	remote = xpc_dictionary_get_remote_connection(request);
1308	reply = xpc_dictionary_create_reply(request);
1309	if (reply == NULL) {
1310		SCLog(TRUE, LOG_ERR,
1311		      CFSTR("<%p> target_status: xpc_dictionary_create_reply: failed"),
1312		      client->connection);
1313		return;
1314	}
1315
1316	target_id = xpc_dictionary_get_uint64(request, REACH_CLIENT_TARGET_ID);
1317	if (target_id == 0) {
1318		SCLog(TRUE, LOG_ERR,
1319		      CFSTR("<%p>   target_status: no target"),
1320		      client->connection);
1321		xpc_dictionary_set_string(reply,
1322					  REACH_REQUEST_REPLY_DETAIL,
1323					  "no target ID");
1324		goto done;
1325	}
1326
1327	target = _client_target_copy(client, target_id);
1328	if (target == NULL) {
1329		SCLog(TRUE, LOG_ERR,
1330		      CFSTR("<%p>   target_status: no target (0x%0llx)"),
1331		      client->connection,
1332		      target_id);
1333		xpc_dictionary_set_string(reply,
1334					  REACH_REQUEST_REPLY_DETAIL,
1335					  "no target");
1336		status = REACH_REQUEST_REPLY_UNKNOWN;
1337		goto done;
1338	}
1339
1340	/*
1341	 * Check to see if the target [for this client] had been "scheduled".
1342	 *
1343	 * If so, also mark that we've picked up the current reachability
1344	 * flags and that any pending notifications have been processed.
1345	 */
1346	scheduled = _target_watcher_checkin(target, client->connection, target_id);
1347
1348	/*
1349	 * return current reachability information to the caller
1350	 */
1351	dispatch_sync(_target_queue(target), ^{
1352		SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
1353
1354		if (scheduled) {
1355			/*
1356			 * The client "scheduled" this target.  As such, we
1357			 * know that this is an async query and that we only
1358			 * need to return the "last known" flags.
1359			 */
1360			_target_reply_add_reachability(target, reply);
1361//			log_xpc_object("  reply [scheduled]", reply);
1362
1363			/*
1364			 * ... and if it's not a "name" query then we can mark the
1365			 * flags as valid.
1366			 */
1367			if (targetPrivate->type != reachabilityTypeName) {
1368				if (_SC_ATOMIC_CMPXCHG(&targetPrivate->serverInfoValid, FALSE, TRUE)) {
1369					if (S_debug) {
1370						SCLog(TRUE, LOG_INFO, CFSTR("%s  flags are now \"valid\"."),
1371						      targetPrivate->log_prefix);
1372					}
1373				}
1374			}
1375
1376			if (S_debug) {
1377				CFStringRef	str;
1378
1379				str = _SCNetworkReachabilityCopyTargetFlags(target);
1380				SCLog(TRUE, LOG_INFO,
1381				      CFSTR("<%p>   reply [scheduled%s], %@"),
1382				      client->connection,
1383				      targetPrivate->serverInfoValid ? "/valid" : "",
1384				      str);
1385				CFRelease(str);
1386			}
1387		} else {
1388			/*
1389			 * The client has NOT "scheduled" this target.  As
1390			 * such, we know that this is a sync query and that
1391			 * we must return "current" flags.
1392			 */
1393			if (targetPrivate->scheduled && targetPrivate->serverInfoValid) {
1394				/*
1395				 * The server target has been "scheduled" and we
1396				 * have flags that are "current".
1397				 */
1398				_target_reply_add_reachability(target, reply);
1399//				log_xpc_object("  reply [scheduled/valid]", reply);
1400
1401				if (S_debug) {
1402					CFStringRef	str;
1403
1404					str = _SCNetworkReachabilityCopyTargetFlags(target);
1405					SCLog(TRUE, LOG_INFO,
1406					      CFSTR("<%p>   reply [scheduled/valid], %@"),
1407					      client->connection,
1408					      str);
1409					CFRelease(str);
1410				}
1411			} else {
1412				dispatch_group_t	group;
1413
1414				/*
1415				 * The server target has NOT been "scheduled" or
1416				 * we do not have "current" flags.  This means that
1417				 * we must query for the current information and
1418				 * return the flags to the client when they are
1419				 * available.
1420				 */
1421
1422				reply_now = FALSE;
1423
1424				group = _target_group(target);
1425				if (_SC_ATOMIC_INC(&targetPrivate->serverSyncQueryActive) == 0) {
1426					CFRetain(target);
1427					dispatch_group_async(group, _server_concurrent_queue(), ^{
1428						SCNetworkReachabilityFlags	flags;
1429						unsigned int			n;
1430						Boolean				ok;
1431
1432						// query for the flags
1433						ok = SCNetworkReachabilityGetFlags(target, &flags);
1434						flags = targetPrivate->info.flags;	// get the "raw" flags
1435						if (!ok) {
1436							SCLog(TRUE, LOG_ERR,
1437							      CFSTR("SCNetworkReachabilityGetFlags() [sync query] failed"
1438								    "\n  target = %@"
1439								    "\n  status = %s"),
1440							      target,
1441							      SCErrorString(SCError()));
1442						}
1443
1444						/*
1445						 * if we have current flags, if the target has since been
1446						 * scheduled, and this is not a "name" query, then mark as
1447						 * valid.
1448						 */
1449						if (ok &&
1450						    targetPrivate->scheduled &&
1451						    targetPrivate->type != reachabilityTypeName) {
1452							if (_SC_ATOMIC_CMPXCHG(&targetPrivate->serverInfoValid, FALSE, TRUE)) {
1453								if (S_debug) {
1454									SCLog(TRUE, LOG_INFO, CFSTR("%s  flags are now \"valid\"!"),
1455									      targetPrivate->log_prefix);
1456								}
1457							}
1458						}
1459
1460						// sync query complete
1461						n = _SC_ATOMIC_ZERO(&targetPrivate->serverSyncQueryActive);
1462						if (S_debug) {
1463							SCLog(TRUE, LOG_INFO,
1464							      CFSTR("%sSCNetworkReachabilityGetFlags() [sync query] complete, n = %d"),
1465							      targetPrivate->log_prefix,
1466							      n);
1467						}
1468
1469						CFRelease(target);
1470					});
1471				}
1472
1473				CFRetain(target);
1474				dispatch_group_notify(group, _target_queue(target), ^{
1475					// flags are now available
1476					_target_reply_add_reachability(target, reply);
1477					xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, REACH_REQUEST_REPLY_OK);
1478//					log_xpc_object("  reply [delayed]", reply);
1479
1480					if (S_debug) {
1481						CFStringRef	str;
1482
1483						str = _SCNetworkReachabilityCopyTargetFlags(target);
1484						SCLog(TRUE, LOG_INFO,
1485						      CFSTR("<%p>   reply [delayed], %@"),
1486						      client->connection,
1487						      str);
1488						CFRelease(str);
1489					}
1490
1491					xpc_connection_send_message(remote, reply);
1492					xpc_release(reply);
1493
1494					CFRelease(target);
1495				});
1496			}
1497		}
1498	});
1499
1500	status = REACH_REQUEST_REPLY_OK;
1501
1502    done :
1503
1504	if (reply_now) {
1505		xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, status);
1506
1507		if (status != REACH_REQUEST_REPLY_OK) {
1508//			log_xpc_object("  reply [!]", reply);
1509
1510			if (S_debug) {
1511				SCLog(TRUE, LOG_INFO,
1512				      CFSTR("<%p>   reply [!]"),
1513				      client->connection);
1514			}
1515		}
1516
1517		xpc_connection_send_message(remote, reply);
1518		xpc_release(reply);
1519	} else if (S_debug) {
1520		CFStringRef	str;
1521
1522		str = _SCNetworkReachabilityCopyTargetFlags(target);
1523		SCLog(TRUE, LOG_INFO,
1524		      CFSTR("<%p>   no reply [yet], %@"),
1525		      client->connection,
1526		      str);
1527		CFRelease(str);
1528	}
1529
1530	if (target != NULL) CFRelease(target);
1531	return;
1532}
1533
1534
1535static void
1536target_unschedule(reach_client_t *client, xpc_object_t request)
1537{
1538	xpc_connection_t		remote;
1539	xpc_object_t			reply;
1540	uint64_t			status		= REACH_REQUEST_REPLY_FAILED;
1541	SCNetworkReachabilityRef	target		= NULL;
1542	uint64_t			target_id;
1543
1544	if (S_debug) {
1545		SCLog(TRUE, LOG_INFO,
1546		      CFSTR("<%p> unschedule reachability target"),
1547		      client->connection);
1548//		log_xpc_object("  unschedule", request);
1549	}
1550
1551	remote = xpc_dictionary_get_remote_connection(request);
1552	reply = xpc_dictionary_create_reply(request);
1553	if (reply == NULL) {
1554		SCLog(TRUE, LOG_ERR,
1555		      CFSTR("<%p> target_unschedule: xpc_dictionary_create_reply: failed"),
1556		      client->connection);
1557		return;
1558	}
1559
1560	target_id = xpc_dictionary_get_uint64(request, REACH_CLIENT_TARGET_ID);
1561	if (target_id == 0) {
1562		xpc_dictionary_set_string(reply,
1563					  REACH_REQUEST_REPLY_DETAIL,
1564					  "no target ID");
1565		goto done;
1566	}
1567
1568	target = _client_target_copy(client, target_id);
1569	if (target == NULL) {
1570		xpc_dictionary_set_string(reply,
1571					  REACH_REQUEST_REPLY_DETAIL,
1572					  "no target");
1573		status = REACH_REQUEST_REPLY_UNKNOWN;
1574		goto done;
1575	}
1576
1577	// disable monitoring
1578	_target_watcher_remove(target, client->connection, target_id);
1579
1580	status = REACH_REQUEST_REPLY_OK;
1581
1582    done :
1583
1584	xpc_dictionary_set_int64(reply, REACH_REQUEST_REPLY, status);
1585//	log_xpc_object("  reply", reply);
1586	xpc_connection_send_message(remote, reply);
1587	xpc_release(reply);
1588
1589	if (target != NULL) CFRelease(target);
1590	return;
1591}
1592
1593
1594#define	SNAPSHOT_PATH_STATE	_PATH_VARTMP "configd-reachability"
1595
1596
1597static void
1598_snapshot_digest_watcher(const void *key, const void *value, void *context)
1599{
1600	__block reach_client_t	*client		= NULL;
1601	FILE			*f		= (FILE *)context;
1602	static reach_client_t	no_client	= {
1603		.pid = 0,
1604		.proc_name = "?",
1605	};
1606	reach_watcher_key_t	*watcher_key;
1607	reach_watcher_val_t	*watcher_val;
1608
1609	/* ALIGN: CF aligns to >8 byte boundries */
1610	watcher_key = (reach_watcher_key_t *)(void *)CFDataGetBytePtr(key);
1611	watcher_val = (reach_watcher_val_t *)(void *)CFDataGetBytePtr(value);
1612
1613	dispatch_sync(_reach_clients_rbt_queue(), ^{
1614		uint64_t	connection_id	= (uintptr_t)watcher_key->connection;
1615		rb_tree_t	*rbt		= _reach_clients_rbt();
1616
1617		client = rb_tree_find_node(rbt, &connection_id);
1618		if (client == NULL) {
1619			client = &no_client;
1620		}
1621	});
1622
1623	SCPrint(TRUE, f,
1624		CFSTR("      connection = %p, target(c) = 0x%0llx, command = %s, pid = %d, changes = %u\n"),
1625		watcher_key->connection,
1626		watcher_key->target_id,
1627		client->proc_name,
1628		client->pid,
1629		watcher_val->n_changes);
1630
1631	return;
1632}
1633
1634
1635static void
1636_snapshot_digest(const void *key, const void *value, void *context)
1637{
1638	FILE				*f		= (FILE *)context;
1639	CFStringRef			digest		= (CFStringRef)key;
1640	dispatch_queue_t		q;
1641	SCNetworkReachabilityRef	target		= (SCNetworkReachabilityRef)value;
1642	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
1643
1644	q = _target_queue(target);
1645	dispatch_sync(q, ^{
1646		CFIndex		nWatchers	= 0;
1647
1648		if (targetPrivate->serverWatchers != NULL) {
1649			nWatchers = CFDictionaryGetCount(targetPrivate->serverWatchers);
1650		}
1651
1652		SCPrint(TRUE, f, CFSTR("\n  digest : %@\n"), digest);
1653		SCPrint(TRUE, f, CFSTR("    %@\n"), target);
1654		SCPrint(TRUE, f, CFSTR("    valid = %s, async watchers = %u, sync queries = %u, refs = %u\n"),
1655			targetPrivate->serverInfoValid ? "Y" : "N",
1656			nWatchers,
1657			targetPrivate->serverSyncQueryActive,
1658			targetPrivate->serverReferences);
1659
1660		SCPrint(TRUE, f, CFSTR("    network %d.%3.3d"),
1661			targetPrivate->last_network.tv_sec,
1662			targetPrivate->last_network.tv_usec / 1000);
1663#if	!TARGET_OS_IPHONE
1664		SCPrint(TRUE, f, CFSTR(", power %d.%3.3d"),
1665			targetPrivate->last_power.tv_sec,
1666			targetPrivate->last_power.tv_usec / 1000);
1667#endif	// !TARGET_OS_IPHONE
1668		if (targetPrivate->type == reachabilityTypeName) {
1669			SCPrint(TRUE, f, CFSTR(", DNS %d.%3.3d"),
1670				targetPrivate->last_dns.tv_sec,
1671				targetPrivate->last_dns.tv_usec / 1000);
1672			if (timerisset(&targetPrivate->dnsQueryEnd)) {
1673				struct timeval	dnsQueryElapsed;
1674
1675				timersub(&targetPrivate->dnsQueryEnd,
1676					 &targetPrivate->dnsQueryStart,
1677					 &dnsQueryElapsed);
1678				SCPrint(TRUE, f, CFSTR(" (query %d.%3.3d / reply %d.%3.3d)"),
1679					targetPrivate->dnsQueryStart.tv_sec,
1680					targetPrivate->dnsQueryStart.tv_usec / 1000,
1681					dnsQueryElapsed.tv_sec,
1682					dnsQueryElapsed.tv_usec / 1000);
1683			}
1684		}
1685		if (timerisset(&targetPrivate->last_push)) {
1686			SCPrint(TRUE, f, CFSTR(", last notify %d.%3.3d"),
1687				targetPrivate->last_push.tv_sec,
1688				targetPrivate->last_push.tv_usec / 1000);
1689		}
1690		SCPrint(TRUE, f, CFSTR("\n"));
1691
1692		if (nWatchers > 0) {
1693			CFDictionaryApplyFunction(targetPrivate->serverWatchers,
1694						  _snapshot_digest_watcher,
1695						  f);
1696		}
1697	});
1698
1699	return;
1700}
1701
1702
1703static void
1704_snapshot_target(const void *key, const void *value, void *context)
1705{
1706	FILE				*f		= (FILE *)context;
1707	SCNetworkReachabilityRef	target		= (SCNetworkReachabilityRef)value;
1708	uint64_t			target_id;
1709	CFDataRef			target_key	= (CFDataRef)key;
1710
1711	/* ALIGN: CF aligns > 8 byte boundries */
1712	target_id = *(uint64_t *)(void *)CFDataGetBytePtr(target_key);
1713
1714	SCPrint(TRUE, f,
1715		CFSTR("    target(c) = 0x%0llx, target(s) = %@\n"),
1716		target_id,
1717		target);
1718
1719	return;
1720}
1721
1722
1723static void
1724_snapshot(reach_client_t *client, xpc_object_t request)
1725{
1726	uid_t			euid;
1727	FILE			*f;
1728	int			fd;
1729	Boolean			ok	= FALSE;
1730	xpc_connection_t	remote;
1731	xpc_object_t		reply;
1732
1733	if (S_debug) {
1734		SCLog(TRUE, LOG_INFO,
1735		      CFSTR("<%p> snapshot"),
1736		      client->connection);
1737//		log_xpc_object("  create", request);
1738	}
1739
1740	remote = xpc_dictionary_get_remote_connection(request);
1741	reply = xpc_dictionary_create_reply(request);
1742	if (reply == NULL) {
1743		SCLog(TRUE, LOG_ERR,
1744		      CFSTR("<%p> _snapshot: xpc_dictionary_create_reply: failed"),
1745		      client->connection);
1746		return;
1747	}
1748
1749	euid = xpc_connection_get_euid(remote);
1750	if (euid != 0) {
1751		xpc_dictionary_set_string(reply,
1752					  REACH_REQUEST_REPLY_DETAIL,
1753					  "Permission denied.");
1754		goto done;
1755	}
1756
1757	// Save a snapshot of the SCNetworkReachability server "state"
1758
1759	(void) unlink(SNAPSHOT_PATH_STATE);
1760	fd = open(SNAPSHOT_PATH_STATE, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644);
1761	if (fd == -1) {
1762		xpc_dictionary_set_string(reply,
1763					  REACH_REQUEST_REPLY_DETAIL,
1764					  "open: failed");
1765		goto done;
1766	}
1767	f = fdopen(fd, "w");
1768	if (f == NULL) {
1769		xpc_dictionary_set_string(reply,
1770					  REACH_REQUEST_REPLY_DETAIL,
1771					  "fdopen: failed");
1772		goto done;
1773	}
1774
1775	// provide connection/client info
1776
1777	dispatch_sync(_reach_clients_rbt_queue(), ^{
1778		rb_tree_t	*rbt	= _reach_clients_rbt();
1779
1780		SCPrint(TRUE, f, CFSTR("Clients :\n"));
1781
1782		if (rb_tree_count(rbt) > 0) {
1783			reach_client_t	*client;
1784
1785			RB_TREE_FOREACH(client, rbt) {
1786				SCPrint(TRUE, f,
1787					CFSTR("\n  connection = %p, client = %p, command = %s, pid = %d\n"),
1788					client->connection,
1789					client,
1790					client->proc_name != NULL ? client->proc_name : "?",
1791					client->pid);
1792				my_CFDictionaryApplyFunction(client->targets,
1793							     _snapshot_target,
1794							     f);
1795			}
1796		} else {
1797			SCPrint(TRUE, f, CFSTR("  None.\n"));
1798		}
1799
1800		SCPrint(TRUE, f, CFSTR("\n"));
1801	});
1802
1803	// provide "digest" info
1804
1805	SCPrint(TRUE, f, CFSTR("Digests :\n"));
1806	dispatch_sync(_server_digest_queue(), ^{
1807		if (reach_digest_map != NULL) {
1808			CFDictionaryApplyFunction(reach_digest_map,
1809						  _snapshot_digest,
1810						  f);
1811		}
1812	});
1813
1814	(void) fclose(f);
1815
1816	ok = TRUE;
1817
1818    done :
1819
1820	xpc_dictionary_set_int64(reply,
1821				 REACH_REQUEST_REPLY,
1822				 ok ? REACH_REQUEST_REPLY_OK : REACH_REQUEST_REPLY_FAILED);
1823//	log_xpc_object("  reply", reply);
1824	xpc_connection_send_message(remote, reply);
1825	xpc_release(reply);
1826
1827	return;
1828}
1829
1830
1831static __inline__ void
1832_extract_client_info(reach_client_t *client, xpc_object_t request)
1833{
1834	// if available/needed, save the process name
1835	if (client->proc_name == NULL) {
1836		const char	*proc_name;
1837
1838		proc_name = xpc_dictionary_get_string(request, REACH_CLIENT_PROC_NAME);
1839		if (proc_name != NULL) {
1840			client->proc_name = strdup(proc_name);
1841		}
1842	}
1843
1844	return;
1845}
1846
1847
1848static void
1849process_request(reach_client_t *client, xpc_object_t request)
1850{
1851	int64_t		op;
1852
1853	op = xpc_dictionary_get_int64(request, REACH_REQUEST);
1854	switch (op) {
1855		case REACH_REQUEST_CREATE :
1856			_extract_client_info(client, request);
1857			target_add(client, request);
1858			break;
1859		case REACH_REQUEST_REMOVE :
1860			target_remove(client, request);
1861			break;
1862		case REACH_REQUEST_STATUS :
1863			target_status(client, request);
1864			break;
1865		case REACH_REQUEST_SCHEDULE :
1866			target_schedule(client, request);
1867			break;
1868		case REACH_REQUEST_UNSCHEDULE :
1869			target_unschedule(client, request);
1870			break;
1871		case REACH_REQUEST_SNAPSHOT :
1872			_extract_client_info(client, request);
1873			_snapshot(client, request);
1874			break;
1875		default :
1876			SCLog(TRUE, LOG_ERR,
1877			      CFSTR("<%p> unknown request : %d"),
1878			      client->connection,
1879			      op);
1880			break;
1881	}
1882
1883	return;
1884}
1885
1886
1887static void
1888process_new_connection(xpc_connection_t connection)
1889{
1890	reach_client_t	*client;
1891
1892	if (S_debug) {
1893		SCLog(TRUE, LOG_INFO, CFSTR("<%p> new reach client, pid=%d"),
1894		      connection,
1895		      xpc_connection_get_pid(connection));
1896	}
1897
1898	client = _reach_client_create(connection);
1899	assert(client != NULL);
1900
1901	dispatch_sync(_reach_clients_rbt_queue(), ^{
1902		rb_tree_t	*rbt	= _reach_clients_rbt();
1903
1904		rb_tree_insert_node(rbt, client);
1905	});
1906
1907	xpc_connection_set_target_queue(connection, _reach_server_queue());
1908
1909	xpc_connection_set_event_handler(connection, ^(xpc_object_t xobj) {
1910		xpc_type_t	type;
1911
1912		type = xpc_get_type(xobj);
1913		if (type == XPC_TYPE_DICTIONARY) {
1914			__block reach_client_t	*client	= NULL;
1915
1916			dispatch_sync(_reach_clients_rbt_queue(), ^{
1917				uint64_t	connection_id	= (uintptr_t)connection;
1918				rb_tree_t	*rbt		= _reach_clients_rbt();
1919
1920				client = rb_tree_find_node(rbt, &connection_id);
1921			});
1922
1923			if (client != NULL) {
1924				// process the request
1925				process_request(client, xobj);
1926			} else {
1927				char		*desc;
1928
1929				SCLog(TRUE, LOG_ERR,
1930				      CFSTR("<%p:%d> unexpected SCNetworkReachability request"),
1931				      connection,
1932				      xpc_connection_get_pid(connection));
1933
1934				desc = xpc_copy_description(xobj);
1935				SCLog(TRUE, LOG_ERR,
1936				      CFSTR("  request = %s"),
1937				      desc);
1938				free(desc);
1939
1940				xpc_connection_cancel(connection);
1941			}
1942
1943		} else if (type == XPC_TYPE_ERROR) {
1944			const char	*desc;
1945
1946			desc = xpc_dictionary_get_string(xobj, XPC_ERROR_KEY_DESCRIPTION);
1947			if (xobj == XPC_ERROR_CONNECTION_INVALID) {
1948				if (S_debug) {
1949					SCLog(TRUE, LOG_INFO,
1950					      CFSTR("<%p:%d> %s"),
1951					      connection,
1952					      xpc_connection_get_pid(connection),
1953					      desc);
1954				}
1955
1956				_reach_client_remove(connection);
1957
1958			} else if (xobj == XPC_ERROR_CONNECTION_INTERRUPTED) {
1959				SCLog(TRUE, LOG_ERR,
1960				      CFSTR("<%p:%d> %s"),
1961				      connection,
1962				      xpc_connection_get_pid(connection),
1963				      desc);
1964
1965			} else {
1966				SCLog(TRUE, LOG_ERR,
1967				      CFSTR("<%p:%d> Connection error: %d : %s"),
1968				      connection,
1969				      xpc_connection_get_pid(connection),
1970				      xobj,
1971				      desc);
1972			}
1973
1974		}  else {
1975			SCLog(TRUE, LOG_ERR,
1976			      CFSTR("<%p:%d> unknown event type : %x"),
1977			      connection,
1978			      xpc_connection_get_pid(connection),
1979			      type);
1980		}
1981	});
1982
1983	xpc_connection_resume(connection);
1984
1985	return;
1986}
1987
1988
1989#pragma mark -
1990#pragma mark Reachability server "main"
1991
1992
1993__private_extern__
1994void
1995load_SCNetworkReachability(CFBundleRef bundle, Boolean bundleVerbose)
1996{
1997	xpc_connection_t	connection;
1998	const char		*name;
1999
2000	S_debug = bundleVerbose;
2001
2002	/*
2003	 * create a dictionary mapping SCNetworkReachability [CFData] digests
2004	 * to SCNetworkReachability objects.
2005	 */
2006	reach_digest_map = CFDictionaryCreateMutable(NULL,
2007						     0,
2008						     &kCFTypeDictionaryKeyCallBacks,
2009						     &kCFTypeDictionaryValueCallBacks);
2010
2011	// create XPC listener
2012	name = getenv("REACH_SERVER");
2013	if (name == NULL) {
2014		name = REACH_SERVICE_NAME;
2015	}
2016	connection = xpc_connection_create_mach_service(name,
2017							_reach_server_queue(),
2018							XPC_CONNECTION_MACH_SERVICE_LISTENER);
2019
2020	xpc_connection_set_event_handler(connection, ^(xpc_object_t event) {
2021		xpc_type_t	type;
2022
2023		type = xpc_get_type(event);
2024		if (type == XPC_TYPE_CONNECTION) {
2025			process_new_connection(event);
2026
2027		} else if (type == XPC_TYPE_ERROR) {
2028			const char	*desc;
2029
2030			desc = xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION);
2031			if (event == XPC_ERROR_CONNECTION_INVALID) {
2032				SCLog(TRUE, LOG_ERR, CFSTR("reach server: %s"), desc);
2033				xpc_release(connection);
2034			} else if (event == XPC_ERROR_CONNECTION_INTERRUPTED) {
2035				SCLog(TRUE, LOG_ERR, CFSTR("reach server: %s"), desc);
2036			} else {
2037				SCLog(TRUE, LOG_ERR,
2038				      CFSTR("reach server: Connection error: %d : %s"),
2039				      event,
2040				      desc);
2041			}
2042
2043		} else {
2044			SCLog(TRUE, LOG_ERR,
2045			      CFSTR("reach server: unknown event type : %x"),
2046			      type);
2047		}
2048	});
2049	xpc_connection_resume(connection);
2050
2051	return;
2052}
2053
2054#ifdef  MAIN
2055
2056int
2057main(int argc, char **argv)
2058{
2059//	_sc_log     = FALSE;
2060	_sc_verbose = (argc > 1) ? TRUE : FALSE;
2061	_sc_debug   = TRUE;
2062
2063	load_SCNetworkReachability(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
2064	CFRunLoopRun();
2065	/* not reached */
2066	exit(0);
2067	return 0;
2068}
2069
2070#endif  /* MAIN */
2071
2072#endif	// HAVE_REACHABILITY_SERVER
2073