]> git.saurik.com Git - apple/configd.git/blobdiff - Plugins/IPMonitor/dns-configuration.c
configd-1109.101.1.tar.gz
[apple/configd.git] / Plugins / IPMonitor / dns-configuration.c
index f60b512bd08676b0c565897bd87cfea12ea7a473..910d8e49afd75bdf1a8206092b55ddda6784089d 100644 (file)
@@ -1,15 +1,15 @@
 /*
- * Copyright (c) 2004-2011 Apple Inc. All rights reserved.
+ * Copyright (c) 2004-2020 Apple Inc. All rights reserved.
  *
  * @APPLE_LICENSE_HEADER_START@
- * 
+ *
  * This file contains Original Code and/or Modifications of Original Code
  * as defined in and that are subject to the Apple Public Source License
  * Version 2.0 (the 'License'). You may not use this file except in
  * compliance with the License. Please obtain a copy of the License at
  * http://www.opensource.apple.com/apsl/ and read it before using this
  * file.
- * 
+ *
  * The Original Code and all software distributed under the License are
  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
@@ -17,7 +17,7 @@
  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
  * Please see the License for the specific language governing rights and
  * limitations under the License.
- * 
+ *
  * @APPLE_LICENSE_HEADER_END@
  */
 
 #include <arpa/inet.h>
 #include <arpa/nameser.h>
 #include <resolv.h>
-#if    !TARGET_OS_IPHONE
-#include <notify.h>
-extern uint32_t notify_monitor_file(int token, const char *name, int flags);
-#endif // !TARGET_OS_IPHONE
+#include <CommonCrypto/CommonDigest.h>
 
 #include <CoreFoundation/CoreFoundation.h>
 #include <SystemConfiguration/SystemConfiguration.h>
 #include <SystemConfiguration/SCPrivate.h>
 #include <SystemConfiguration/SCValidation.h>
+#include "ip_plugin.h"
+
+#include "dns-configuration.h"
 
 #include <dnsinfo.h>
-#include <dnsinfo_create.h>
+#include "dnsinfo_create.h"
+#include "dnsinfo_internal.h"
+#include "dnsinfo_logging.h"
+#include "dnsinfo_private.h"
+#include "dnsinfo_server.h"
 
-#ifdef MAIN
-#undef MAIN
-#include "dnsinfo_copy.c"
-#define        MAIN
-#endif // MAIN
+#include <network_information.h>
 
 #include <dns_sd.h>
-#ifndef        kDNSServiceCompMulticastDNS
-#define        kDNSServiceCompMulticastDNS     "MulticastDNS"
-#endif
-#ifndef        kDNSServiceCompPrivateDNS
-#define        kDNSServiceCompPrivateDNS       "PrivateDNS"
-#endif
+#include <dns_sd_private.h>
+
+#if    !TARGET_OS_IPHONE
+#include <CoreServices/CoreServices.h>
+#else  // TARGET_OS_IPHONE
+#include <FSEvents/FSEvents.h>
+#endif // TARGET_OS_IPHONE
+
+#define DNS_CONFIGURATION_FLAGS_KEY    CFSTR("__FLAGS__")
+#define DNS_CONFIGURATION_IF_INDEX_KEY CFSTR("__IF_INDEX__")
+#define DNS_CONFIGURATION_ORDER_KEY    CFSTR("__ORDER__")
 
 /* multicast DNS resolver configurations */
 static CFNumberRef     S_mdns_timeout  = NULL;
@@ -75,17 +80,73 @@ static      CFNumberRef     S_mdns_timeout  = NULL;
 static CFNumberRef     S_pdns_timeout  = NULL;
 
 
+#pragma mark -
+#pragma mark DNS resolver flags
+
+
+static __inline__ boolean_t
+dns_resolver_flags_all_queries(uint32_t query_flags)
+{
+       return ((query_flags & DNS_RESOLVER_FLAGS_REQUEST_ALL_RECORDS) == DNS_RESOLVER_FLAGS_REQUEST_ALL_RECORDS);
+}
+
+
+
+
+static uint32_t
+dns_resolver_flags_service(CFDictionaryRef service, uint32_t resolver_flags)
+{
+
+       // check if the service has v4 configured
+       if (((resolver_flags & DNS_RESOLVER_FLAGS_REQUEST_A_RECORDS) == 0) &&
+           service_is_routable(service, AF_INET)) {
+               resolver_flags |= DNS_RESOLVER_FLAGS_REQUEST_A_RECORDS;
+       }
+
+       // check if the service has v6 configured
+       if (((resolver_flags & DNS_RESOLVER_FLAGS_REQUEST_AAAA_RECORDS) == 0) &&
+           service_is_routable(service, AF_INET6)) {
+               resolver_flags |= DNS_RESOLVER_FLAGS_REQUEST_AAAA_RECORDS;
+       }
+
+       return resolver_flags;
+}
+
+
+static void
+add_dns_resolver_flags(const void *key, const void *value, void *context)
+{
+#pragma unused(key)
+       CFDictionaryRef service         = (CFDictionaryRef)value;
+//     CFStringRef     serviceID       = (CFStringRef)key;
+       uint32_t        *resolver_flags = (uint32_t *)context;
+
+       if (service_is_scoped_only(service)) {
+               return;
+       }
+
+       // update resovler flags based on configured (and available) protocols
+       *resolver_flags = dns_resolver_flags_service(service, *resolver_flags);
+       return;
+}
+
+
+#pragma mark -
+#pragma mark DNS resolver configuration
+
+
 static void
 add_resolver(CFMutableArrayRef resolvers, CFMutableDictionaryRef resolver)
 {
        CFIndex         i;
+       CFStringRef     interface;
        CFIndex         n_resolvers;
        CFNumberRef     order;
        uint32_t        order_val       = 0;
 
        order = CFDictionaryGetValue(resolver, kSCPropNetDNSSearchOrder);
        if (!isA_CFNumber(order) ||
-           !CFNumberGetValue(order, kCFNumberIntType, &order_val)) {
+           !CFNumberGetValue(order, kCFNumberSInt32Type, &order_val)) {
                order     = NULL;
                order_val = 0;
        }
@@ -115,7 +176,7 @@ add_resolver(CFMutableArrayRef resolvers, CFMutableDictionaryRef resolver)
                                // if only the search order's are different
                                match_order = CFDictionaryGetValue(match_resolver, kSCPropNetDNSSearchOrder);
                                if (!isA_CFNumber(match_order) ||
-                                   !CFNumberGetValue(match_order, kCFNumberIntType, &match_order_val)) {
+                                   !CFNumberGetValue(match_order, kCFNumberSInt32Type, &match_order_val)) {
                                        match_order_val = 0;
                                }
 
@@ -129,36 +190,106 @@ add_resolver(CFMutableArrayRef resolvers, CFMutableDictionaryRef resolver)
                }
        }
 
-       order = CFNumberCreate(NULL, kCFNumberIntType, &n_resolvers);
-       CFDictionarySetValue(resolver, CFSTR("*ORDER*"), order);
+       order = CFNumberCreate(NULL, kCFNumberCFIndexType, &n_resolvers);
+       CFDictionarySetValue(resolver, DNS_CONFIGURATION_ORDER_KEY, order);
        CFRelease(order);
 
+       interface = CFDictionaryGetValue(resolver, kSCPropInterfaceName);
+       if ((interface != NULL) && !CFEqual(interface, CFSTR("*"))) {
+               uint32_t        flags;
+               unsigned int    if_index                = 0;
+               char            if_name[IF_NAMESIZE];
+               CFNumberRef     num;
+               CFBooleanRef    val;
+
+               if (_SC_cfstring_to_cstring(interface,
+                                           if_name,
+                                           sizeof(if_name),
+                                           kCFStringEncodingASCII) != NULL) {
+                       if_index = my_if_nametoindex(if_name);
+               }
+
+               if ((if_index != 0) &&
+                   (
+                    // check if this is a "scoped" configuration
+                    (CFDictionaryGetValueIfPresent(resolver, DNS_CONFIGURATION_FLAGS_KEY, (const void **)&num) &&
+                     isA_CFNumber(num) &&
+                     CFNumberGetValue(num, kCFNumberSInt32Type, &flags) &&
+                     (flags & DNS_RESOLVER_FLAGS_SCOPED) != 0)
+                    ||
+                    // check if we should scope all queries with this configuration
+                    (CFDictionaryGetValueIfPresent(resolver, DNS_CONFIGURATION_SCOPED_QUERY_KEY, (const void **)&val) &&
+                     isA_CFBoolean(val) &&
+                     CFBooleanGetValue(val))
+                   )
+                  ) {
+                       // if interface index available and it should be used
+                       num = CFNumberCreate(NULL, kCFNumberIntType, &if_index);
+                       CFDictionarySetValue(resolver, DNS_CONFIGURATION_IF_INDEX_KEY, num);
+                       CFRelease(num);
+               }
+       }
+
        CFArrayAppendValue(resolvers, resolver);
        return;
 }
 
 
