+#pragma mark -
+
+
+static Boolean
+enqueueAsyncDNSRetry(SCNetworkReachabilityRef target)
+{
+ int64_t delay;
+ dispatch_source_t source;
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ MUTEX_ASSERT_HELD(&targetPrivate->lock);
+
+ source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
+ 0,
+ 0,
+ __SCNetworkReachability_concurrent_queue());
+ if (source == NULL) {
+ SCLog(TRUE, LOG_ERR, CFSTR("SCNetworkReachability retry dispatch_source_create() failed"));
+ return FALSE;
+ }
+
+ // retain the target ... and release it when the [timer] source is released
+ CFRetain(target);
+ dispatch_set_context(source, (void *)target);
+ dispatch_set_finalizer_f(source, (dispatch_function_t)CFRelease);
+
+ dispatch_source_set_event_handler(source, ^(void) {
+ __SCNetworkReachabilityPerformInlineNoLock(target, TRUE);
+ });
+
+ // start a one-shot timer
+ delay = targetPrivate->dnsRetryCount * EAI_NONAME_RETRY_DELAY_USEC * NSEC_PER_USEC;
+ dispatch_source_set_timer(source,
+ dispatch_time(DISPATCH_TIME_NOW, delay), // start
+ 0, // interval
+ 10 * NSEC_PER_MSEC); // leeway
+
+ targetPrivate->dnsRetry = source;
+ dispatch_resume(source);
+
+ return TRUE;
+}
+
+
+static void
+dequeueAsyncDNSRetry(SCNetworkReachabilityRef target)
+{
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ MUTEX_ASSERT_HELD(&targetPrivate->lock);
+
+ if (targetPrivate->dnsRetry != NULL) {
+ dispatch_source_cancel(targetPrivate->dnsRetry);
+ dispatch_release(targetPrivate->dnsRetry);
+ targetPrivate->dnsRetry = NULL;
+ }
+
+ return;
+}
+
+
+#pragma mark -
+
+
+static dispatch_queue_t
+_llq_queue()
+{
+ static dispatch_once_t once;
+ static dispatch_queue_t q;
+
+ dispatch_once(&once, ^{
+ q = dispatch_queue_create("SCNetworkReachabilty.longLivedQueries", NULL);
+ });
+
+ return q;
+}
+
+
+/*
+ * _llq_notify
+ *
+ * Called to push out a target's DNS changes
+ * - caller must be running on the _llq_queue()
+ */
+static void
+_llq_notify(const void *value, void *context)
+{
+ SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)value;
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ MUTEX_LOCK(&targetPrivate->lock);
+
+ __dns_query_end(target,
+ (targetPrivate->resolvedAddressError == NETDB_SUCCESS), // if successful query
+ dns_query_llq, // long-lived-query
+ &targetPrivate->dnsQueryStart, // start time
+ &targetPrivate->dnsQueryEnd); // end time
+
+ if (targetPrivate->scheduled) {
+ __SCNetworkReachabilityPerform(target);
+ }
+
+ // last long-lived-query end time is new start time
+ targetPrivate->dnsQueryStart = targetPrivate->dnsQueryEnd;
+
+ MUTEX_UNLOCK(&targetPrivate->lock);
+ return;
+}
+
+
+/*
+ * _llq_callback
+ *
+ * Called to process mDNSResponder long-lived-query updates
+ * - caller must be running on the _llq_queue()
+ */
+static void
+_llq_callback(DNSServiceRef sdRef,
+ DNSServiceFlags flags,
+ uint32_t interfaceIndex,
+ DNSServiceErrorType errorCode,
+ const char *hostname,
+ const struct sockaddr *address,
+ uint32_t ttl,
+ void *context)
+{
+ SCNetworkReachabilityRef target = (SCNetworkReachabilityRef)context;
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ MUTEX_LOCK(&targetPrivate->lock);
+
+ if (targetPrivate->llqTimer != NULL) {
+ dispatch_source_cancel(targetPrivate->llqTimer);
+ dispatch_release(targetPrivate->llqTimer);
+ targetPrivate->llqTimer = NULL;
+ }
+
+ switch (errorCode) {
+ case kDNSServiceErr_NoError :
+ if (address != NULL) {
+ CFMutableArrayRef addresses;
+ CFDataRef llqAddress;
+
+ if (targetPrivate->resolvedAddress != NULL) {
+ if (isA_CFArray(targetPrivate->resolvedAddress)) {
+ addresses = CFArrayCreateMutableCopy(NULL, 0, targetPrivate->resolvedAddress);
+ } else {
+ addresses = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ }
+
+ CFRelease(targetPrivate->resolvedAddress);
+ targetPrivate->resolvedAddress = NULL;
+ } else {
+ addresses = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ }
+
+ llqAddress = CFDataCreate(NULL, (void *)address, address->sa_len);
+ if (flags & kDNSServiceFlagsAdd) {
+ // add address
+ CFArrayAppendValue(addresses, llqAddress);
+ } else {
+ CFIndex i;
+
+ // remove address
+ i = CFArrayGetFirstIndexOfValue(addresses,
+ CFRangeMake(0, CFArrayGetCount(addresses)),
+ llqAddress);
+ if (i != kCFNotFound) {
+ CFArrayRemoveValueAtIndex(addresses, i);
+ }
+ }
+ CFRelease(llqAddress);
+
+ if (CFArrayGetCount(addresses) > 0) {
+ targetPrivate->resolvedAddress = addresses;
+ targetPrivate->resolvedAddressError = NETDB_SUCCESS;
+ } else {
+ // if host not found
+ targetPrivate->resolvedAddress = CFRetain(kCFNull);
+ targetPrivate->resolvedAddressError = EAI_NONAME;
+ CFRelease(addresses);
+ }
+
+ targetPrivate->needResolve = FALSE;
+ }
+ break;
+ case kDNSServiceErr_NoSuchRecord :
+ if (address != NULL) {
+ // no IPv4/IPv6 address for name (NXDOMAIN)
+ if (targetPrivate->resolvedAddress == NULL) {
+ targetPrivate->resolvedAddress = CFRetain(kCFNull);
+ targetPrivate->resolvedAddressError = EAI_NONAME;
+ }
+ targetPrivate->needResolve = FALSE;
+ }
+ break;
+ case kDNSServiceErr_Timeout :
+ if (targetPrivate->resolvedAddress == NULL) {
+ targetPrivate->resolvedAddress = CFRetain(kCFNull);
+ targetPrivate->resolvedAddressError = EAI_NONAME;
+ }
+ targetPrivate->needResolve = FALSE;
+ break;
+ default :
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("%sSCNetworkReachability _llq_callback w/error=%d"),
+ targetPrivate->log_prefix,
+ errorCode);
+ break;
+ }
+
+ MUTEX_UNLOCK(&targetPrivate->lock);
+
+ // the "more coming" flag applies to DNSService callouts for any/all
+ // hosts that are being watched so we need to keep track of the targets
+ // we have updated. When we [finally] have the last callout then we
+ // push our notifications for all of the updated targets.
+
+ if (llqUpdated == NULL) {
+ llqUpdated = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
+ }
+ CFSetAddValue(llqUpdated, target);
+
+ if (!(flags & kDNSServiceFlagsMoreComing)) {
+ CFSetApplyFunction(llqUpdated, _llq_notify, NULL);
+ CFRelease(llqUpdated);
+ llqUpdated = NULL;
+ }
+
+ return;
+}
+
+
+static Boolean
+enqueueLongLivedQuery(SCNetworkReachabilityRef target)
+{
+ SCNetworkReachabilityPrivateRef targetPrivate = (SCNetworkReachabilityPrivateRef)target;
+
+ MUTEX_ASSERT_HELD(&targetPrivate->lock);
+
+ if (targetPrivate->serv != NULL) {
+ // if "serv" provided, can't use DNSServiceGetAddrInfo
+ return FALSE;
+ }
+
+ if (memcmp(&targetPrivate->hints, &HINTS_DEFAULT, sizeof(targetPrivate->hints)) != 0) {
+ // non-default "hints" provided, can't use DNSServiceGetAddrInfo
+ return FALSE;
+ }
+
+ // mark the long lived query active
+ targetPrivate->llqActive = TRUE;
+
+ // track the DNS resolution time
+ __dns_query_start(&targetPrivate->dnsQueryStart, &targetPrivate->dnsQueryEnd);
+
+ CFRetain(target);
+ dispatch_async(_llq_queue(), ^{
+ DNSServiceErrorType err;
+ dispatch_source_t source;
+
+ MUTEX_LOCK(&targetPrivate->lock);
+
+ if (targetPrivate->llqTarget != NULL) {
+ // if already running
+ MUTEX_UNLOCK(&targetPrivate->lock);
+ CFRelease(target);
+ return;
+ }
+
+ // if needed, start interacting with mDNSResponder
+
+ if (llqMain == NULL) {
+ err = DNSServiceCreateConnection(&llqMain);
+ if (err != kDNSServiceErr_NoError) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("DNSServiceCreateConnection(&llqMain) failed, error = %d"),
+ err);
+
+ targetPrivate->llqActive = FALSE;
+
+ MUTEX_UNLOCK(&targetPrivate->lock);
+ CFRelease(target);
+ return;
+ }