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