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 <xpc/xpc.h>
32#include <xpc/private.h>
33#include <sys/rbtree.h>
34
35
36#pragma mark -
37#pragma mark Globals
38
39
40static Boolean			serverAvailable	= TRUE;
41
42
43#pragma mark -
44#pragma mark Support functions
45
46
47static void
48log_xpc_object(const char *msg, xpc_object_t obj)
49{
50	char		*desc;
51
52	desc = xpc_copy_description(obj);
53	SCLog(TRUE, LOG_DEBUG, CFSTR("%s = %s"), msg, desc);
54	free(desc);
55}
56
57
58#pragma mark -
59#pragma mark Reachability [RBT] client support
60
61
62typedef struct {
63	rb_node_t			rbn;
64	SCNetworkReachabilityRef	target;
65} reach_request_t;
66
67
68static int
69_rbt_compare_transaction_nodes(void *context, const void *n1, const void *n2)
70{
71	uint64_t	a = (uintptr_t)(((reach_request_t *)n1)->target);
72	uint64_t	b = (uintptr_t)(((reach_request_t *)n2)->target);
73
74	return (a - b);
75}
76
77
78static int
79_rbt_compare_transaction_key(void *context, const void *n1, const void *key)
80{
81	uint64_t	a = (uintptr_t)(((reach_request_t *)n1)->target);
82	uint64_t	b = *(uint64_t *)key;
83
84	return (a - b);
85}
86
87
88static rb_tree_t *
89_reach_requests_rbt()
90{
91	static dispatch_once_t		once;
92	static const rb_tree_ops_t	ops = {
93		.rbto_compare_nodes	= _rbt_compare_transaction_nodes,
94		.rbto_compare_key	= _rbt_compare_transaction_key,
95		.rbto_node_offset	= offsetof(reach_request_t, rbn),
96		.rbto_context		= NULL
97	};
98	static rb_tree_t		rbt;
99
100	dispatch_once(&once, ^{
101		rb_tree_init(&rbt, &ops);
102	});
103
104	return &rbt;
105}
106
107
108static dispatch_queue_t
109_reach_requests_rbt_queue()
110{
111	static dispatch_once_t	once;
112	static dispatch_queue_t	q;
113
114	dispatch_once(&once, ^{
115		q = dispatch_queue_create(REACH_SERVICE_NAME ".requests.rbt", NULL);
116	});
117
118	return q;
119}
120
121
122static reach_request_t *
123_reach_request_create(SCNetworkReachabilityRef target)
124{
125	reach_request_t	*request;
126
127	request = calloc(1, sizeof(*request));
128	request->target = CFRetain(target);
129
130	return request;
131}
132
133
134static void
135_reach_request_release(reach_request_t *request)
136{
137	SCNetworkReachabilityRef	target	= request->target;
138
139	CFRelease(target);
140	free(request);
141
142	return;
143}
144
145
146static void
147_reach_request_add(SCNetworkReachabilityRef target)
148{
149	uint64_t	target_id	= (uintptr_t)target;
150
151	dispatch_sync(_reach_requests_rbt_queue(), ^{
152		rb_tree_t	*rbt	= _reach_requests_rbt();
153		reach_request_t	*request;
154
155		request = rb_tree_find_node(rbt, &target_id);
156		if (request == NULL) {
157			request = _reach_request_create(target);
158			if (request == NULL || !rb_tree_insert_node(rbt, request)) {
159				__builtin_trap();
160			}
161		}
162	});
163
164	return;
165}
166
167
168static void
169_reach_request_remove(SCNetworkReachabilityRef target)
170{
171	uint64_t	target_id	= (uintptr_t)target;
172
173	dispatch_sync(_reach_requests_rbt_queue(), ^{		// FIXME ?? use dispatch_async?
174		rb_tree_t	*rbt	= _reach_requests_rbt();
175		reach_request_t	*request;
176
177		request = rb_tree_find_node(rbt, &target_id);
178		if (request != NULL) {
179			rb_tree_remove_node(rbt, request);
180			_reach_request_release(request);
181		}
182	});
183
184	return;
185}
186
187
188static SCNetworkReachabilityRef
189_reach_request_copy_target(uint64_t target_id)
190{
191	__block SCNetworkReachabilityRef	target	= NULL;
192
193	dispatch_sync(_reach_requests_rbt_queue(), ^{
194		rb_tree_t	*rbt	= _reach_requests_rbt();
195		reach_request_t	*request;
196
197		request = rb_tree_find_node(rbt, &target_id);
198		if (request != NULL) {
199			target = request->target;
200			CFRetain(target);
201		}
202	});
203
204	return target;
205}
206
207
208#pragma mark -
209#pragma mark Reachability [XPC] client support
210
211
212static void
213handle_reachability_status(SCNetworkReachabilityRef target, xpc_object_t dict)
214{
215	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
216
217	if (_sc_debug) {
218		SCLog(TRUE, LOG_INFO, CFSTR("%sgot [async] notification"),
219		      targetPrivate->log_prefix);
220//		log_xpc_object("  status", dict);
221	}
222
223	__SCNetworkReachabilityPerformConcurrent(target);
224
225	return;
226}
227
228
229static void
230handle_async_notification(SCNetworkReachabilityRef target, xpc_object_t dict)
231{
232	int64_t				op;
233	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
234
235	op = xpc_dictionary_get_int64(dict, MESSAGE_NOTIFY);
236	switch (op) {
237		case MESSAGE_REACHABILITY_STATUS :
238			handle_reachability_status(target, dict);
239			break;
240		default :
241			SCLog(TRUE, LOG_ERR, CFSTR("%sgot [async] unknown reply : %d"),
242			      targetPrivate->log_prefix,
243			      op);
244			log_xpc_object("  reply", dict);
245			break;
246	}
247
248	return;
249}
250
251
252static dispatch_queue_t
253_reach_xpc_queue()
254{
255	static dispatch_once_t	once;
256	static dispatch_queue_t	q;
257
258	dispatch_once(&once, ^{
259		q = dispatch_queue_create(REACH_SERVICE_NAME ".xpc", NULL);
260	});
261
262	return q;
263}
264
265
266static void
267_reach_connection_reconnect(xpc_connection_t connection);
268
269
270static xpc_connection_t
271_reach_connection_create()
272{
273	xpc_connection_t		c;
274#if	!TARGET_IPHONE_SIMULATOR
275	const uint64_t		flags	=	XPC_CONNECTION_MACH_SERVICE_PRIVILEGED;
276#else	// !TARGET_IPHONE_SIMULATOR
277	const uint64_t		flags	=	0;
278#endif	// !TARGET_IPHONE_SIMULATOR
279	const char			*name;
280	dispatch_queue_t		q	= _reach_xpc_queue();
281
282	// create XPC connection
283	name = getenv("REACH_SERVER");
284	if ((name == NULL) || (issetugid() != 0)) {
285		name = REACH_SERVICE_NAME;
286	}
287
288	c = xpc_connection_create_mach_service(name, q, flags);
289
290	xpc_connection_set_event_handler(c, ^(xpc_object_t xobj) {
291		xpc_type_t	type;
292
293		type = xpc_get_type(xobj);
294		if (type == XPC_TYPE_DICTIONARY) {
295			SCNetworkReachabilityRef	target;
296			uint64_t			target_id;
297
298			target_id = xpc_dictionary_get_uint64(xobj, REACH_CLIENT_TARGET_ID);
299			if (target_id == 0) {
300				SCLog(TRUE, LOG_ERR,
301				      CFSTR("reach client %p: async reply with no target [ID]"),
302				      c);
303				log_xpc_object("  reply", xobj);
304				return;
305			}
306
307			target = _reach_request_copy_target(target_id);
308			if (target == NULL) {
309//				SCLog(TRUE, LOG_ERR,
310//				      CFSTR("received unexpected target [ID] from SCNetworkReachability server"));
311//				log_xpc_object("  reply", xobj);
312				return;
313			}
314
315			xpc_retain(xobj);
316			dispatch_async(__SCNetworkReachability_concurrent_queue(), ^{
317				handle_async_notification(target, xobj);
318				CFRelease(target);
319				xpc_release(xobj);
320			});
321
322		} else if (type == XPC_TYPE_ERROR) {
323			if (xobj == XPC_ERROR_CONNECTION_INVALID) {
324				SCLog(TRUE, LOG_ERR,
325				      CFSTR("SCNetworkReachability server not available"));
326				serverAvailable = FALSE;
327			} else if (xobj == XPC_ERROR_CONNECTION_INTERRUPTED) {
328				SCLog(TRUE, LOG_DEBUG,
329				      CFSTR("SCNetworkReachability server failure, reconnecting"));
330				_reach_connection_reconnect(c);
331			} else {
332				const char	*desc;
333
334				desc = xpc_dictionary_get_string(xobj, XPC_ERROR_KEY_DESCRIPTION);
335				SCLog(TRUE, LOG_ERR,
336				      CFSTR("reach client %p: Connection error: %s"),
337				      c,
338				      desc);
339			}
340
341		} else {
342			SCLog(TRUE, LOG_ERR,
343			      CFSTR("reach client %p: unknown event type : %x"),
344			      c,
345			      type);
346		}
347	});
348	xpc_connection_resume(c);
349
350	return c;
351}
352
353
354static xpc_connection_t
355_reach_connection()
356{
357	static xpc_connection_t		c;
358	static dispatch_once_t		once;
359	static dispatch_queue_t		q;
360
361	if (!serverAvailable) {
362		// if SCNetworkReachabilty [XPC] server not available
363		return NULL;
364	}
365
366	dispatch_once(&once, ^{
367		q = dispatch_queue_create(REACH_SERVICE_NAME ".connection", NULL);
368	});
369
370	dispatch_sync(q, ^{
371		if (c == NULL) {
372			c = _reach_connection_create();
373		}
374	});
375
376	return c;
377}
378
379
380typedef void (^reach_server_reply_handler_t)(xpc_object_t reply);
381
382
383static void
384add_proc_name(xpc_object_t reqdict)
385{
386	static const char	*name	= NULL;
387	static dispatch_once_t	once;
388
389	// add the process name
390	dispatch_once(&once, ^{
391		name = getprogname();
392	});
393	xpc_dictionary_set_string(reqdict, REACH_CLIENT_PROC_NAME, name);
394
395	return;
396}
397
398
399static void
400_reach_server_target_reconnect(xpc_connection_t connection, SCNetworkReachabilityRef target, Boolean disconnect);
401
402
403static Boolean
404_reach_server_target_add(xpc_connection_t connection, SCNetworkReachabilityRef target)
405{
406	Boolean				ok		= FALSE;
407	xpc_object_t			reply;
408	xpc_object_t			reqdict;
409	Boolean				retry		= FALSE;
410	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
411
412	// create message
413	reqdict = xpc_dictionary_create(NULL, NULL, 0);
414
415	// set request
416	xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_CREATE);
417
418	// add reachability target info
419	if (targetPrivate->name != NULL) {
420		xpc_dictionary_set_string(reqdict,
421					  REACH_TARGET_NAME,
422					  targetPrivate->name);
423	}
424	if (targetPrivate->localAddress != NULL) {
425		xpc_dictionary_set_data(reqdict,
426					REACH_TARGET_LOCAL_ADDR,
427					targetPrivate->localAddress,
428					targetPrivate->localAddress->sa_len);
429	}
430	if (targetPrivate->remoteAddress != NULL) {
431		xpc_dictionary_set_data(reqdict,
432					REACH_TARGET_REMOTE_ADDR,
433					targetPrivate->remoteAddress,
434					targetPrivate->remoteAddress->sa_len);
435	}
436	if (targetPrivate->if_index != 0) {
437		xpc_dictionary_set_int64(reqdict,
438					 REACH_TARGET_IF_INDEX,
439					 targetPrivate->if_index);
440		xpc_dictionary_set_string(reqdict,
441					  REACH_TARGET_IF_NAME,
442					  targetPrivate->if_name);
443	}
444	if (targetPrivate->onDemandBypass) {
445		xpc_dictionary_set_bool(reqdict,
446					REACH_TARGET_ONDEMAND_BYPASS,
447					TRUE);
448	}
449	if (targetPrivate->resolverBypass) {
450		xpc_dictionary_set_bool(reqdict,
451					REACH_TARGET_RESOLVER_BYPASS,
452					TRUE);
453	}
454
455
456
457	// add the target [ID]
458	xpc_dictionary_set_uint64(reqdict, REACH_CLIENT_TARGET_ID, (uintptr_t)target);
459
460	// add the process name (for debugging)
461	add_proc_name(reqdict);
462
463    retry :
464
465	// send request to the SCNetworkReachability server
466	reply = xpc_connection_send_message_with_reply_sync(connection, reqdict);
467	if (reply != NULL) {
468		xpc_type_t	type;
469
470		type = xpc_get_type(reply);
471		if (type == XPC_TYPE_DICTIONARY) {
472			int64_t		status;
473
474			status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
475			ok = (status == REACH_REQUEST_REPLY_OK);
476		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
477			SCLog(TRUE, LOG_ERR,
478			      CFSTR("SCNetworkReachability server not available"));
479			serverAvailable = FALSE;
480		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
481			SCLog(TRUE, LOG_DEBUG,
482			      CFSTR("reach target %p: SCNetworkReachability server failure, retrying"),
483			      target);
484			retry = TRUE;
485		} else {
486			SCLog(TRUE, LOG_ERR,
487			      CFSTR("reach target %p: _targetAdd with unexpected reply"),
488			      target);
489			log_xpc_object("  reply", reply);
490		}
491
492		xpc_release(reply);
493	}
494
495	if (retry) {
496		retry = FALSE;
497		goto retry;
498	}
499
500	xpc_release(reqdict);
501	return ok;
502}
503
504
505static Boolean
506_reach_server_target_remove(xpc_connection_t connection, SCNetworkReachabilityRef target)
507{
508	Boolean				ok		= FALSE;
509	xpc_object_t			reply;
510	xpc_object_t			reqdict;
511	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
512
513	// create message
514	reqdict = xpc_dictionary_create(NULL, NULL, 0);
515
516	// set request
517	xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_REMOVE);
518
519	// add the target [ID]
520	xpc_dictionary_set_uint64(reqdict, REACH_CLIENT_TARGET_ID, (uintptr_t)target);
521
522	reply = xpc_connection_send_message_with_reply_sync(connection, reqdict);
523	if (reply != NULL) {
524		xpc_type_t	type;
525
526		type = xpc_get_type(reply);
527		if (type == XPC_TYPE_DICTIONARY) {
528			int64_t		status;
529
530			status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
531			switch (status) {
532				case REACH_REQUEST_REPLY_OK :
533					ok = TRUE;
534					break;
535				case REACH_REQUEST_REPLY_UNKNOWN :
536					// target not known by the server (most likely due to a
537					// SCNetworkReachability server failure), no need to
538					// remove.
539					ok = TRUE;
540					break;
541				default : {
542					SCLog(TRUE, LOG_ERR, CFSTR("%s  target remove failed"),
543					      targetPrivate->log_prefix);
544					log_xpc_object("  reply", reply);
545				}
546			}
547		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
548			SCLog(TRUE, LOG_ERR,
549			      CFSTR("SCNetworkReachability server not available"));
550			serverAvailable = FALSE;
551			ok = TRUE;
552		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
553			SCLog(TRUE, LOG_DEBUG,
554			      CFSTR("reach target %p: SCNetworkReachability server failure, no need to remove"),
555			      target);
556			ok = TRUE;
557		} else {
558			SCLog(TRUE, LOG_ERR,
559			      CFSTR("reach target %p: _targetRemove with unexpected reply"),
560			      target);
561			log_xpc_object("  reply", reply);
562		}
563
564		xpc_release(reply);
565	}
566
567	xpc_release(reqdict);
568	return ok;
569}
570
571
572static Boolean
573_reach_server_target_schedule(xpc_connection_t connection, SCNetworkReachabilityRef target)
574{
575	Boolean				ok		= FALSE;
576	xpc_object_t			reply;
577	xpc_object_t			reqdict;
578	Boolean				retry		= FALSE;
579	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
580
581	// create message
582	reqdict = xpc_dictionary_create(NULL, NULL, 0);
583
584	// set request
585	xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_SCHEDULE);
586
587	// add the target [ID]
588	xpc_dictionary_set_uint64(reqdict, REACH_CLIENT_TARGET_ID, (uintptr_t)target);
589
590    retry :
591
592	reply = xpc_connection_send_message_with_reply_sync(connection, reqdict);
593	if (reply != NULL) {
594		xpc_type_t	type;
595
596		type = xpc_get_type(reply);
597		if (type == XPC_TYPE_DICTIONARY) {
598			int64_t		status;
599
600			status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
601			switch (status) {
602				case REACH_REQUEST_REPLY_OK :
603					ok = TRUE;
604					break;
605				case REACH_REQUEST_REPLY_UNKNOWN :
606					// target not known by the server (most likely due to a
607					// SCNetworkReachability server failure), re-establish
608					// and retry scheduling.
609					retry = TRUE;
610					break;
611				default : {
612					SCLog(TRUE, LOG_ERR, CFSTR("%s  target schedule failed"),
613					      targetPrivate->log_prefix);
614					log_xpc_object("  reply", reply);
615				}
616			}
617
618			if (ok) {
619				CFRetain(target);
620			}
621		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
622			SCLog(TRUE, LOG_ERR,
623			      CFSTR("SCNetworkReachability server not available"));
624			serverAvailable = FALSE;
625		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
626			SCLog(TRUE, LOG_DEBUG,
627			      CFSTR("reach target %p: SCNetworkReachability server failure, retry schedule"),
628			      target);
629			retry = TRUE;
630		} else {
631			SCLog(TRUE, LOG_ERR,
632			      CFSTR("reach target %p: _targetSchedule with unexpected reply"),
633			      target);
634			log_xpc_object("  reply", reply);
635		}
636
637		xpc_release(reply);
638	}
639
640	if (retry) {
641		// reconnect
642		_reach_server_target_reconnect(connection, target, FALSE);
643
644		// and retry
645		retry = FALSE;
646		goto retry;
647	}
648
649	xpc_release(reqdict);
650	return ok;
651}
652
653
654static void
655_reach_reply_set_reachability(SCNetworkReachabilityRef	target,
656			      xpc_object_t		reply)
657{
658	char	*if_name;
659	size_t	len		= 0;
660
661	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
662
663	targetPrivate->serverInfo.cycle = xpc_dictionary_get_uint64(reply,
664								    REACH_STATUS_CYCLE);
665
666	targetPrivate->serverInfo.flags = xpc_dictionary_get_uint64(reply,
667								    REACH_STATUS_FLAGS);
668
669	targetPrivate->serverInfo.if_index = xpc_dictionary_get_uint64(reply,
670								       REACH_STATUS_IF_INDEX);
671
672	bzero(&targetPrivate->serverInfo.if_name, sizeof(targetPrivate->serverInfo.if_name));
673	if_name = (void *)xpc_dictionary_get_data(reply,
674						  REACH_STATUS_IF_NAME,
675						  &len);
676	if ((if_name != NULL) && (len > 0)) {
677		if (len > sizeof(targetPrivate->serverInfo.if_name)) {
678			len = sizeof(targetPrivate->serverInfo.if_name);
679		}
680
681		bcopy(if_name, targetPrivate->serverInfo.if_name, len);
682	}
683
684	targetPrivate->serverInfo.sleeping = xpc_dictionary_get_bool(reply,
685								     REACH_STATUS_SLEEPING);
686
687	if (targetPrivate->type == reachabilityTypeName) {
688		xpc_object_t		addresses;
689
690		if (targetPrivate->resolvedAddresses != NULL) {
691			CFRelease(targetPrivate->resolvedAddresses);
692			targetPrivate->resolvedAddresses = NULL;
693		}
694
695		targetPrivate->resolvedError = xpc_dictionary_get_int64(reply,
696									REACH_STATUS_RESOLVED_ERROR);
697
698		addresses = xpc_dictionary_get_value(reply, REACH_STATUS_RESOLVED_ADDRESSES);
699		if ((addresses != NULL) && (xpc_get_type(addresses) != XPC_TYPE_ARRAY)) {
700			addresses = NULL;
701		}
702
703		if ((targetPrivate->resolvedError == NETDB_SUCCESS) && (addresses != NULL)) {
704			int			i;
705			int			n;
706			CFMutableArrayRef	newAddresses;
707
708			newAddresses = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
709
710			n = xpc_array_get_count(addresses);
711			for (i = 0; i < n; i++) {
712				struct addrinfo	*sa;
713				size_t		len;
714				CFDataRef	newAddress;
715
716				sa = (struct addrinfo *)xpc_array_get_data(addresses, i, &len);
717				newAddress = CFDataCreate(NULL, (const UInt8 *)sa, len);
718				CFArrayAppendValue(newAddresses, newAddress);
719				CFRelease(newAddress);
720			}
721
722			targetPrivate->resolvedAddresses = newAddresses;
723		} else {
724			/* save the error associated with the attempt to resolve the name */
725			targetPrivate->resolvedAddresses = CFRetain(kCFNull);
726		}
727		targetPrivate->needResolve = FALSE;
728	}
729
730	return;
731}
732
733
734static Boolean
735_reach_server_target_status(xpc_connection_t connection, SCNetworkReachabilityRef target)
736{
737	Boolean				ok		= FALSE;
738	xpc_object_t			reply;
739	xpc_object_t			reqdict;
740	Boolean				retry		= FALSE;
741	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
742
743	if (_sc_debug) {
744		CFStringRef	str;
745
746		str = _SCNetworkReachabilityCopyTargetDescription(target);
747		SCLog(TRUE, LOG_INFO, CFSTR("%scheckReachability(%@)"),
748		      targetPrivate->log_prefix,
749		      str);
750		CFRelease(str);
751	}
752
753	// create message
754	reqdict = xpc_dictionary_create(NULL, NULL, 0);
755
756	// set request
757	xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_STATUS);
758
759	// add the target [ID]
760	xpc_dictionary_set_uint64(reqdict, REACH_CLIENT_TARGET_ID, (uintptr_t)target);
761
762    retry :
763
764	reply = xpc_connection_send_message_with_reply_sync(connection, reqdict);
765	if (reply != NULL) {
766		xpc_type_t	type;
767
768		type = xpc_get_type(reply);
769		if (type == XPC_TYPE_DICTIONARY) {
770			int64_t		status;
771
772			status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
773			switch (status) {
774				case REACH_REQUEST_REPLY_OK :
775					ok = TRUE;
776					break;
777				case REACH_REQUEST_REPLY_UNKNOWN :
778					// target not known by the server (most likely due to a
779					// SCNetworkReachability server failure), re-establish
780					// and retry status.
781					retry = TRUE;
782					break;
783				default :
784					SCLog(TRUE, LOG_INFO, CFSTR("%s  target status failed"),
785					      targetPrivate->log_prefix);
786					log_xpc_object("  reply", reply);
787			}
788
789			if (ok) {
790				_reach_reply_set_reachability(target, reply);
791
792				if (_sc_debug) {
793					SCLog(TRUE, LOG_INFO, CFSTR("%s  flags     = 0x%08x"),
794					      targetPrivate->log_prefix,
795					      targetPrivate->serverInfo.flags);
796					if (targetPrivate->serverInfo.if_index != 0) {
797						SCLog(TRUE, LOG_INFO, CFSTR("%s  device    = %s (%hu%s)"),
798						      targetPrivate->log_prefix,
799						      targetPrivate->serverInfo.if_name,
800						      targetPrivate->serverInfo.if_index,
801						      targetPrivate->serverInfo.sleeping ? ", z" : "");
802					}
803					if (targetPrivate->serverInfo.cycle != targetPrivate->cycle) {
804						SCLog(TRUE, LOG_INFO, CFSTR("%s  forced"),
805						      targetPrivate->log_prefix);
806					}
807				}
808			}
809		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
810			SCLog(TRUE, LOG_ERR,
811			      CFSTR("SCNetworkReachability server not available"));
812			serverAvailable = FALSE;
813			ok = TRUE;
814		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
815			SCLog(TRUE, LOG_DEBUG,
816			      CFSTR("reach target %p: SCNetworkReachability server failure, retry status"),
817			      target);
818			retry = TRUE;
819		} else {
820			SCLog(TRUE, LOG_ERR,
821			      CFSTR("reach target %p: _targetStatus with unexpected reply"),
822			      target);
823			log_xpc_object("  reply", reply);
824		}
825
826		xpc_release(reply);
827	}
828
829	if (retry) {
830		// reconnect
831		_reach_server_target_reconnect(connection, target, FALSE);
832
833		// and retry
834		retry = FALSE;
835		goto retry;
836	}
837
838	xpc_release(reqdict);
839	return ok;
840}
841
842
843static Boolean
844_reach_server_target_unschedule(xpc_connection_t connection, SCNetworkReachabilityRef target)
845{
846	Boolean				ok		= FALSE;
847	xpc_object_t			reply;
848	xpc_object_t			reqdict;
849	Boolean				retry		= FALSE;
850	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
851
852	// create message
853	reqdict = xpc_dictionary_create(NULL, NULL, 0);
854
855	// set request
856	xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_UNSCHEDULE);
857
858	// add the target [ID]
859	xpc_dictionary_set_uint64(reqdict, REACH_CLIENT_TARGET_ID, (uintptr_t)target);
860
861	reply = xpc_connection_send_message_with_reply_sync(connection, reqdict);
862	if (reply != NULL) {
863		xpc_type_t	type;
864
865		type = xpc_get_type(reply);
866		if (type == XPC_TYPE_DICTIONARY) {
867			int64_t		status;
868
869			status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
870			switch (status) {
871				case REACH_REQUEST_REPLY_OK :
872					ok = TRUE;
873					break;
874				case REACH_REQUEST_REPLY_UNKNOWN :
875					// target not known by the server (most likely due to a
876					// SCNetworkReachability server failure), re-establish
877					// but no need to unschedule.
878					retry = TRUE;
879					break;
880				default :
881					SCLog(TRUE, LOG_INFO, CFSTR("%s  target unschedule failed"),
882					      targetPrivate->log_prefix);
883					log_xpc_object("  reply", reply);
884			}
885		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
886			SCLog(TRUE, LOG_ERR,
887			      CFSTR("SCNetworkReachability server not available"));
888			serverAvailable = FALSE;
889			ok = TRUE;
890		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
891			SCLog(TRUE, LOG_DEBUG,
892			      CFSTR("reach target %p: SCNetworkReachability server failure, re-establish (but do not re-schedule)"),
893			      target);
894			retry = TRUE;
895		} else {
896			SCLog(TRUE, LOG_ERR,
897			      CFSTR("reach target %p: _targetUnschedule with unexpected reply"),
898			      target);
899			log_xpc_object("  reply", reply);
900		}
901
902		xpc_release(reply);
903	}
904
905	if (retry) {
906		// reconnect
907		targetPrivate->serverScheduled = FALSE;
908		_reach_server_target_reconnect(connection, target, FALSE);
909		ok = TRUE;
910	}
911
912	if (ok) {
913		CFRelease(target);
914	}
915
916	xpc_release(reqdict);
917	return ok;
918}
919
920
921#pragma mark -
922#pragma mark Reconnect
923
924
925static void
926_reach_server_target_reconnect(xpc_connection_t connection, SCNetworkReachabilityRef target, Boolean disconnect)
927{
928	Boolean				ok;
929	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
930
931	if (!targetPrivate->serverActive) {
932		// if target already removed
933		return;
934	}
935
936	if (disconnect) {
937		// if we should first disconnect (unschedule, remove)
938		if (targetPrivate->serverScheduled) {
939			(void) _reach_server_target_unschedule(connection, target);
940		}
941
942		(void) _reach_server_target_remove(connection, target);
943	} else {
944		// server has been restarted
945		targetPrivate->cycle = 0;
946	}
947
948	// re-associate with server
949	ok = _reach_server_target_add(connection, target);
950	if (!ok) {
951		// if we could not add the target
952		return;
953	}
954
955	if (!targetPrivate->serverScheduled) {
956		// if not scheduled
957		return;
958	}
959
960	// ... and re-schedule with server
961	ok = _reach_server_target_schedule(connection, target);
962	if (!ok) {
963		// if we could not reschedule the target
964		return;
965	}
966
967	// For addresses, update our status now.  For names, queries will
968	// be updated with a callback
969	if (targetPrivate->type != reachabilityTypeName) {
970		__SCNetworkReachabilityPerform(target);
971	}
972
973	return;
974}
975
976
977static void
978_reach_connection_reconnect(xpc_connection_t connection)
979{
980	dispatch_sync(_reach_requests_rbt_queue(), ^{
981		rb_tree_t	*rbt	= _reach_requests_rbt();
982		reach_request_t	*request;
983
984		RB_TREE_FOREACH(request, rbt) {
985			SCNetworkReachabilityRef	target;
986
987			xpc_retain(connection);
988			target = request->target;
989			CFRetain(target);
990			dispatch_async(__SCNetworkReachability_concurrent_queue(), ^{
991				_reach_server_target_reconnect(connection, target, FALSE);
992				CFRelease(target);
993				xpc_release(connection);
994			});
995		}
996	});
997
998	return;
999}
1000
1001
1002#pragma mark -
1003#pragma mark SPI (exposed)
1004
1005
1006Boolean
1007_SCNetworkReachabilityServer_snapshot(void)
1008{
1009	xpc_connection_t	c;
1010	Boolean			ok	= FALSE;
1011	xpc_object_t		reply;
1012	xpc_object_t		reqdict;
1013
1014	// initialize connection with SCNetworkReachability server
1015	c = _reach_connection();
1016	if (c == NULL) {
1017		return FALSE;
1018	}
1019
1020	// create message
1021	reqdict = xpc_dictionary_create(NULL, NULL, 0);
1022
1023	// set request
1024	xpc_dictionary_set_int64(reqdict, REACH_REQUEST, REACH_REQUEST_SNAPSHOT);
1025
1026	// add the process name (for debugging)
1027	add_proc_name(reqdict);
1028
1029    retry :
1030
1031	// send request
1032	reply = xpc_connection_send_message_with_reply_sync(c, reqdict);
1033	if (reply != NULL) {
1034		xpc_type_t	type;
1035
1036		type = xpc_get_type(reply);
1037		if (type == XPC_TYPE_DICTIONARY) {
1038			int64_t		status;
1039
1040			status = xpc_dictionary_get_int64(reply, REACH_REQUEST_REPLY);
1041			ok = (status == REACH_REQUEST_REPLY_OK);
1042		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INVALID)) {
1043			SCLog(TRUE, LOG_ERR,
1044			      CFSTR("SCNetworkReachability server not available"));
1045			serverAvailable = FALSE;
1046		} else if ((type == XPC_TYPE_ERROR) && (reply == XPC_ERROR_CONNECTION_INTERRUPTED)) {
1047			SCLog(TRUE, LOG_DEBUG,
1048			      CFSTR("SCNetworkReachability server failure, retrying"));
1049			xpc_release(reply);
1050			goto retry;
1051		} else {
1052			SCLog(TRUE, LOG_ERR,
1053			      CFSTR("_snapshot with unexpected reply"));
1054			log_xpc_object("  reply", reply);
1055		}
1056
1057		xpc_release(reply);
1058	}
1059
1060	xpc_release(reqdict);
1061	return ok;
1062}
1063
1064
1065__private_extern__
1066Boolean
1067__SCNetworkReachabilityServer_targetAdd(SCNetworkReachabilityRef target)
1068{
1069	xpc_connection_t		c;
1070	Boolean				ok;
1071	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
1072
1073	c = _reach_connection();
1074	if (c == NULL) {
1075		return FALSE;
1076	}
1077
1078	ok = _reach_server_target_add(c, target);
1079	if (ok) {
1080		_SC_ATOMIC_CMPXCHG(&targetPrivate->serverActive, FALSE, TRUE);
1081	}
1082
1083	return ok;
1084}
1085
1086
1087__private_extern__
1088void
1089__SCNetworkReachabilityServer_targetRemove(SCNetworkReachabilityRef target)
1090{
1091	xpc_connection_t		c;
1092	Boolean				ok;
1093	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
1094
1095	if (!targetPrivate->serverActive) {
1096		// if not active
1097		return;
1098	}
1099
1100	c = _reach_connection();
1101	if (c == NULL) {
1102		return;
1103	}
1104
1105	ok = _reach_server_target_remove(c, target);
1106	if (ok) {
1107		_SC_ATOMIC_CMPXCHG(&targetPrivate->serverActive, TRUE, FALSE);
1108	}
1109
1110	return;
1111}
1112
1113
1114__private_extern__
1115Boolean
1116__SCNetworkReachabilityServer_targetSchedule(SCNetworkReachabilityRef target)
1117{
1118	xpc_connection_t		c;
1119	Boolean				ok;
1120	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
1121
1122	c = _reach_connection();
1123	if (c == NULL) {
1124		return FALSE;
1125	}
1126
1127	_reach_request_add(target);
1128	ok = _reach_server_target_schedule(c, target);
1129	if (ok) {
1130		_SC_ATOMIC_CMPXCHG(&targetPrivate->serverScheduled, FALSE, TRUE);
1131	} else {
1132		_reach_request_remove(target);
1133	}
1134
1135	return ok;
1136}
1137
1138
1139__private_extern__
1140Boolean
1141__SCNetworkReachabilityServer_targetStatus(SCNetworkReachabilityRef target)
1142{
1143	xpc_connection_t		c;
1144	Boolean				ok;
1145
1146	c = _reach_connection();
1147	if (c == NULL) {
1148		return FALSE;
1149	}
1150
1151	ok = _reach_server_target_status(c, target);
1152	return ok;
1153}
1154
1155
1156__private_extern__
1157Boolean
1158__SCNetworkReachabilityServer_targetUnschedule(SCNetworkReachabilityRef target)
1159{
1160	xpc_connection_t		c;
1161	Boolean				ok;
1162	SCNetworkReachabilityPrivateRef	targetPrivate	= (SCNetworkReachabilityPrivateRef)target;
1163
1164	if (!targetPrivate->serverScheduled) {
1165		// if not scheduled
1166		return TRUE;
1167	}
1168
1169	c = _reach_connection();
1170	if (c == NULL) {
1171		return FALSE;
1172	}
1173
1174	ok = _reach_server_target_unschedule(c, target);
1175	if (ok) {
1176		_SC_ATOMIC_CMPXCHG(&targetPrivate->serverScheduled, TRUE, FALSE);
1177		_reach_request_remove(target);
1178	} else {
1179		// if unschedule failed
1180	}
1181
1182	return ok;
1183}
1184
1185
1186
1187#endif	// HAVE_REACHABILITY_SERVER
1188