+#define DNS_CONFIGURATION_CONFIGURATION_ID     CFSTR("__CONFIGURATION_ID__")
+
+
+static void
+add_resolver_signature(CFMutableDictionaryRef resolver, const char *rType, CFStringRef cID, CFIndex rIndex)
+{
+       CFStringRef     str;
+
+       str = CFStringCreateWithFormat(NULL, NULL,
+                                      CFSTR("%s:%s%@ %ld"),
+                                      rType,
+                                      (cID != NULL) ? " " : "",
+                                      (cID != NULL) ? cID : CFSTR(""),
+                                      rIndex);
+       CFDictionarySetValue(resolver, DNS_CONFIGURATION_CONFIGURATION_ID, str);
+       CFRelease(str);
+
+       return;
+}
+
+
 static void
-add_supplemental(CFMutableArrayRef resolvers, CFDictionaryRef dns, uint32_t defaultOrder)
+add_supplemental(CFMutableArrayRef     resolvers,
+                CFStringRef            serviceID,
+                CFDictionaryRef        dns,
+                uint32_t               defaultOrder,
+                Boolean                scoped)
 {
        CFArrayRef      domains;
        CFIndex         i;
        CFIndex         n_domains;
        CFArrayRef      orders;
+       CFArrayRef      servers;
 
        domains = CFDictionaryGetValue(dns, kSCPropNetDNSSupplementalMatchDomains);
        n_domains = isA_CFArray(domains) ? CFArrayGetCount(domains) : 0;
        if (n_domains == 0) {
+               // if no supplemental match domains
                return;
        }
 
        orders = CFDictionaryGetValue(dns, kSCPropNetDNSSupplementalMatchOrders);
        if (orders != NULL) {
                if (!isA_CFArray(orders) || (n_domains != CFArrayGetCount(orders))) {
+                       // if supplemental match orders... but too many/not enough
                        return;
                }
        }
 
+       servers = CFDictionaryGetValue(dns, kSCPropNetDNSServerAddresses);
+       if (!isA_CFArray(servers) || (CFArrayGetCount(servers) == 0)) {
+               // if no DNS server addresses
+               return;
+       }
+
        /*
         * yes, this is a "supplemental" resolver configuration, expand
         * the match domains and add each to the resolvers list.
@@ -202,6 +333,10 @@ add_supplemental(CFMutableArrayRef resolvers, CFDictionaryRef dns, uint32_t defa
                CFDictionaryRemoveValue(match_resolver, kSCPropNetDNSSearchDomains);
                CFDictionaryRemoveValue(match_resolver, kSCPropNetDNSSortList);
 
+               add_resolver_signature(match_resolver,
+                                      scoped ? "Supplemental/Scoped" : "Supplemental",
+                                      serviceID,
+                                      i);
                add_resolver(resolvers, match_resolver);
                CFRelease(match_resolver);
        }
@@ -214,7 +349,33 @@ add_supplemental(CFMutableArrayRef resolvers, CFDictionaryRef dns, uint32_t defa
 
 
 static void
-add_supplemental_resolvers(CFMutableArrayRef resolvers, CFDictionaryRef services, CFArrayRef service_order)
+merge_configuration_flags(CFMutableDictionaryRef newDNS, uint32_t mergeFlags)
+{
+       uint32_t        flags;
+       CFNumberRef     num;
+
+       if (!CFDictionaryGetValueIfPresent(newDNS, DNS_CONFIGURATION_FLAGS_KEY, (const void **)&num) ||
+           !isA_CFNumber(num) ||
+           !CFNumberGetValue(num, kCFNumberSInt32Type, &flags)) {
+               flags = 0;
+       }
+
+       flags |= mergeFlags;
+
+       num = CFNumberCreate(NULL, kCFNumberSInt32Type, &flags);
+       CFDictionarySetValue(newDNS, DNS_CONFIGURATION_FLAGS_KEY, num);
+       CFRelease(num);
+
+       return;
+}
+
+
+static void
+add_supplemental_resolvers(CFMutableArrayRef   resolvers,
+                          CFDictionaryRef      services,
+                          CFArrayRef           service_order,
+                          CFStringRef          scoped_interface,
+                          CFDictionaryRef      scoped_service)
 {
        const void *            keys_q[N_QUICK];
        const void **           keys    = keys_q;
@@ -238,29 +399,123 @@ add_supplemental_resolvers(CFMutableArrayRef resolvers, CFDictionaryRef services
 
        CFDictionaryGetKeysAndValues(services, keys, vals);
        for (i = 0; i < n_services; i++) {
-               uint32_t        defaultOrder;
-               CFDictionaryRef dns;
-               CFDictionaryRef service = (CFDictionaryRef)vals[i];
+               uint32_t                defaultOrder;
+               CFDictionaryRef         dns;
+               uint32_t                dns_resolver_flags;
+               CFStringRef             interface;
+               CFMutableDictionaryRef  newDNS          = NULL;
+               uint32_t                newFlags;
+               CFDictionaryRef         service         = (CFDictionaryRef)vals[i];
+               CFStringRef             serviceID       = (CFStringRef)keys[i];
+               Boolean                 trusted         = FALSE;        // trusted config w/interface
 
                if (!isA_CFDictionary(service)) {
                        continue;
                }
 
                dns = CFDictionaryGetValue(service, kSCEntNetDNS);
-               if (!isA_CFDictionary(dns)) {
+               dns = isA_CFDictionary(dns);
+               if (dns == NULL) {
                        continue;
                }
 
+               interface = CFDictionaryGetValue(dns, kSCPropInterfaceName);
+
+               if (scoped_interface != NULL) {
+                       //
+                       // we only want to add split/supplemental configurations
+                       // for queries scoped to an interface if they are NOT
+                       // associated with a "real" service
+                       //
+                       if (CFDictionaryContainsKey(service, kSCEntNetIPv4) ||
+                           CFDictionaryContainsKey(service, kSCEntNetIPv6)) {
+                               continue;
+                       }
+
+                       //
+                       // in addition, we don't want to add split/supplemental
+                       // configurations for queries scoped to an interface if
+                       // the configuration does not apply to all interfaces and
+                       // the configuration is explicitly NOT for this interface
+                       //
+                       if (!_SC_CFEqual(interface, CFSTR("*")) &&
+                           !_SC_CFEqual(interface, scoped_interface)) {
+                               continue;
+                       }
+
+                       //
+                       // lastly, check if A/AAAA queries should be issued (based
+                       // on the IP[v6] addresses).  If we would not be issuing a
+                       // query then don't bother adding the configuration.
+                       //
+                       dns_resolver_flags = dns_resolver_flags_service(scoped_service, 0);
+                       if (dns_resolver_flags == 0) {
+                               continue;
+                       }
+               }
+
                defaultOrder = DEFAULT_SEARCH_ORDER
                               - (DEFAULT_SEARCH_ORDER / 2)
-                              + ((DEFAULT_SEARCH_ORDER / 1000) * i);
+                              + ((DEFAULT_SEARCH_ORDER / 1000) * (uint32_t)i);
                if ((n_order > 0) &&
                    !CFArrayContainsValue(service_order, CFRangeMake(0, n_order), keys[i])) {
                        // push out services not specified in service order
                        defaultOrder += (DEFAULT_SEARCH_ORDER / 1000) * n_services;
                }
 
-               add_supplemental(resolvers, dns, defaultOrder);
+               /*
+                * Ensure that we have the correct InterfaceName in the DNS configuration
+                *
+                * scoped_interface  [supplemental] interface  Trusted config  DNS interface
+                * ================  ========================  ==============  =================
+                * NULL              NULL                      No              NULL (No change)
+                * NULL              en0                       No              NULL
+                * NULL              *                         No              NULL
+                * NULL              NULL                      Yes             NULL (No change)
+                * NULL              en0                       Yes             en0  (trusted config w/interface)
+                * NULL              *                         Yes             NULL
+                * en0               NULL                      N/A             en0  (scoped interface)
+                * en0               en0                       N/A             en0  (scoped interface)
+                * en0               *                         N/A             en0  (scoped interface)
+                */
+               if ((scoped_interface == NULL) && (interface == NULL)) {
+                       newDNS = (CFMutableDictionaryRef)CFRetain(dns);
+               } else {
+                       CFBooleanRef    val;
+
+                       newDNS = CFDictionaryCreateMutableCopy(NULL, 0, dns);
+                       if (scoped_interface != NULL) {
+                               CFDictionarySetValue(newDNS, kSCPropInterfaceName, scoped_interface);
+                       } else if ((interface != NULL) &&
+                                  CFDictionaryGetValueIfPresent(dns, DNS_CONFIGURATION_SCOPED_QUERY_KEY, (const void **)&val) &&
+                                  isA_CFBoolean(val) &&
+                                  CFBooleanGetValue(val)) {
+                               // leave the [trusted configuration] InterfaceName in place
+                               trusted = TRUE;
+                       } else {
+                               CFDictionaryRemoveValue(newDNS, kSCPropInterfaceName);
+                       }
+               }
+
+               // set "supplemental" flag
+               newFlags = DNS_RESOLVER_FLAGS_SUPPLEMENTAL;
+
+               if (scoped_interface != NULL) {
+                       // set "scoped" configuration flag
+                       newFlags |= DNS_RESOLVER_FLAGS_SCOPED;
+
+                       // add "Request A/AAAA query" flag(s)
+                       newFlags |= dns_resolver_flags;
+               } else if (trusted) {
+                       // use the DNS query flags from the supplemental match service
+                       newFlags |= dns_resolver_flags_service(service, 0);
+               }
+
+               merge_configuration_flags(newDNS, newFlags);
+
+               // add [scoped] resolver entry
+               add_supplemental(resolvers, serviceID, newDNS, defaultOrder, (scoped_interface != NULL));
+               CFRelease(newDNS);
        }
 
        if (keys != keys_q) {
@@ -293,7 +548,7 @@ add_multicast_resolvers(CFMutableArrayRef resolvers, CFArrayRef multicastResolve
 
                defaultOrder = DEFAULT_SEARCH_ORDER
                + (DEFAULT_SEARCH_ORDER / 2)
-               + ((DEFAULT_SEARCH_ORDER / 1000) * i);
+               + ((DEFAULT_SEARCH_ORDER / 1000) * (uint32_t)i);
 
                resolver = CFDictionaryCreateMutable(NULL,
                                                     0,
@@ -307,6 +562,7 @@ add_multicast_resolvers(CFMutableArrayRef resolvers, CFArrayRef multicastResolve
                if (S_mdns_timeout != NULL) {
                        CFDictionarySetValue(resolver, kSCPropNetDNSServerTimeout, S_mdns_timeout);
                }
+               add_resolver_signature(resolver, "Multicast DNS", NULL, i);
                add_resolver(resolvers, resolver);
                CFRelease(resolver);
                CFRelease(domain);
@@ -337,7 +593,7 @@ add_private_resolvers(CFMutableArrayRef resolvers, CFArrayRef privateResolvers)
 
                defaultOrder = DEFAULT_SEARCH_ORDER
                               - (DEFAULT_SEARCH_ORDER / 4)
-                              + ((DEFAULT_SEARCH_ORDER / 1000) * i);
+                              + ((DEFAULT_SEARCH_ORDER / 1000) * (uint32_t)i);
 
                resolver = CFDictionaryCreateMutable(NULL,
                                                     0,
@@ -351,6 +607,7 @@ add_private_resolvers(CFMutableArrayRef resolvers, CFArrayRef privateResolvers)
                if (S_pdns_timeout != NULL) {
                        CFDictionarySetValue(resolver, kSCPropNetDNSServerTimeout, S_pdns_timeout);
                }
+               add_resolver_signature(resolver, "Private DNS", NULL, i);
                add_resolver(resolvers, resolver);
                CFRelease(resolver);
                CFRelease(domain);
@@ -363,6 +620,7 @@ add_private_resolvers(CFMutableArrayRef resolvers, CFArrayRef privateResolvers)
 static CFComparisonResult
 compareBySearchOrder(const void *val1, const void *val2, void *context)
 {
+#pragma unused(context)
        CFDictionaryRef dns1    = (CFDictionaryRef)val1;
        CFDictionaryRef dns2    = (CFDictionaryRef)val2;
        CFNumberRef     num1;
@@ -372,24 +630,24 @@ compareBySearchOrder(const void *val1, const void *val2, void *context)
 
        num1 = CFDictionaryGetValue(dns1, kSCPropNetDNSSearchOrder);
        if (!isA_CFNumber(num1) ||
-           !CFNumberGetValue(num1, kCFNumberIntType, &order1)) {
+           !CFNumberGetValue(num1, kCFNumberSInt32Type, &order1)) {
                order1 = DEFAULT_SEARCH_ORDER;
        }
 
        num2 = CFDictionaryGetValue(dns2, kSCPropNetDNSSearchOrder);
        if (!isA_CFNumber(num2) ||
-           !CFNumberGetValue(num2, kCFNumberIntType, &order2)) {
+           !CFNumberGetValue(num2, kCFNumberSInt32Type, &order2)) {
                order2 = DEFAULT_SEARCH_ORDER;
        }
 
        if (order1 == order2) {
                // if same "SearchOrder", retain original orderring for configurations
-               if (CFDictionaryGetValueIfPresent(dns1, CFSTR("*ORDER*"), (const void **)&num1) &&
-                   CFDictionaryGetValueIfPresent(dns2, CFSTR("*ORDER*"), (const void **)&num2) &&
+               if (CFDictionaryGetValueIfPresent(dns1, DNS_CONFIGURATION_ORDER_KEY, (const void **)&num1) &&
+                   CFDictionaryGetValueIfPresent(dns2, DNS_CONFIGURATION_ORDER_KEY, (const void **)&num2) &&
                    isA_CFNumber(num1) &&
                    isA_CFNumber(num2) &&
-                   CFNumberGetValue(num1, kCFNumberIntType, &order1) &&
-                   CFNumberGetValue(num2, kCFNumberIntType, &order2)) {
+                   CFNumberGetValue(num1, kCFNumberSInt32Type, &order1) &&
+                   CFNumberGetValue(num2, kCFNumberSInt32Type, &order2)) {
                        if (order1 == order2) {
                                return kCFCompareEqualTo;
                        } else {
@@ -404,17 +662,17 @@ compareBySearchOrder(const void *val1, const void *val2, void *context)
 }
 
 
-static CFArrayRef
+static CF_RETURNS_RETAINED CFArrayRef
 extract_search_domains(CFMutableDictionaryRef defaultDomain, CFArrayRef supplemental)
 {
        CFStringRef             defaultDomainName       = NULL;
        uint32_t                defaultOrder            = DEFAULT_SEARCH_ORDER;
        CFArrayRef              defaultSearchDomains    = NULL;
        CFIndex                 defaultSearchIndex      = 0;
-       CFIndex                 i;
        CFMutableArrayRef       mySearchDomains;
        CFMutableArrayRef       mySupplemental          = NULL;
        CFIndex                 n_supplemental;
+       CFStringRef             trimmedDomainName;
 
        mySearchDomains = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
 
@@ -423,7 +681,7 @@ extract_search_domains(CFMutableDictionaryRef defaultDomain, CFArrayRef suppleme
 
                num = CFDictionaryGetValue(defaultDomain, kSCPropNetDNSSearchOrder);
                if (!isA_CFNumber(num) ||
-                   !CFNumberGetValue(num, kCFNumberIntType, &defaultOrder)) {
+                   !CFNumberGetValue(num, kCFNumberSInt32Type, &defaultOrder)) {
                        defaultOrder = DEFAULT_SEARCH_ORDER;
                }
 
@@ -436,7 +694,7 @@ extract_search_domains(CFMutableDictionaryRef defaultDomain, CFArrayRef suppleme
                CFIndex n_search;
 
                n_search = CFArrayGetCount(defaultSearchDomains);
-               for (i = 0; i < n_search; i++) {
+               for (int i = 0; i < n_search; i++) {
                        CFStringRef     search;
 
                        search = CFArrayGetValueAtIndex(defaultSearchDomains, i);
@@ -447,49 +705,28 @@ extract_search_domains(CFMutableDictionaryRef defaultDomain, CFArrayRef suppleme
                        }
                }
        } else {
-               defaultDomainName = _SC_trimDomain(defaultDomainName);
-               if (defaultDomainName != NULL) {
-                       CFStringRef     defaultOptions;
+               trimmedDomainName = _SC_trimDomain(defaultDomainName);
+#ifdef PERFORM_DOMAIN_EXPANSION
+               /*
+                * With BIND 4.8.3 (and earlier) resolvers, the default search list included
+                * the default domain and each of its parent domains with two or more labels.
+                */
+               if ((trimmedDomainName != NULL) &&
+                   CFStringHasSuffix(defaultDomainName, CFSTR("."))) {
+                       // if "domain" name is fully qualified
+                       CFArrayAppendValue(mySearchDomains, trimmedDomainName);
+                       CFRelease(trimmedDomainName);
+               } else if (trimmedDomainName != NULL) {
                        char            *domain;
                        int             domain_parts    = 1;
                        char            *dp;
-                       int             ndots           = 1;
-
-#define        NDOTS_OPT       "ndots="
-#define        NDOTS_OPT_LEN   (sizeof("ndots=") - 1)
-
-                       defaultOptions = CFDictionaryGetValue(defaultDomain, kSCPropNetDNSOptions);
-                       if (defaultOptions != NULL) {
-                               char    *cp;
-                               char    *options;
-
-                               options = _SC_cfstring_to_cstring(defaultOptions,
-                                                                NULL,
-                                                                0,
-                                                                kCFStringEncodingUTF8);
-                               cp = strstr(options, NDOTS_OPT);
-                               if ((cp != NULL) &&
-                                   ((cp == options) || isspace(cp[-1])) &&
-                                   ((cp[NDOTS_OPT_LEN] != '\0') && isdigit(cp[NDOTS_OPT_LEN]))) {
-                                       char    *end;
-                                       long    val;
-
-                                       cp +=  NDOTS_OPT_LEN;
-                                       errno = 0;
-                                       val = strtol(cp, &end, 10);
-                                       if ((*cp != '\0') && (cp != end) && (errno == 0) &&
-                                           ((*end == '\0') || isspace(*end)) && (val > 0)) {
-                                               ndots = val;
-                                       }
-                               }
-                               CFAllocatorDeallocate(NULL, options);
-                       }
+                       const int       ndots           = 1;
 
-                       domain = _SC_cfstring_to_cstring(defaultDomainName,
+                       domain = _SC_cfstring_to_cstring(trimmedDomainName,
                                                         NULL,
                                                         0,
                                                         kCFStringEncodingUTF8);
-                       CFRelease(defaultDomainName);
+                       CFRelease(trimmedDomainName);
 
                        // count domain parts
                        for (dp = domain; *dp != '\0'; dp++) {
@@ -499,7 +736,6 @@ extract_search_domains(CFMutableDictionaryRef defaultDomain, CFArrayRef suppleme
                        }
 
                        // move "domain" to "search" list (and expand as needed)
-                       i = LOCALDOMAINPARTS;
                        dp = domain;
                        do {
                                CFStringRef     search;
@@ -516,9 +752,19 @@ extract_search_domains(CFMutableDictionaryRef defaultDomain, CFArrayRef suppleme
                                }
 
                                dp = strchr(dp, '.') + 1;
-                       } while (++i <= (domain_parts - ndots));
+                       } while (domain_parts-- > 2);
                        CFAllocatorDeallocate(NULL, domain);
                }
+#else  // PERFORM_DOMAIN_EXPANSION
+               /*
+                * With BIND 4.9.3 (and later) resolvers, the default search list included
+                * just the default domain.
+                */
+               if (trimmedDomainName != NULL) {
+                       CFArrayAppendValue(mySearchDomains, trimmedDomainName);
+                       CFRelease(trimmedDomainName);
+               }
+#endif // PERFORM_DOMAIN_EXPANSION
        }
 
        // add any supplemental "domain" names to the search list
@@ -531,9 +777,10 @@ extract_search_domains(CFMutableDictionaryRef defaultDomain, CFArrayRef suppleme
                                  NULL);
                supplemental = mySupplemental;
        }
-       for (i = 0; i < n_supplemental; i++) {
+       for (int i = 0; i < n_supplemental; i++) {
                CFDictionaryRef dns;
                CFIndex         domainIndex;
+               int             noSearch;
                CFNumberRef     num;
                CFStringRef     options;
                CFStringRef     supplementalDomain;
@@ -563,6 +810,14 @@ extract_search_domains(CFMutableDictionaryRef defaultDomain, CFArrayRef suppleme
                        continue;
                }
 
+               num = CFDictionaryGetValue(dns, kSCPropNetDNSSupplementalMatchDomainsNoSearch);
+               if (isA_CFNumber(num) &&
+                   CFNumberGetValue(num, kCFNumberIntType, &noSearch) &&
+                   (noSearch != 0)) {
+                       CFRelease(supplementalDomain);
+                       continue;
+               }
+
                if (CFStringHasSuffix(supplementalDomain, CFSTR(".in-addr.arpa")) ||
                    CFStringHasSuffix(supplementalDomain, CFSTR(".ip6.arpa"    ))) {
                        CFRelease(supplementalDomain);
@@ -575,7 +830,7 @@ extract_search_domains(CFMutableDictionaryRef defaultDomain, CFArrayRef suppleme
 
                num = CFDictionaryGetValue(dns, kSCPropNetDNSSearchOrder);
                if (!isA_CFNumber(num) ||
-                   !CFNumberGetValue(num, kCFNumberIntType, &supplementalOrder)) {
+                   !CFNumberGetValue(num, kCFNumberSInt32Type, &supplementalOrder)) {
                        supplementalOrder = DEFAULT_SEARCH_ORDER;
                }
 
@@ -617,7 +872,9 @@ extract_search_domains(CFMutableDictionaryRef defaultDomain, CFArrayRef suppleme
 
 
 static void
-add_scoped_resolvers(CFMutableArrayRef scoped, CFDictionaryRef services, CFArrayRef service_order)
+add_scoped_resolvers(CFMutableArrayRef scoped,
+                    CFDictionaryRef    services,
+                    CFArrayRef         service_order)
 {
        const void *            keys_q[N_QUICK];
        const void **           keys    = keys_q;
@@ -637,7 +894,7 @@ add_scoped_resolvers(CFMutableArrayRef scoped, CFDictionaryRef services, CFArray
        n_order = isA_CFArray(service_order) ? CFArrayGetCount(service_order) : 0;
        if (n_order > 0) {
                order = CFArrayCreateMutableCopy(NULL, 0, service_order);
-       } else{
+       } else {
                order = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
        }
 
@@ -662,15 +919,15 @@ add_scoped_resolvers(CFMutableArrayRef scoped, CFDictionaryRef services, CFArray
        seen = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
        for (i = 0; i < n_order; i++) {
                CFDictionaryRef         dns;
-               uint32_t                flags;
-               unsigned int            if_index;
+               uint32_t                dns_resolver_flags;
                char                    if_name[IF_NAMESIZE];
                CFStringRef             interface;
                CFMutableDictionaryRef  newDNS;
-               CFNumberRef             num;
+               uint32_t                newFlags;
                CFArrayRef              searchDomains;
                CFDictionaryRef         service;
                CFStringRef             serviceID;
+               CFArrayRef              servers;
 
                serviceID = CFArrayGetValueAtIndex(order, i);
                service = CFDictionaryGetValue(services, serviceID);
@@ -685,11 +942,23 @@ add_scoped_resolvers(CFMutableArrayRef scoped, CFDictionaryRef services, CFArray
                        continue;
                }
 
+               servers = CFDictionaryGetValue(dns, kSCPropNetDNSServerAddresses);
+               if (!isA_CFArray(servers) || (CFArrayGetCount(servers) == 0)) {
+                       // if no DNS server addresses
+                       continue;
+               }
+
                interface = CFDictionaryGetValue(dns, kSCPropInterfaceName);
-               if (interface == NULL) {
-                       // if no [scoped] interface
+               if ((interface == NULL) || CFEqual(interface, CFSTR("*"))) {
+                       // if no [scoped] interface or supplemental configuration w/match-all
                        continue;
                }
+
+               if (CFDictionaryContainsKey(dns, kSCPropNetDNSServiceIdentifier)) {
+                       // if this is a service-specific resolver
+                       continue;
+               }
+
                if (CFSetContainsValue(seen, interface)) {
                        // if we've already processed this [scoped] interface
                        continue;
@@ -700,7 +969,7 @@ add_scoped_resolvers(CFMutableArrayRef scoped, CFDictionaryRef services, CFArray
                                             if_name,
                                             sizeof(if_name),
                                             kCFStringEncodingASCII) == NULL) ||
-                   ((if_index = if_nametoindex(if_name)) == 0)) {
+                   (my_if_nametoindex(if_name) == 0)) {
                        // if interface index not available
                        continue;
                }
@@ -715,22 +984,32 @@ add_scoped_resolvers(CFMutableArrayRef scoped, CFDictionaryRef services, CFArray
                        CFRelease(searchDomains);
                }
 
-               // set if_index
-               num = CFNumberCreate(NULL, kCFNumberIntType, &if_index);
-               CFDictionarySetValue(newDNS, CFSTR("*IF_INDEX*"), num);
-               CFRelease(num);
+               // get "Request A/AAAA query" flag(s)
+               dns_resolver_flags = dns_resolver_flags_service(service, 0);
+               if (dns_resolver_flags == 0) {
+                       goto skip;
+               }
 
-               // set "scoped" flag
-               flags = DNS_RESOLVER_FLAGS_SCOPED;
-               num = CFNumberCreate(NULL, kCFNumberSInt32Type, &flags);
-               CFDictionarySetValue(newDNS, CFSTR("*FLAGS*"), num);
-               CFRelease(num);
+               // set "scoped" configuration flag
+               newFlags = DNS_RESOLVER_FLAGS_SCOPED;
+
+               // add "Request A/AAAA query" flag(s)
+               newFlags |= dns_resolver_flags;
+
+               merge_configuration_flags(newDNS, newFlags);
 
                // remove keys we don't want in a [scoped] resolver
                CFDictionaryRemoveValue(newDNS, kSCPropNetDNSSupplementalMatchDomains);
                CFDictionaryRemoveValue(newDNS, kSCPropNetDNSSupplementalMatchOrders);
 
+               // add the [scoped] resolver
+               add_resolver_signature(newDNS, "Scoped", serviceID, 0);
                add_resolver(scoped, newDNS);
+
+               // add any supplemental resolver configurations for this interface
+               add_supplemental_resolvers(scoped, services, service_order, interface, service);
+
+       skip:
                CFRelease(newDNS);
        }
 
@@ -740,6 +1019,113 @@ add_scoped_resolvers(CFMutableArrayRef scoped, CFDictionaryRef services, CFArray
 }
 
 
+static void
+add_service_specific_resolvers(CFMutableArrayRef resolvers, CFDictionaryRef services)
+{
+       CFIndex                 i;
+       CFStringRef             keys_q[N_QUICK];
+       CFStringRef             *keys   = keys_q;
+       CFIndex                 n_services;
+       CFMutableSetRef         seen;
+       CFDictionaryRef         vals_q[N_QUICK];
+       CFDictionaryRef         *vals   = vals_q;
+
+       n_services = isA_CFDictionary(services) ? CFDictionaryGetCount(services) : 0;
+       if (n_services == 0) {
+               return;         // if no services
+       }
+
+       if (n_services > (CFIndex)(sizeof(keys_q) / sizeof(keys_q[0]))) {
+               keys = CFAllocatorAllocate(kCFAllocatorDefault, n_services * sizeof(keys[0]), 0);
+               vals = CFAllocatorAllocate(kCFAllocatorDefault, n_services * sizeof(vals[0]), 0);
+       }
+       CFDictionaryGetKeysAndValues(services, (const void **)keys, (const void **)vals);
+
+       seen = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
+       for (i = 0; i < n_services; i++) {
+               CFDictionaryRef         dns;
+               CFNumberRef             dns_service_identifier;
+               CFMutableDictionaryRef  newDNS;
+               uint32_t                newFlags                = 0;
+               CFDictionaryRef         service                 = vals[i];
+               CFStringRef             serviceID               = keys[i];
+               CFArrayRef              searchDomains;
+
+               dns = CFDictionaryGetValue(service, kSCEntNetDNS);
+               if (!isA_CFDictionary(dns)) {
+                       // if no DNS
+                       continue;
+               }
+
+               dns_service_identifier  = CFDictionaryGetValue(dns, kSCPropNetDNSServiceIdentifier);
+               if (!isA_CFNumber(dns_service_identifier)) {
+                       // if no DNS [vpn] Service Identifier
+                       continue;
+               }
+
+               if (CFSetContainsValue(seen, dns_service_identifier)) {
+                       my_log(LOG_ERR, "add_service_specific_resolvers: got a resolver with a duplicate service identifier, skipping");
+                       continue;
+               }
+               CFSetSetValue(seen, dns_service_identifier);
+
+               newDNS = CFDictionaryCreateMutableCopy(NULL, 0, dns);
+
+               // add "Request A/AAAA query" flag(s)
+               newFlags |= DNS_RESOLVER_FLAGS_REQUEST_ALL_RECORDS;
+
+               // set search list
+               searchDomains = extract_search_domains(newDNS, NULL);
+               if (searchDomains != NULL) {
+                       CFDictionarySetValue(newDNS, kSCPropNetDNSSearchDomains, searchDomains);
+                       CFRelease(searchDomains);
+                       searchDomains = NULL;
+               }
+
+               CFDictionaryRemoveValue(newDNS, kSCPropNetDNSSupplementalMatchDomains);
+               CFDictionaryRemoveValue(newDNS, kSCPropNetDNSSupplementalMatchOrders);
+
+               if (CFDictionaryContainsKey(newDNS, kSCPropInterfaceName)) {
+                       CFMutableDictionaryRef  interfaceScopedDNS;
+                       uint32_t                interfaceScopedFlags;
+
+                       // The dictionary has an interface, so add a interface-scoped resolver
+
+                       CFDictionarySetValue(newDNS, DNS_CONFIGURATION_SCOPED_QUERY_KEY, kCFBooleanTrue);
+
+                       interfaceScopedDNS = CFDictionaryCreateMutableCopy(NULL, 0, newDNS);
+                       interfaceScopedFlags = newFlags;
+
+                       // set "scoped" configuration flag
+                       interfaceScopedFlags |= DNS_RESOLVER_FLAGS_SCOPED;
+                       merge_configuration_flags(interfaceScopedDNS, interfaceScopedFlags);
+
+                       CFDictionaryRemoveValue(interfaceScopedDNS, kSCPropNetDNSServiceIdentifier);
+
+                       add_resolver_signature(interfaceScopedDNS, "Service", serviceID, 0);
+                       add_resolver(resolvers, interfaceScopedDNS);
+                       CFRelease(interfaceScopedDNS);
+               }
+
+               // set "service specific" configuration flag
+               newFlags |= DNS_RESOLVER_FLAGS_SERVICE_SPECIFIC;
+               merge_configuration_flags(newDNS, newFlags);
+
+               add_resolver_signature(newDNS, "Service", serviceID, 0);
+               add_resolver(resolvers, newDNS);
+               CFRelease(newDNS);
+       }
+       CFRelease(seen);
+
+       if (keys != keys_q) {
+               CFAllocatorDeallocate(kCFAllocatorDefault, keys);
+               CFAllocatorDeallocate(kCFAllocatorDefault, vals);
+       }
+
+       return;
+}
+
+
 static void
 add_default_resolver(CFMutableArrayRef resolvers,
                     CFDictionaryRef    defaultResolver,
@@ -758,12 +1144,13 @@ add_default_resolver(CFMutableArrayRef   resolvers,
        } else {
                myDefault = CFDictionaryCreateMutableCopy(NULL, 0, defaultResolver);
        }
+       assert(myDefault != NULL);
 
        // ensure that the default resolver has a search order
 
        order = CFDictionaryGetValue(myDefault, kSCPropNetDNSSearchOrder);
        if (!isA_CFNumber(order) ||
-           !CFNumberGetValue(order, kCFNumberIntType, &myOrder)) {
+           !CFNumberGetValue(order, kCFNumberSInt32Type, &myOrder)) {
                myOrder = DEFAULT_SEARCH_ORDER;
                order = CFNumberCreate(NULL, kCFNumberIntType, &myOrder);
                CFDictionarySetValue(myDefault, kSCPropNetDNSSearchOrder, order);
@@ -778,6 +1165,7 @@ add_default_resolver(CFMutableArrayRef     resolvers,
 
        // add the default resolver
 
+       add_resolver_signature(myDefault, "Default", NULL, 0);
        add_resolver(resolvers, myDefault);
        CFRelease(myDefault);
        return;
@@ -791,6 +1179,8 @@ create_resolver(CFDictionaryRef dns)
        CFNumberRef             num;
        dns_create_resolver_t   _resolver;
        CFStringRef             str;
+       CFStringRef             targetInterface         = NULL;
+       unsigned int            targetInterfaceIndex    = 0;
 
        _resolver = _dns_resolver_create();
 
@@ -823,19 +1213,53 @@ create_resolver(CFDictionaryRef dns)
                }
        }
 
+       // process interface index
+       num = CFDictionaryGetValue(dns, DNS_CONFIGURATION_IF_INDEX_KEY);
+       if (isA_CFNumber(num)) {
+               int     if_index;
+
+               if (CFNumberGetValue(num, kCFNumberIntType, &if_index)) {
+                       char            buf[IFNAMSIZ];
+                       const char      *if_name        = NULL;
+
+                       if (if_index != 0) {
+                               if_name = my_if_indextoname(if_index, buf);
+                               if (if_name != NULL) {
+                                       targetInterface = CFStringCreateWithCString(NULL,
+                                                                                   if_name,
+                                                                                   kCFStringEncodingASCII);
+                                       targetInterfaceIndex = if_index;
+                               }
+                       }
+
+                       _dns_resolver_set_if_index(&_resolver, if_index, if_name);
+               }
+       }
+
+       // process flags
+       num = CFDictionaryGetValue(dns, DNS_CONFIGURATION_FLAGS_KEY);
+       if (isA_CFNumber(num)) {
+               uint32_t        flags;
+
+               if (CFNumberGetValue(num, kCFNumberSInt32Type, &flags)) {
+                       _dns_resolver_set_flags(&_resolver, flags);
+               }
+       }
+
        // process nameserver addresses
+       // Note: the flags may be overwritten if the resolver has LOOPBACK addresses
        list = CFDictionaryGetValue(dns, kSCPropNetDNSServerAddresses);
        if (isA_CFArray(list)) {
                CFIndex i;
-               CFIndex n       = CFArrayGetCount(list);
+               CFIndex n       = CFArrayGetCount(list);
 
                for (i = 0; i < n; i++) {
                        union {
-                               struct sockaddr         sa;
-                               struct sockaddr_in      sin;
-                               struct sockaddr_in6     sin6;
+                               struct sockaddr         sa;
+                               struct sockaddr_in      sin;
+                               struct sockaddr_in6     sin6;
                        } addr;
-                       char    buf[128];
+                       char                            buf[64];
 
                        str = CFArrayGetValueAtIndex(list, i);
                        if (!isA_CFString(str)) {
@@ -846,27 +1270,21 @@ create_resolver(CFDictionaryRef dns)
                                continue;
                        }
 
-                       bzero(&addr, sizeof(addr));
-                       if (inet_aton(buf, &addr.sin.sin_addr) == 1) {
-                               /* if IPv4 address */
-                               addr.sin.sin_len    = sizeof(addr.sin);
-                               addr.sin.sin_family = AF_INET;
-                               _dns_resolver_add_nameserver(&_resolver, &addr.sa);
-                       } else if (inet_pton(AF_INET6, buf, &addr.sin6.sin6_addr) == 1) {
-                               /* if IPv6 address */
-                               char    *p;
-
-                               p = strchr(buf, '%');
-                               if (p != NULL) {
-                                       addr.sin6.sin6_scope_id = if_nametoindex(p + 1);
-                               }
-
-                               addr.sin6.sin6_len    = sizeof(addr.sin6);
-                               addr.sin6.sin6_family = AF_INET6;
-                               _dns_resolver_add_nameserver(&_resolver, &addr.sa);
-                       } else {
+                       if (_SC_string_to_sockaddr(buf, AF_UNSPEC, (void *)&addr, sizeof(addr)) == NULL) {
                                continue;
                        }
+
+                       if ((addr.sa.sa_family == AF_INET6) &&
+                           IN6_IS_ADDR_LINKLOCAL(&addr.sin6.sin6_addr) &&
+                           (addr.sin6.sin6_scope_id == 0) &&
+                           (targetInterfaceIndex != 0)) {
+                               // for link local [IPv6] addresses, if the scope id is not
+                               // set then we should use the interface associated with the
+                               // resolver configuration
+                               addr.sin6.sin6_scope_id = targetInterfaceIndex;
+                       }
+
+                       _dns_resolver_add_nameserver(&_resolver, &addr.sa);
                }
        }
 
@@ -875,7 +1293,7 @@ create_resolver(CFDictionaryRef dns)
        if (isA_CFNumber(num)) {
                uint32_t        order;
 
-               if (CFNumberGetValue(num, kCFNumberIntType, &order)) {
+               if (CFNumberGetValue(num, kCFNumberSInt32Type, &order)) {
                        _dns_resolver_set_order(&_resolver, order);
                }
        }
@@ -905,7 +1323,7 @@ create_resolver(CFDictionaryRef dns)
                                *slash = '\0';
                        }
 
-                       bzero(&sortaddr, sizeof(sortaddr));
+                       memset(&sortaddr, 0, sizeof(sortaddr));
                        if (inet_aton(buf, &sortaddr.address) != 1) {
                                /* if address not valid */
                                continue;
@@ -970,41 +1388,47 @@ create_resolver(CFDictionaryRef dns)
                }
        }
 
-       // process interface index
-       num = CFDictionaryGetValue(dns, CFSTR("*IF_INDEX*"));
+       num = CFDictionaryGetValue(dns, kSCPropNetDNSServiceIdentifier);
        if (isA_CFNumber(num)) {
-               int     if_index;
+               int     dns_service_identifier;
 
-               if (CFNumberGetValue(num, kCFNumberIntType, &if_index)) {
-                       _dns_resolver_set_if_index(&_resolver, if_index);
+               if (CFNumberGetValue(num, kCFNumberIntType, &dns_service_identifier)) {
+                       _dns_resolver_set_service_identifier(&_resolver, (uint32_t)dns_service_identifier);
                }
        }
 
-       // process flags
-       num = CFDictionaryGetValue(dns, CFSTR("*FLAGS*"));
-       if (isA_CFNumber(num)) {
-               uint32_t        flags;
+       // process configuration ID
+       str = CFDictionaryGetValue(dns, DNS_CONFIGURATION_CONFIGURATION_ID);
+       if (isA_CFString(str) && (CFStringGetLength(str) > 0)) {
+               char    *cID;
 
-               if (CFNumberGetValue(num, kCFNumberSInt32Type, &flags)) {
-                       _dns_resolver_set_flags(&_resolver, flags);
+               cID = _SC_cfstring_to_cstring(str, NULL, 0, kCFStringEncodingUTF8);
+               if (cID != NULL) {
+                       _dns_resolver_set_configuration_identifier(&_resolver, cID);
+                       CFAllocatorDeallocate(NULL, cID);
                }
        }
 
+       if (targetInterface != NULL) {
+               CFRelease(targetInterface);
+       }
+
        return _resolver;
 }
 
 
 static __inline__ Boolean
-isScopedDNS(CFDictionaryRef dns)
+isScopedConfiguration(CFDictionaryRef dns)
 {
        uint32_t        flags;
        CFNumberRef     num;
 
        if ((dns != NULL) &&
-           CFDictionaryGetValueIfPresent(dns, CFSTR("*FLAGS*"), (const void **)&num) &&
+           CFDictionaryGetValueIfPresent(dns, DNS_CONFIGURATION_FLAGS_KEY, (const void **)&num) &&
            (num != NULL) &&
            CFNumberGetValue(num, kCFNumberSInt32Type, &flags) &&
            ((flags & DNS_RESOLVER_FLAGS_SCOPED) != 0)) {
+               // if scoped
                return TRUE;
        }
 
@@ -1039,8 +1463,8 @@ compareDomain(const void *val1, const void *val2, void *context)
        }
 
        // sort non-scoped before scoped
-       scoped1 = isScopedDNS(dns1);
-       scoped2 = isScopedDNS(dns2);
+       scoped1 = isScopedConfiguration(dns1);
+       scoped2 = isScopedConfiguration(dns2);
        if (scoped1 != scoped2) {
                if (!scoped1) {
                        return kCFCompareLessThan;
@@ -1049,11 +1473,6 @@ compareDomain(const void *val1, const void *val2, void *context)
                }
        }
 
-       // must have domain names for any further comparisons
-       if ((domain1 == NULL) || (domain2 == NULL)) {
-               return kCFCompareEqualTo;
-       }
-
        // forward (A, AAAA) domains sort before reverse (PTR) domains
        rev1 = CFStringHasSuffix(domain1, CFSTR(".arpa"));
        rev2 = CFStringHasSuffix(domain2, CFSTR(".arpa"));
@@ -1102,28 +1521,63 @@ compareDomain(const void *val1, const void *val2, void *context)
 }
 
 
+static __inline__ Boolean
+needsMergeWithDefaultConfiguration(CFDictionaryRef dns)
+{
+       uint32_t        flags;
+       CFNumberRef     num;
+
+       if ((dns != NULL) &&
+           CFDictionaryGetValueIfPresent(dns, DNS_CONFIGURATION_FLAGS_KEY, (const void **)&num) &&
+           (num != NULL) &&
+           CFNumberGetValue(num, kCFNumberSInt32Type, &flags)) {
+
+               // check if merge needed (at all)
+               if (dns_resolver_flags_all_queries(flags)) {
+                       // if we are already querying for both A/AAAA
+                       return FALSE;
+               }
+
+               // check if scoped or service-specific
+               if (((flags & DNS_RESOLVER_FLAGS_SCOPED          ) != 0) ||
+                   ((flags & DNS_RESOLVER_FLAGS_SERVICE_SPECIFIC) != 0)) {
+                       // yes, skip merge
+                       return FALSE;
+               }
+       }
+
+       return TRUE;
+}
+
+
 __private_extern__
-void
+Boolean
 dns_configuration_set(CFDictionaryRef   defaultResolver,
                      CFDictionaryRef   services,
                      CFArrayRef        serviceOrder,
                      CFArrayRef        multicastResolvers,
-                     CFArrayRef        privateResolvers)
+                     CFArrayRef        privateResolvers,
+                     CFDictionaryRef   *globalResolver)
 {
+       dns_create_config_t     dns_create_config;
+       Boolean                 changed                 = FALSE;
        CFIndex                 i;
        CFMutableDictionaryRef  myDefault;
-       Boolean                 myOrderAdded    = FALSE;
-       CFArrayRef              mySearchDomains = NULL;
+       Boolean                 myOrderAdded            = FALSE;
+       CFArrayRef              mySearchDomains         = NULL;
        CFIndex                 n_resolvers;
        CFMutableArrayRef       resolvers;
+       unsigned char           signature[CC_SHA256_DIGEST_LENGTH];
+       static unsigned char    signature_last[CC_SHA256_DIGEST_LENGTH];
 
        // establish list of resolvers
 
        resolvers = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+       assert(resolvers != NULL);
 
        // collect (and add) any "supplemental" resolver configurations
 
-       add_supplemental_resolvers(resolvers, services, serviceOrder);
+       add_supplemental_resolvers(resolvers, services, serviceOrder, NULL, NULL);
 
        // collect (and add) any "private" resolver configurations
 
@@ -1131,6 +1585,16 @@ dns_configuration_set(CFDictionaryRef   defaultResolver,
 
        // add the "default" resolver
 
+       if (defaultResolver != NULL) {
+               CFArrayRef      servers;
+
+               servers = CFDictionaryGetValue(defaultResolver, kSCPropNetDNSServerAddresses);
+               if (!isA_CFArray(servers) || (CFArrayGetCount(servers) == 0)) {
+                       // if no DNS server addresses
+                       defaultResolver = NULL;
+               }
+       }
+
        add_default_resolver(resolvers, defaultResolver, &myOrderAdded, &mySearchDomains);
 
        // collect (and add) any "multicast" resolver configurations
@@ -1141,6 +1605,10 @@ dns_configuration_set(CFDictionaryRef   defaultResolver,
 
        add_scoped_resolvers(resolvers, services, serviceOrder);
 
+       // collect (and add) any "service-specific" resolver configurations
+
+       add_service_specific_resolvers(resolvers, services);
+
        // sort resolvers
 
        n_resolvers = CFArrayGetCount(resolvers);
@@ -1178,9 +1646,9 @@ dns_configuration_set(CFDictionaryRef   defaultResolver,
 
                resolver = CFArrayGetValueAtIndex(resolvers, 1);
                if (CFDictionaryContainsKey(resolver, kSCPropNetDNSDomainName) ||
-                   isScopedDNS(resolver)) {
+                   isScopedConfiguration(resolver)) {
                        // if not a supplemental "default" resolver (a domain name is
-                       // present) or a if it's a scoped resolver
+                       // present) or if it's a scoped configuration
                        CFDictionaryRemoveValue(myDefault, kSCPropNetDNSSearchOrder);
                }
        }
@@ -1193,61 +1661,129 @@ dns_configuration_set(CFDictionaryRef   defaultResolver,
                /*
                 * if no default and no supplemental/scoped resolvers
                 */
-               if (!_dns_configuration_store(NULL)) {
-                       SCLog(TRUE, LOG_ERR, CFSTR("dns_configuration_set: could not store configuration"));
-               }
+               dns_create_config = NULL;
        } else {
-               dns_create_config_t     _config;
+               uint32_t        default_resolver_flags  = 0;
+               Boolean         have_default_flags      = FALSE;
 
                /*
                 * if default and/or supplemental/scoped resolvers are defined
                 */
-               _config = _dns_configuration_create();
-
-               // add resolvers
+               dns_create_config = _dns_configuration_create();
 
                for (i = 0; i < n_resolvers; i++) {
+                       Boolean                 merge_default_flags;
                        CFDictionaryRef         resolver;
                        dns_create_resolver_t   _resolver;
 
                        resolver = CFArrayGetValueAtIndex(resolvers, i);
+
+                       merge_default_flags = needsMergeWithDefaultConfiguration(resolver);
+                       if (merge_default_flags) {
+                               CFMutableDictionaryRef  new_resolver;
+
+                               if (!have_default_flags) {
+                                       CFDictionaryApplyFunction(services,
+                                                                 add_dns_resolver_flags,
+                                                                 &default_resolver_flags);
+                                       have_default_flags = TRUE;
+                               }
+
+                               new_resolver = CFDictionaryCreateMutableCopy(NULL, 0, resolver);
+                               merge_configuration_flags(new_resolver, default_resolver_flags);
+                               resolver = new_resolver;
+                       }
+
+                       if (i == 0) {
+                               *globalResolver = CFRetain(resolver);
+                       }
+
                        _resolver = create_resolver(resolver);
-                       _dns_configuration_add_resolver(&_config, _resolver);
+                       _dns_configuration_add_resolver(&dns_create_config, _resolver);
                        _dns_resolver_free(&_resolver);
+
+                       if (merge_default_flags) {
+                               CFRelease(resolver);
+                       }
                }
 
-#if    !TARGET_OS_IPHONE
                // add flatfile resolvers
 
-               _dnsinfo_flatfile_add_resolvers(&_config);
-#endif // !TARGET_OS_IPHONE
+               _dnsinfo_flatfile_set_flags(default_resolver_flags);
+               _dnsinfo_flatfile_add_resolvers(&dns_create_config);
+       }
 
-               // save configuration
+       // check if the configuration changed
+       _dns_configuration_signature(&dns_create_config, signature, sizeof(signature));
+       if (bcmp(signature, signature_last, sizeof(signature)) != 0) {
+               // save [new] signature
+               memcpy(signature_last, signature, sizeof(signature));
+
+               my_log(LOG_INFO, "Updating DNS configuration");
+               if (dns_create_config != NULL) {
+                       dns_config_t            *dns_config     = NULL;
+                       _dns_config_buf_t       *dns_config_buf;
+                       size_t                  n;
+
+                       n = sizeof(_dns_config_buf_t);
+                       n += ntohl(((_dns_config_buf_t *)dns_create_config)->n_attribute);
+                       dns_config_buf = _dns_configuration_buffer_create((void *)dns_create_config, n);
+                       if (dns_config_buf != NULL) {
+                               dns_config = _dns_configuration_buffer_expand(dns_config_buf);
+                               if (dns_config == NULL) {
+                                       // if we were unable to expand the configuration
+                                       _dns_configuration_buffer_free(&dns_config_buf);
+                               }
+                       }
 
-               if (!_dns_configuration_store(&_config)) {
-                       SCLog(TRUE, LOG_ERR, CFSTR("dns_configuration_set: could not store configuration"));
+                       if (dns_config != NULL) {
+                               _dns_configuration_log(dns_config, TRUE, NULL);
+                               free(dns_config);
+                       }
+               } else {
+                       my_log(LOG_INFO, "*** No DNS configuration");
                }
+#ifndef        MAIN
+               // save [new] configuration
+               if (!_dns_configuration_store(&dns_create_config)) {
+                       my_log(LOG_ERR, "could not store configuration");
+               }
+#endif // MAIN
 
-               _dns_configuration_free(&_config);
+               changed = TRUE;
+       }
+
+       if (dns_create_config != NULL) {
+               _dns_configuration_free(&dns_create_config);
        }
 
        CFRelease(resolvers);
-       return;
+       return changed;
 }
 
 
-#if    !TARGET_OS_IPHONE
 static SCDynamicStoreRef       dns_configuration_store;
 static SCDynamicStoreCallBack  dns_configuration_callout;
 
 static void
-dns_configuration_changed(CFMachPortRef port, void *msg, CFIndex size, void *info)
+dns_configuration_changed(ConstFSEventStreamRef                streamRef,
+                         void                          *clientCallBackInfo,
+                         size_t                        numEvents,
+                         void                          *eventPaths,
+                         const FSEventStreamEventFlags *eventFlags,
+                         const FSEventStreamEventId    *eventIds)
 {
-       CFStringRef     key             = CFSTR(_PATH_RESOLVER_DIR);
-       CFArrayRef      keys;
-       Boolean         resolvers_now;
-       static Boolean  resolvers_save  = FALSE;
-       struct stat     statbuf;
+#pragma unused(streamRef)
+#pragma unused(clientCallBackInfo)
+#pragma unused(numEvents)
+#pragma unused(eventPaths)
+#pragma unused(eventFlags)
+#pragma unused(eventIds)
+       static const CFStringRef        key     = CFSTR(_PATH_RESOLVER_DIR);
+       CFArrayRef                      keys;
+       Boolean                         resolvers_now;
+       static Boolean                  resolvers_save  = FALSE;
+       struct stat                     statbuf;
 
        resolvers_now = (stat(_PATH_RESOLVER_DIR, &statbuf) == 0);
        if (!resolvers_save && (resolvers_save == resolvers_now)) {
@@ -1258,61 +1794,89 @@ dns_configuration_changed(CFMachPortRef port, void *msg, CFIndex size, void *inf
        }
        resolvers_save = resolvers_now;
 
-       SCLog(TRUE, LOG_DEBUG, CFSTR(_PATH_RESOLVER_DIR " changed"));
+       my_log(LOG_INFO, _PATH_RESOLVER_DIR " changed");
 
        // fake a "DNS" change
        keys = CFArrayCreate(NULL, (const void **)&key, 1, &kCFTypeArrayCallBacks);
        (*dns_configuration_callout)(dns_configuration_store, keys, NULL);
        CFRelease(keys);
+
        return;
 }
 
 
+static Boolean
+normalize_path(const char *file_name, char resolved_name[PATH_MAX])
+{
+       char    *ch;
+       char    path[PATH_MAX];
+
+       strlcpy(path, file_name, sizeof(path));
+       if (realpath(path, resolved_name) != NULL) {
+               // if the path exists
+               return TRUE;
+       }
+
+       ch = strrchr(path, '/');
+       if (ch != NULL) {
+               *ch = '\0';
+               if (realpath(path, resolved_name) != NULL) {
+                       // if a parent path exists
+                       strlcat(resolved_name, "/", PATH_MAX);
+                       strlcat(resolved_name, ch+1, PATH_MAX);
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+
 __private_extern__
 void
 dns_configuration_monitor(SCDynamicStoreRef store, SCDynamicStoreCallBack callout)
 {
-       CFMachPortRef           mp;
-       mach_port_t             notify_port;
-       int                     notify_token;
-       CFRunLoopSourceRef      rls;
-       uint32_t                status;
+       FSEventStreamContext            context = { 0,          // version
+                                                   NULL,       // info
+                                                   NULL,       // retain
+                                                   NULL,       // release
+                                                   NULL };     // copyDescription
+       FSEventStreamCreateFlags        flags   = kFSEventStreamCreateFlagUseCFTypes
+                                                 | kFSEventStreamCreateFlagFileEvents
+                                                 | kFSEventStreamCreateFlagWatchRoot;
+       FSEventStreamRef                monitor;
+       CFStringRef                     path;
+       CFMutableArrayRef               paths;
+       char                            resolver_directory_path[PATH_MAX];
+
+       if (!normalize_path(_PATH_RESOLVER_DIR, resolver_directory_path)) {
+               my_log(LOG_ERR, "Not monitoring \"%s\", could not resolve directory path", _PATH_RESOLVER_DIR);
+               return;
+       }
 
        dns_configuration_store   = store;
        dns_configuration_callout = callout;
 
-       status = notify_register_mach_port(_PATH_RESOLVER_DIR, &notify_port, 0, &notify_token);
-       if (status != NOTIFY_STATUS_OK) {
-               SCLOG(NULL, NULL, ASL_LEVEL_ERR, CFSTR("notify_register_mach_port() failed"));
-               return;
-       }
+       paths = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+       path = CFStringCreateWithCString(NULL, resolver_directory_path, kCFStringEncodingUTF8);
+       CFArrayAppendValue(paths, path);
+       CFRelease(path);
 
-       status = notify_monitor_file(notify_token, "/private" _PATH_RESOLVER_DIR, 0);
-       if (status != NOTIFY_STATUS_OK) {
-               SCLOG(NULL, NULL, ASL_LEVEL_ERR, CFSTR("notify_monitor_file() failed"));
-               (void)notify_cancel(notify_token);
-               return;
-       }
+       monitor = FSEventStreamCreate(NULL,                             // allocator
+                                     dns_configuration_changed,        // callback
+                                     &context,                         // context
+                                     paths,                            // pathsToWatch (CFArray)
+                                     kFSEventStreamEventIdSinceNow,    // sinceWhen
+                                     0.0,                              // latency
+                                     flags);                           // flags
 
-       mp = _SC_CFMachPortCreateWithPort("IPMonitor/dns_configuration",
-                                         notify_port,
-                                         dns_configuration_changed,
-                                         NULL);
+       CFRelease(paths);
 
-       rls = CFMachPortCreateRunLoopSource(NULL, mp, -1);
-       if (rls == NULL) {
-               SCLOG(NULL, NULL, ASL_LEVEL_ERR, CFSTR("SCDynamicStoreCreateRunLoopSource() failed"));
-               CFRelease(mp);
-               (void)notify_cancel(notify_token);
-               return;
-       }
-       CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
-       CFRelease(rls);
+       FSEventStreamScheduleWithRunLoop(monitor, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+       FSEventStreamStart(monitor);
 
-       CFRelease(mp);
        return;
 }
-#endif // !TARGET_OS_IPHONE
 
 
 __private_extern__
@@ -1334,6 +1898,10 @@ dns_configuration_init(CFBundleRef bundle)
 }
 
 
+#pragma mark -
+#pragma mark Standalone test code
+
+
 #ifdef MAIN
 
 static void
@@ -1359,7 +1927,18 @@ split(const void * key, const void * value, void * context)
 
        if (CFEqual(entity_id, kSCEntNetIPv4) ||
            CFEqual(entity_id, kSCEntNetIPv6)) {
-               CFStringRef     interface;
+               CFDictionaryRef         dict;
+               CFStringRef             interface;
+
+               if (CFEqual(entity_id, kSCEntNetIPv4)) {
+                       dict = ipv4_dict_create(value);
+               }
+               else {
+                       dict = ipv6_dict_create(value);
+               }
+               if (dict != NULL) {
+                       CFDictionarySetValue(state_dict, entity_id, dict);
+               }
 
                interface = CFDictionaryGetValue((CFDictionaryRef)value, kSCPropInterfaceName);
                if (interface != NULL) {
@@ -1371,9 +1950,9 @@ split(const void * key, const void * value, void * context)
                                new_dns = CFDictionaryCreateMutableCopy(NULL, 0, dns);
                        } else {
                                new_dns = CFDictionaryCreateMutable(NULL,
-                                                               0,
-                                                               &kCFTypeDictionaryKeyCallBacks,
-                                                               &kCFTypeDictionaryValueCallBacks);
+                                                                   0,
+                                                                   &kCFTypeDictionaryKeyCallBacks,
+                                                                   &kCFTypeDictionaryValueCallBacks);
                        }
                        CFDictionarySetValue(new_dns, kSCPropInterfaceName, interface);
                        CFDictionarySetValue(state_dict, kSCEntNetDNS, new_dns);
@@ -1415,6 +1994,7 @@ int
 main(int argc, char **argv)
 {
        CFDictionaryRef         entities;
+       CFDictionaryRef         globalResolver  = NULL;
        CFStringRef             key;
        CFArrayRef              multicast_resolvers;
        CFStringRef             pattern;
@@ -1428,7 +2008,8 @@ main(int argc, char **argv)
        CFDictionaryRef         state_global_ipv4;
        SCDynamicStoreRef       store;
 
-       _sc_log     = FALSE;
+       _sc_debug   = TRUE;
+       _sc_log     = kSCLogDestinationFile;
        _sc_verbose = (argc > 1) ? TRUE : FALSE;
 
        store = SCDynamicStoreCreate(NULL, CFSTR("TEST"), NULL, NULL);
@@ -1510,17 +2091,19 @@ main(int argc, char **argv)
 
        // update DNS configuration
        dns_configuration_init(CFBundleGetMainBundle());
-       dns_configuration_set(primaryDNS,
-                             service_state_dict,
-                             service_order,
-                             multicast_resolvers,
-                             private_resolvers);
+       (void)dns_configuration_set(primaryDNS,
+                                   service_state_dict,
+                                   service_order,
+                                   multicast_resolvers,
+                                   private_resolvers,
+                                   &globalResolver);
 
        // cleanup
        if (setup_global_ipv4 != NULL)  CFRelease(setup_global_ipv4);
        if (state_global_ipv4 != NULL)  CFRelease(state_global_ipv4);
        if (multicast_resolvers != NULL) CFRelease(multicast_resolvers);
        if (private_resolvers != NULL)  CFRelease(private_resolvers);
+       if (globalResolver != NULL)     CFRelease(globalResolver);
        CFRelease(service_state_dict);
        CFRelease(store);