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