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